#include #include #include #include #include #include #include #include #include namespace { bool has_field(const Lardon3DAcquisitionMetadata &metadata, uint32_t field) { return (metadata.present_fields & field) != 0u; } struct BoundedText { const char *data; size_t length; bool available; }; BoundedText bounded_text(const char *text, size_t capacity) { const void *terminator = std::memchr(text, '\0', capacity); if (terminator == nullptr || terminator == text) { return {text, 0u, false}; } const size_t length = static_cast(static_cast(terminator) - text); return {text, length, true}; } bool same_text(const char *left, size_t left_capacity, const char *right, size_t right_capacity) { const BoundedText left_text = bounded_text(left, left_capacity); const BoundedText right_text = bounded_text(right, right_capacity); return left_text.available && right_text.available && left_text.length == right_text.length && std::memcmp(left_text.data, right_text.data, left_text.length) == 0; } bool different_text(const char *left, size_t left_capacity, const char *right, size_t right_capacity) { const BoundedText left_text = bounded_text(left, left_capacity); const BoundedText right_text = bounded_text(right, right_capacity); return left_text.available && right_text.available && (left_text.length != right_text.length || std::memcmp(left_text.data, right_text.data, left_text.length) != 0); } bool copy_padded_text(char *destination, size_t capacity, const unsigned char *source, size_t source_size) { size_t length = source_size; while (length > 0u && (source[length - 1u] == '\0' || source[length - 1u] == ' ')) { --length; } if (length >= capacity) { destination[0] = '\0'; return false; } if (length > 0u) { std::memcpy(destination, source, length); } destination[length] = '\0'; return length != 0u; } void extract_raw_fixed_text(const libraw_data_t &raw, Lardon3DAcquisitionMetadata &metadata) { if (copy_padded_text(metadata.make, sizeof(metadata.make), reinterpret_cast(raw.idata.make), sizeof(raw.idata.make))) { metadata.present_fields |= LARDON3D_ACQUISITION_FIELD_MAKE; } if (copy_padded_text(metadata.model, sizeof(metadata.model), reinterpret_cast(raw.idata.model), sizeof(raw.idata.model))) { metadata.present_fields |= LARDON3D_ACQUISITION_FIELD_MODEL; } if (copy_padded_text(metadata.body_serial, sizeof(metadata.body_serial), reinterpret_cast(raw.shootinginfo.BodySerial), sizeof(raw.shootinginfo.BodySerial))) { metadata.present_fields |= LARDON3D_ACQUISITION_FIELD_BODY_SERIAL; } } void extract_raw_image_unique_id(const libraw_data_t &raw, Lardon3DAcquisitionMetadata &metadata) { if (copy_padded_text(metadata.image_unique_id, sizeof(metadata.image_unique_id), reinterpret_cast(raw.color.ImageUniqueID), sizeof(raw.color.ImageUniqueID))) { metadata.present_fields |= LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID; } } ExifEntry *find_entry(ExifData *data, ExifTag tag) { for (unsigned int index = 0u; index < EXIF_IFD_COUNT; ++index) { ExifEntry *entry = exif_content_get_entry(data->ifd[index], tag); if (entry != nullptr) { return entry; } } return nullptr; } void extract_ascii(ExifData *data, ExifTag tag, char *destination, size_t capacity, uint32_t field, Lardon3DAcquisitionMetadata &metadata) { ExifEntry *entry = find_entry(data, tag); if (entry == nullptr || entry->data == nullptr || entry->size == 0u || entry->format != EXIF_FORMAT_ASCII) { return; } if (copy_padded_text(destination, capacity, entry->data, entry->size)) { metadata.present_fields |= field; } } bool extract_unsigned(ExifData *data, ExifTag tag, uint32_t &value) { ExifEntry *entry = find_entry(data, tag); if (entry == nullptr || entry->data == nullptr) { return false; } const ExifByteOrder order = exif_data_get_byte_order(data); if (entry->format == EXIF_FORMAT_SHORT && entry->size >= 2u) { value = exif_get_short(entry->data, order); return true; } if (entry->format == EXIF_FORMAT_LONG && entry->size >= 4u) { value = exif_get_long(entry->data, order); return true; } return false; } enum class JpegValidationResult { valid, corrupt, io_error, }; constexpr size_t JPEG_MAX_CONTAINER_IMAGES = 8u; // Structural validation deliberately parses JPEG framing without decoding // pixels, so metadata acceptance does not depend on decoder permissiveness. bool read_byte(FILE *file, unsigned char &value) { const int byte = std::fgetc(file); if (byte == EOF) { return false; } value = static_cast(byte); return true; } bool skip_bytes(FILE *file, uint16_t count) { unsigned char byte = 0u; for (uint16_t index = 0u; index < count; ++index) { if (!read_byte(file, byte)) { return false; } } return true; } bool marker_has_length(unsigned char marker) { return (marker >= 0xc0u && marker <= 0xc7u) || (marker >= 0xc9u && marker <= 0xcfu) || (marker >= 0xdau && marker <= 0xdfu) || (marker >= 0xe0u && marker <= 0xfeu); } bool read_marker(FILE *file, unsigned char &marker) { unsigned char byte = 0u; if (!read_byte(file, byte) || byte != 0xffu) { return false; } do { if (!read_byte(file, marker)) { return false; } } while (marker == 0xffu); return marker != 0x00u; } bool skip_marker_payload(FILE *file, unsigned char marker, bool &has_mpf_app2) { unsigned char high = 0u; unsigned char low = 0u; if (!read_byte(file, high) || !read_byte(file, low)) { return false; } const uint16_t length = static_cast((static_cast(high) << 8u) | static_cast(low)); if (marker == 0xdau) { unsigned char component_count = 0u; if (length < 3u || !read_byte(file, component_count) || component_count == 0u || component_count > 4u) { return false; } const uint16_t expected_length = static_cast(6u + 2u * static_cast(component_count)); return length == expected_length && skip_bytes(file, static_cast(length - 3u)); } if (marker == 0xdcu) { unsigned char line_count_high = 0u; unsigned char line_count_low = 0u; return length == 4u && read_byte(file, line_count_high) && read_byte(file, line_count_low) && (line_count_high != 0u || line_count_low != 0u); } if (marker == 0xe2u && length >= 6u) { unsigned char identifier[4] = {}; for (unsigned char &byte : identifier) { if (!read_byte(file, byte)) { return false; } } if (std::memcmp(identifier, "MPF\0", sizeof(identifier)) == 0) { has_mpf_app2 = true; } return skip_bytes(file, static_cast(length - 6u)); } return length >= 2u && skip_bytes(file, static_cast(length - 2u)); } JpegValidationResult validate_jpeg_image(FILE *file, bool soi_already_consumed, bool &has_mpf_app2) { has_mpf_app2 = false; if (!soi_already_consumed) { unsigned char first = 0u; unsigned char second = 0u; if (!read_byte(file, first) || !read_byte(file, second) || first != 0xffu || second != 0xd8u) { return std::ferror(file) != 0 ? JpegValidationResult::io_error : JpegValidationResult::corrupt; } } // Entropy bytes are not marker payload: FF 00 is stuffing and RSTn stays // within the scan. Only a real following marker resumes marker parsing. bool in_entropy = false; for (;;) { unsigned char marker = 0u; bool marker_from_entropy = false; if (in_entropy) { unsigned char byte = 0u; do { if (!read_byte(file, byte)) { return std::ferror(file) != 0 ? JpegValidationResult::io_error : JpegValidationResult::corrupt; } } while (byte != 0xffu); do { if (!read_byte(file, marker)) { return std::ferror(file) != 0 ? JpegValidationResult::io_error : JpegValidationResult::corrupt; } } while (marker == 0xffu); if (marker == 0x00u || (marker >= 0xd0u && marker <= 0xd7u)) { continue; } marker_from_entropy = true; in_entropy = false; } else if (!read_marker(file, marker)) { return std::ferror(file) != 0 ? JpegValidationResult::io_error : JpegValidationResult::corrupt; } if (marker == 0xd9u) { return JpegValidationResult::valid; } if (marker == 0xd8u) { return JpegValidationResult::corrupt; } if (marker == 0x01u || (marker >= 0xd0u && marker <= 0xd7u)) { continue; } if (!marker_has_length(marker) || !skip_marker_payload(file, marker, has_mpf_app2)) { return std::ferror(file) != 0 ? JpegValidationResult::io_error : JpegValidationResult::corrupt; } if (marker == 0xdau || (marker == 0xdcu && marker_from_entropy)) { in_entropy = true; } } } JpegValidationResult validate_jpeg_structure(FILE *file) { // Secondary JPEGs are allowed only in the bounded private container form: // primary APP2 contains MPF\0, every image reaches EOI structurally, and // inter-image/final padding is zero only. No image pixels are decoded. bool primary_has_mpf = false; for (size_t image_index = 0u; image_index < JPEG_MAX_CONTAINER_IMAGES; ++image_index) { bool image_has_mpf = false; const JpegValidationResult image_result = validate_jpeg_image(file, image_index != 0u, image_has_mpf); if (image_result != JpegValidationResult::valid) { return image_result; } if (image_index == 0u) { primary_has_mpf = image_has_mpf; } int byte = EOF; do { byte = std::fgetc(file); } while (byte == 0); if (byte == EOF) { return std::ferror(file) != 0 ? JpegValidationResult::io_error : JpegValidationResult::valid; } if (!primary_has_mpf || byte != 0xff || std::fgetc(file) != 0xd8) { return std::ferror(file) != 0 ? JpegValidationResult::io_error : JpegValidationResult::corrupt; } } return JpegValidationResult::corrupt; } Lardon3DAcquisitionResult extract_jpeg(const char *path, Lardon3DAcquisitionMetadata &metadata) { FILE *file = std::fopen(path, "rb"); if (file == nullptr) { return errno == ENOENT || errno == EACCES ? LARDON3D_ACQUISITION_IO_ERROR : LARDON3D_ACQUISITION_IO_ERROR; } const JpegValidationResult validation = validate_jpeg_structure(file); const bool close_failed = std::fclose(file) != 0; if (validation == JpegValidationResult::io_error || close_failed) { return LARDON3D_ACQUISITION_IO_ERROR; } if (validation != JpegValidationResult::valid) { return LARDON3D_ACQUISITION_CORRUPT_SOURCE; } ExifData *data = exif_data_new_from_file(path); if (data == nullptr) { return LARDON3D_ACQUISITION_METADATA_UNAVAILABLE; } metadata.source_kind = LARDON3D_ACQUISITION_SOURCE_JPEG; extract_ascii(data, EXIF_TAG_MAKE, metadata.make, sizeof(metadata.make), LARDON3D_ACQUISITION_FIELD_MAKE, metadata); extract_ascii(data, EXIF_TAG_MODEL, metadata.model, sizeof(metadata.model), LARDON3D_ACQUISITION_FIELD_MODEL, metadata); extract_ascii(data, EXIF_TAG_BODY_SERIAL_NUMBER, metadata.body_serial, sizeof(metadata.body_serial), LARDON3D_ACQUISITION_FIELD_BODY_SERIAL, metadata); extract_ascii(data, EXIF_TAG_DATE_TIME_ORIGINAL, metadata.datetime_original, sizeof(metadata.datetime_original), LARDON3D_ACQUISITION_FIELD_DATETIME_ORIGINAL, metadata); extract_ascii(data, EXIF_TAG_SUB_SEC_TIME_ORIGINAL, metadata.subsec_original, sizeof(metadata.subsec_original), LARDON3D_ACQUISITION_FIELD_SUBSEC_ORIGINAL, metadata); extract_ascii(data, EXIF_TAG_OFFSET_TIME_ORIGINAL, metadata.offset_original, sizeof(metadata.offset_original), LARDON3D_ACQUISITION_FIELD_OFFSET_ORIGINAL, metadata); extract_ascii(data, EXIF_TAG_IMAGE_UNIQUE_ID, metadata.image_unique_id, sizeof(metadata.image_unique_id), LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID, metadata); uint32_t width = 0u; uint32_t height = 0u; if (extract_unsigned(data, EXIF_TAG_PIXEL_X_DIMENSION, width) && extract_unsigned(data, EXIF_TAG_PIXEL_Y_DIMENSION, height) && width != 0u && height != 0u) { metadata.width = width; metadata.height = height; metadata.present_fields |= LARDON3D_ACQUISITION_FIELD_DIMENSIONS; } uint32_t orientation = 0u; if (extract_unsigned(data, EXIF_TAG_ORIENTATION, orientation) && orientation <= UINT16_MAX) { metadata.orientation = static_cast(orientation); metadata.present_fields |= LARDON3D_ACQUISITION_FIELD_ORIENTATION; } exif_data_unref(data); return LARDON3D_ACQUISITION_OK; } Lardon3DAcquisitionResult extract_raw(const char *path, Lardon3DAcquisitionMetadata &metadata) { libraw_data_t *raw = libraw_init(0u); if (raw == nullptr) { return LARDON3D_ACQUISITION_INTERNAL_ERROR; } const int status = libraw_open_file(raw, path); if (status != LIBRAW_SUCCESS) { libraw_close(raw); if (status == LIBRAW_FILE_UNSUPPORTED || status == LIBRAW_DATA_ERROR || status == LIBRAW_IO_ERROR) { return LARDON3D_ACQUISITION_UNSUPPORTED_FORMAT; } return LARDON3D_ACQUISITION_CORRUPT_SOURCE; } metadata.source_kind = LARDON3D_ACQUISITION_SOURCE_RAW; extract_raw_fixed_text(*raw, metadata); extract_raw_image_unique_id(*raw, metadata); if (raw->other.timestamp > 0) { std::tm time_parts = {}; if (gmtime_r(&raw->other.timestamp, &time_parts) != nullptr && std::strftime(metadata.datetime_original, sizeof(metadata.datetime_original), "%Y:%m:%d %H:%M:%S", &time_parts) != 0u) { metadata.present_fields |= LARDON3D_ACQUISITION_FIELD_DATETIME_ORIGINAL; } } if (raw->sizes.width != 0u && raw->sizes.height != 0u) { metadata.width = raw->sizes.width; metadata.height = raw->sizes.height; metadata.present_fields |= LARDON3D_ACQUISITION_FIELD_DIMENSIONS; } if (raw->sizes.flip >= 0 && raw->sizes.flip <= UINT16_MAX) { metadata.orientation = static_cast(raw->sizes.flip); metadata.present_fields |= LARDON3D_ACQUISITION_FIELD_ORIENTATION; } libraw_close(raw); return LARDON3D_ACQUISITION_OK; } void record_matching_text(const Lardon3DAcquisitionMetadata &left, const Lardon3DAcquisitionMetadata &right, uint32_t field, const char *left_text, size_t left_capacity, const char *right_text, size_t right_capacity, uint32_t evidence, Lardon3DAcquisitionPairResult &result) { if (has_field(left, field) && has_field(right, field) && same_text(left_text, left_capacity, right_text, right_capacity)) { result.evidence |= evidence; } } unsigned evidence_count(uint32_t value) { unsigned count = 0u; while (value != 0u) { count += value & 1u; value >>= 1u; } return count; } } // namespace #ifdef LARDON3D_ACQUISITION_PAIRING_TESTING extern "C" void lardon3d_acquisition_test_extract_raw_image_unique_id( const libraw_data_t *raw, Lardon3DAcquisitionMetadata *metadata) { if (raw != nullptr && metadata != nullptr) { extract_raw_image_unique_id(*raw, *metadata); } } extern "C" void lardon3d_acquisition_test_extract_raw_fixed_text( const libraw_data_t *raw, Lardon3DAcquisitionMetadata *metadata) { if (raw != nullptr && metadata != nullptr) { extract_raw_fixed_text(*raw, *metadata); } } #endif extern "C" Lardon3DAcquisitionResult lardon3d_acquisition_extract_metadata( const char *path, Lardon3DAcquisitionMetadata *metadata) { if (path == nullptr || path[0] == '\0' || metadata == nullptr) { return LARDON3D_ACQUISITION_INVALID_ARGUMENT; } try { *metadata = {}; metadata->policy_version = LARDON3D_ACQUISITION_PAIRING_POLICY_VERSION; FILE *file = std::fopen(path, "rb"); if (file == nullptr) { return LARDON3D_ACQUISITION_IO_ERROR; } unsigned char signature[2] = {}; const size_t bytes_read = std::fread(signature, 1u, sizeof(signature), file); const bool read_error = std::ferror(file) != 0; std::fclose(file); if (read_error) { return LARDON3D_ACQUISITION_IO_ERROR; } if (bytes_read == sizeof(signature) && signature[0] == 0xffu && signature[1] == 0xd8u) { return extract_jpeg(path, *metadata); } return extract_raw(path, *metadata); } catch (const std::bad_alloc &) { return LARDON3D_ACQUISITION_INTERNAL_ERROR; } catch (...) { return LARDON3D_ACQUISITION_INTERNAL_ERROR; } } extern "C" Lardon3DAcquisitionResult lardon3d_acquisition_compare( const Lardon3DAcquisitionMetadata *left, const Lardon3DAcquisitionMetadata *right, int same_asset, int basename_hint, Lardon3DAcquisitionPairResult *result) { if (left == nullptr || right == nullptr || result == nullptr || left->policy_version != LARDON3D_ACQUISITION_PAIRING_POLICY_VERSION || right->policy_version != LARDON3D_ACQUISITION_PAIRING_POLICY_VERSION) { return LARDON3D_ACQUISITION_INVALID_ARGUMENT; } *result = {}; result->decision = LARDON3D_ACQUISITION_INSUFFICIENT; if (basename_hint != 0) { result->evidence |= LARDON3D_ACQUISITION_EVIDENCE_BASENAME_HINT; } record_matching_text(*left, *right, LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID, left->image_unique_id, sizeof(left->image_unique_id), right->image_unique_id, sizeof(right->image_unique_id), LARDON3D_ACQUISITION_EVIDENCE_IMAGE_UNIQUE_ID, *result); record_matching_text(*left, *right, LARDON3D_ACQUISITION_FIELD_BODY_SERIAL, left->body_serial, sizeof(left->body_serial), right->body_serial, sizeof(right->body_serial), LARDON3D_ACQUISITION_EVIDENCE_BODY_SERIAL, *result); record_matching_text(*left, *right, LARDON3D_ACQUISITION_FIELD_MAKE, left->make, sizeof(left->make), right->make, sizeof(right->make), LARDON3D_ACQUISITION_EVIDENCE_MAKE, *result); record_matching_text(*left, *right, LARDON3D_ACQUISITION_FIELD_MODEL, left->model, sizeof(left->model), right->model, sizeof(right->model), LARDON3D_ACQUISITION_EVIDENCE_MODEL, *result); record_matching_text(*left, *right, LARDON3D_ACQUISITION_FIELD_DATETIME_ORIGINAL, left->datetime_original, sizeof(left->datetime_original), right->datetime_original, sizeof(right->datetime_original), LARDON3D_ACQUISITION_EVIDENCE_DATETIME_SECOND, *result); if (has_field(*left, LARDON3D_ACQUISITION_FIELD_DIMENSIONS) && has_field(*right, LARDON3D_ACQUISITION_FIELD_DIMENSIONS) && left->width == right->width && left->height == right->height) { result->evidence |= LARDON3D_ACQUISITION_EVIDENCE_DIMENSIONS; } if (has_field(*left, LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID) && has_field(*right, LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID) && different_text(left->image_unique_id, sizeof(left->image_unique_id), right->image_unique_id, sizeof(right->image_unique_id))) { result->contradictions |= LARDON3D_ACQUISITION_CONTRADICTION_IMAGE_UNIQUE_ID; } if (has_field(*left, LARDON3D_ACQUISITION_FIELD_BODY_SERIAL) && has_field(*right, LARDON3D_ACQUISITION_FIELD_BODY_SERIAL) && different_text(left->body_serial, sizeof(left->body_serial), right->body_serial, sizeof(right->body_serial))) { result->contradictions |= LARDON3D_ACQUISITION_CONTRADICTION_BODY_SERIAL; } if (same_asset != 0) { result->strength = 0u; return LARDON3D_ACQUISITION_OK; } if (result->contradictions != 0u) { result->decision = LARDON3D_ACQUISITION_DIFFERENT; return LARDON3D_ACQUISITION_OK; } if ((result->evidence & LARDON3D_ACQUISITION_EVIDENCE_IMAGE_UNIQUE_ID) != 0u) { result->decision = LARDON3D_ACQUISITION_SAME_ACQUISITION_STRONG; result->strength = 100u; return LARDON3D_ACQUISITION_OK; } const uint32_t metadata_evidence = result->evidence & ~LARDON3D_ACQUISITION_EVIDENCE_BASENAME_HINT; if (metadata_evidence != 0u) { result->decision = LARDON3D_ACQUISITION_SAME_ACQUISITION_CANDIDATE; result->strength = evidence_count(metadata_evidence) + ((result->evidence & LARDON3D_ACQUISITION_EVIDENCE_BASENAME_HINT) != 0u ? 1u : 0u); } return LARDON3D_ACQUISITION_OK; } extern "C" Lardon3DAcquisitionResult lardon3d_acquisition_select_candidate( const Lardon3DAcquisitionMetadata *source, const Lardon3DAcquisitionCandidate *candidates, size_t candidate_count, Lardon3DAcquisitionSelection *selection) { if (source == nullptr || selection == nullptr || (candidate_count != 0u && candidates == nullptr)) { return LARDON3D_ACQUISITION_INVALID_ARGUMENT; } if (candidate_count > LARDON3D_ACQUISITION_MAX_CANDIDATES) { return LARDON3D_ACQUISITION_LIMIT_EXCEEDED; } *selection = {}; selection->decision = LARDON3D_ACQUISITION_INSUFFICIENT; uint32_t top_strength = 0u; for (size_t index = 0u; index < candidate_count; ++index) { if (candidates[index].metadata == nullptr) { return LARDON3D_ACQUISITION_INVALID_ARGUMENT; } Lardon3DAcquisitionPairResult pair = {}; const Lardon3DAcquisitionResult status = lardon3d_acquisition_compare( source, candidates[index].metadata, candidates[index].same_asset, candidates[index].basename_hint, &pair); if (status != LARDON3D_ACQUISITION_OK) { return status; } if (pair.decision != LARDON3D_ACQUISITION_SAME_ACQUISITION_STRONG && pair.decision != LARDON3D_ACQUISITION_SAME_ACQUISITION_CANDIDATE) { continue; } if (pair.strength > top_strength) { top_strength = pair.strength; selection->selected_candidate_id = candidates[index].candidate_id; selection->selected_pair = pair; selection->top_candidate_count = 1u; } else if (pair.strength == top_strength) { ++selection->top_candidate_count; } } if (selection->top_candidate_count == 0u) { return LARDON3D_ACQUISITION_OK; } if (selection->top_candidate_count > 1u) { selection->decision = LARDON3D_ACQUISITION_AMBIGUOUS; selection->selected_candidate_id = 0u; return LARDON3D_ACQUISITION_OK; } selection->decision = selection->selected_pair.decision; return LARDON3D_ACQUISITION_OK; }