lardon3d/tests/test_acquisition_pairing.cpp

290 lines
13 KiB
C++

#include <lardon3d/acquisition_pairing.h>
#include <libexif/exif-data.h>
#include <libraw/libraw.h>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <string>
#include <unistd.h>
namespace {
extern "C" void lardon3d_acquisition_test_extract_raw_image_unique_id(
const libraw_data_t *raw, Lardon3DAcquisitionMetadata *metadata);
extern "C" void lardon3d_acquisition_test_extract_raw_fixed_text(
const libraw_data_t *raw, Lardon3DAcquisitionMetadata *metadata);
Lardon3DAcquisitionMetadata metadata() {
Lardon3DAcquisitionMetadata value = {};
value.policy_version = LARDON3D_ACQUISITION_PAIRING_POLICY_VERSION;
value.source_kind = LARDON3D_ACQUISITION_SOURCE_RAW;
return value;
}
void set_text(Lardon3DAcquisitionMetadata &value, uint32_t field, char *destination,
size_t capacity, const char *text) {
assert(std::strlen(text) < capacity);
std::strcpy(destination, text);
value.present_fields |= field;
}
Lardon3DAcquisitionPairResult compare(const Lardon3DAcquisitionMetadata &left,
const Lardon3DAcquisitionMetadata &right,
bool same_asset = false, bool basename_hint = false) {
Lardon3DAcquisitionPairResult result = {};
assert(lardon3d_acquisition_compare(&left, &right, same_asset, basename_hint, &result) ==
LARDON3D_ACQUISITION_OK);
return result;
}
void test_pairing_policy() {
auto left = metadata();
auto right = metadata();
set_text(left, LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID, left.image_unique_id,
sizeof(left.image_unique_id), "UID-1");
set_text(right, LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID, right.image_unique_id,
sizeof(right.image_unique_id), "UID-1");
assert(compare(left, right).decision == LARDON3D_ACQUISITION_SAME_ACQUISITION_STRONG);
auto result = compare(left, right, false, true);
assert(result.decision == LARDON3D_ACQUISITION_SAME_ACQUISITION_STRONG);
assert((result.evidence & LARDON3D_ACQUISITION_EVIDENCE_BASENAME_HINT) != 0u);
auto empty_left = metadata();
auto empty_right = metadata();
result = compare(empty_left, empty_right, false, true); /* DSC03350.ARW / DSC03350.JPG */
assert(result.decision == LARDON3D_ACQUISITION_INSUFFICIENT);
assert(result.evidence == LARDON3D_ACQUISITION_EVIDENCE_BASENAME_HINT);
set_text(empty_left, LARDON3D_ACQUISITION_FIELD_DATETIME_ORIGINAL,
empty_left.datetime_original, sizeof(empty_left.datetime_original),
"2026:08:27 12:34:56");
set_text(empty_right, LARDON3D_ACQUISITION_FIELD_DATETIME_ORIGINAL,
empty_right.datetime_original, sizeof(empty_right.datetime_original),
"2026:08:27 12:34:56");
assert(compare(empty_left, empty_right).decision ==
LARDON3D_ACQUISITION_SAME_ACQUISITION_CANDIDATE);
set_text(empty_left, LARDON3D_ACQUISITION_FIELD_MODEL, empty_left.model,
sizeof(empty_left.model), "ILCE-7M4");
set_text(empty_right, LARDON3D_ACQUISITION_FIELD_MODEL, empty_right.model,
sizeof(empty_right.model), "ILCE-7M4");
assert(compare(empty_left, empty_right).decision ==
LARDON3D_ACQUISITION_SAME_ACQUISITION_CANDIDATE);
std::strcpy(right.image_unique_id, "UID-2");
result = compare(left, right);
assert(result.decision == LARDON3D_ACQUISITION_DIFFERENT);
assert(result.contradictions ==
LARDON3D_ACQUISITION_CONTRADICTION_IMAGE_UNIQUE_ID);
auto serial_left = metadata();
auto serial_right = metadata();
set_text(serial_left, LARDON3D_ACQUISITION_FIELD_BODY_SERIAL, serial_left.body_serial,
sizeof(serial_left.body_serial), "BODY-A");
set_text(serial_right, LARDON3D_ACQUISITION_FIELD_BODY_SERIAL, serial_right.body_serial,
sizeof(serial_right.body_serial), "BODY-B");
assert(compare(serial_left, serial_right).decision == LARDON3D_ACQUISITION_DIFFERENT);
serial_right = metadata();
assert(compare(serial_left, serial_right).decision == LARDON3D_ACQUISITION_INSUFFICIENT);
auto other_device = metadata();
set_text(other_device, LARDON3D_ACQUISITION_FIELD_BODY_SERIAL, other_device.body_serial,
sizeof(other_device.body_serial), "BODY-B");
set_text(other_device, LARDON3D_ACQUISITION_FIELD_DATETIME_ORIGINAL,
other_device.datetime_original, sizeof(other_device.datetime_original),
"2026:08:27 12:34:56");
set_text(serial_left, LARDON3D_ACQUISITION_FIELD_DATETIME_ORIGINAL,
serial_left.datetime_original, sizeof(serial_left.datetime_original),
"2026:08:27 12:34:56");
assert(compare(serial_left, other_device, false, true).decision ==
LARDON3D_ACQUISITION_DIFFERENT);
assert(compare(left, left, true, true).decision == LARDON3D_ACQUISITION_INSUFFICIENT);
}
void test_selector() {
auto source = metadata();
auto candidate_b = metadata();
auto candidate_c = metadata();
set_text(source, LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID, source.image_unique_id,
sizeof(source.image_unique_id), "SHARED");
set_text(candidate_b, LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID,
candidate_b.image_unique_id, sizeof(candidate_b.image_unique_id), "SHARED");
set_text(candidate_c, LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID,
candidate_c.image_unique_id, sizeof(candidate_c.image_unique_id), "SHARED");
Lardon3DAcquisitionCandidate candidates[2] = {
{20u, &candidate_b, 0u, 0u, {}},
{30u, &candidate_c, 0u, 0u, {}},
};
Lardon3DAcquisitionSelection selection = {};
assert(lardon3d_acquisition_select_candidate(&source, candidates, 2u, &selection) ==
LARDON3D_ACQUISITION_OK);
assert(selection.decision == LARDON3D_ACQUISITION_AMBIGUOUS);
assert(selection.top_candidate_count == 2u);
assert(selection.selected_candidate_id == 0u);
const auto temporary = candidates[0];
candidates[0] = candidates[1];
candidates[1] = temporary;
assert(lardon3d_acquisition_select_candidate(&source, candidates, 2u, &selection) ==
LARDON3D_ACQUISITION_OK);
assert(selection.decision == LARDON3D_ACQUISITION_AMBIGUOUS);
assert(selection.top_candidate_count == 2u);
assert(selection.selected_candidate_id == 0u);
}
void test_non_terminated_public_text_is_unavailable() {
auto left = metadata();
auto right = metadata();
std::memset(left.image_unique_id, 'X', sizeof(left.image_unique_id));
std::memset(right.image_unique_id, 'X', sizeof(right.image_unique_id));
left.present_fields |= LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID;
right.present_fields |= LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID;
auto result = compare(left, right);
assert(result.decision == LARDON3D_ACQUISITION_INSUFFICIENT);
assert(result.evidence == 0u);
assert(result.contradictions == 0u);
std::memcpy(right.image_unique_id, "VALID", 6u);
result = compare(left, right);
assert(result.decision == LARDON3D_ACQUISITION_INSUFFICIENT);
assert(result.evidence == 0u);
assert(result.contradictions == 0u);
}
void add_ascii(ExifData *data, ExifIfd ifd, ExifTag tag, const char *value) {
ExifEntry *entry = exif_entry_new();
assert(entry != nullptr);
entry->tag = tag;
entry->format = EXIF_FORMAT_ASCII;
const size_t value_size = std::strlen(value) + 2u;
assert(value_size <= UINT32_MAX);
entry->components = static_cast<unsigned long>(value_size);
entry->size = static_cast<unsigned int>(value_size);
entry->data = static_cast<unsigned char *>(std::malloc(entry->size));
assert(entry->data != nullptr);
std::memcpy(entry->data, value, std::strlen(value));
entry->data[entry->size - 2u] = ' ';
entry->data[entry->size - 1u] = '\0';
exif_content_add_entry(data->ifd[ifd], entry);
exif_entry_unref(entry);
}
std::string temporary_path(const char *suffix) {
char path[] = "/tmp/lardon3d-acquisition-XXXXXX";
const int descriptor = mkstemp(path);
assert(descriptor >= 0);
close(descriptor);
std::string result(path);
result += suffix;
assert(std::rename(path, result.c_str()) == 0);
return result;
}
void write_exif_jpeg(const std::string &path) {
ExifData *data = exif_data_new();
assert(data != nullptr);
exif_data_set_byte_order(data, EXIF_BYTE_ORDER_INTEL);
add_ascii(data, EXIF_IFD_0, EXIF_TAG_MAKE, "Sony");
add_ascii(data, EXIF_IFD_0, EXIF_TAG_MODEL, "ILCE-7M4");
add_ascii(data, EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_ORIGINAL, "2026:08:27 12:34:56");
add_ascii(data, EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_ORIGINAL, "123");
add_ascii(data, EXIF_IFD_EXIF, EXIF_TAG_OFFSET_TIME_ORIGINAL, "+02:00");
add_ascii(data, EXIF_IFD_EXIF, EXIF_TAG_BODY_SERIAL_NUMBER, "BODY-42");
add_ascii(data, EXIF_IFD_EXIF, EXIF_TAG_IMAGE_UNIQUE_ID, "IMAGE-99");
unsigned char *encoded = nullptr;
unsigned int encoded_size = 0u;
exif_data_save_data(data, &encoded, &encoded_size);
assert(encoded != nullptr && encoded_size > 0u && encoded_size <= UINT16_MAX - 2u);
FILE *file = std::fopen(path.c_str(), "wb");
assert(file != nullptr);
const unsigned char soi[] = {0xffu, 0xd8u, 0xffu, 0xe1u};
const unsigned int segment_size = encoded_size + 2u;
const unsigned char length[] = {static_cast<unsigned char>(segment_size >> 8u),
static_cast<unsigned char>(segment_size & 0xffu)};
const unsigned char eoi[] = {0xffu, 0xd9u};
assert(std::fwrite(soi, 1u, sizeof(soi), file) == sizeof(soi));
assert(std::fwrite(length, 1u, sizeof(length), file) == sizeof(length));
assert(std::fwrite(encoded, 1u, encoded_size, file) == encoded_size);
assert(std::fwrite(eoi, 1u, sizeof(eoi), file) == sizeof(eoi));
assert(std::fclose(file) == 0);
std::free(encoded);
exif_data_unref(data);
}
void test_extraction() {
const std::string jpeg = temporary_path(".jpg");
write_exif_jpeg(jpeg);
Lardon3DAcquisitionMetadata value = {};
assert(lardon3d_acquisition_extract_metadata(jpeg.c_str(), &value) ==
LARDON3D_ACQUISITION_OK);
assert(value.source_kind == LARDON3D_ACQUISITION_SOURCE_JPEG);
assert(std::strcmp(value.make, "Sony") == 0);
assert(std::strcmp(value.model, "ILCE-7M4") == 0);
assert(std::strcmp(value.datetime_original, "2026:08:27 12:34:56") == 0);
assert(std::strcmp(value.subsec_original, "123") == 0);
assert(std::strcmp(value.offset_original, "+02:00") == 0);
assert(std::strcmp(value.body_serial, "BODY-42") == 0);
assert(std::strcmp(value.image_unique_id, "IMAGE-99") == 0);
assert(unlink(jpeg.c_str()) == 0);
const std::string malformed = temporary_path(".jpg");
FILE *file = std::fopen(malformed.c_str(), "wb");
assert(file != nullptr);
const unsigned char bad[] = {0xffu, 0xd8u, 0x00u};
assert(std::fwrite(bad, 1u, sizeof(bad), file) == sizeof(bad));
assert(std::fclose(file) == 0);
assert(lardon3d_acquisition_extract_metadata(malformed.c_str(), &value) ==
LARDON3D_ACQUISITION_CORRUPT_SOURCE);
assert(unlink(malformed.c_str()) == 0);
const std::string unsupported = temporary_path(".bin");
file = std::fopen(unsupported.c_str(), "wb");
assert(file != nullptr);
assert(std::fwrite("not an image", 1u, 12u, file) == 12u);
assert(std::fclose(file) == 0);
assert(lardon3d_acquisition_extract_metadata(unsupported.c_str(), &value) ==
LARDON3D_ACQUISITION_UNSUPPORTED_FORMAT);
assert(unlink(unsupported.c_str()) == 0);
}
void test_raw_image_unique_id_mapping() {
libraw_data_t raw = {};
Lardon3DAcquisitionMetadata value = {};
std::memcpy(raw.color.ImageUniqueID, "RAW-UID-42 ", 11u);
lardon3d_acquisition_test_extract_raw_image_unique_id(&raw, &value);
assert(std::strcmp(value.image_unique_id, "RAW-UID-42") == 0);
assert((value.present_fields & LARDON3D_ACQUISITION_FIELD_IMAGE_UNIQUE_ID) != 0u);
}
void test_non_terminated_raw_fixed_text_mapping() {
libraw_data_t raw = {};
Lardon3DAcquisitionMetadata value = {};
std::memset(raw.idata.make, 'M', sizeof(raw.idata.make));
std::memset(raw.idata.model, 'D', sizeof(raw.idata.model));
std::memset(raw.shootinginfo.BodySerial, 'S', sizeof(raw.shootinginfo.BodySerial));
lardon3d_acquisition_test_extract_raw_fixed_text(&raw, &value);
assert(std::strlen(value.make) == sizeof(raw.idata.make));
assert(std::strlen(value.model) == sizeof(raw.idata.model));
assert(std::strlen(value.body_serial) == sizeof(raw.shootinginfo.BodySerial));
assert((value.present_fields & LARDON3D_ACQUISITION_FIELD_MAKE) != 0u);
assert((value.present_fields & LARDON3D_ACQUISITION_FIELD_MODEL) != 0u);
assert((value.present_fields & LARDON3D_ACQUISITION_FIELD_BODY_SERIAL) != 0u);
}
} // namespace
int main() {
test_pairing_policy();
test_selector();
test_non_terminated_public_text_is_unavailable();
test_extraction();
test_raw_image_unique_id_mapping();
test_non_terminated_raw_fixed_text_mapping();
return 0;
}