lardon3d/src/calibration_bootstrap_v2.c

350 lines
16 KiB
C

#include <lardon3d/calibration_bootstrap_v2.h>
#include <math.h>
#include <openssl/evp.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
enum {
BOOTSTRAP_V2_HEADER_SIZE = 28,
BOOTSTRAP_V2_GROUP_SIZE = 232,
BOOTSTRAP_V2_ENTRY_SIZE = 144,
BOOTSTRAP_V2_VALIDATION_FLAGS = 15,
};
static const unsigned char bootstrap_v2_magic[8] = {'L', '3', 'D', 'C', 'A', 'L', 'B', '2'};
typedef struct {
const unsigned char *bytes;
size_t remaining;
} BootstrapV2Reader;
typedef struct {
uint32_t selected_item_index;
unsigned char representation_sha256[32];
Lardon3DSparseCalibration calibration;
Lardon3DSparseCalibrationMember member;
} BootstrapV2Entry;
static bool take(BootstrapV2Reader *reader, void *output, size_t count) {
if (count > reader->remaining) return false;
if (output) memcpy(output, reader->bytes, count);
reader->bytes += count;
reader->remaining -= count;
return true;
}
static bool read_u32(BootstrapV2Reader *reader, uint32_t *output) {
unsigned char bytes[4];
if (!take(reader, bytes, sizeof(bytes))) return false;
*output = (uint32_t)bytes[0] | ((uint32_t)bytes[1] << 8) |
((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24);
return true;
}
static bool read_u64(BootstrapV2Reader *reader, uint64_t *output) {
unsigned char bytes[8];
if (!take(reader, bytes, sizeof(bytes))) return false;
*output = 0;
for (size_t index = 0; index < sizeof(bytes); ++index)
*output |= (uint64_t)bytes[index] << (8u * index);
return true;
}
static bool read_f64(BootstrapV2Reader *reader, double *output) {
uint64_t bits = 0;
if (!read_u64(reader, &bits)) return false;
memcpy(output, &bits, sizeof(bits));
if (!isfinite(*output)) return false;
if (*output == 0.0) *output = 0.0;
return true;
}
static bool sha256(const unsigned char *bytes, size_t size, unsigned char output[32]) {
unsigned int output_size = 0;
return EVP_Digest(bytes, size, output, &output_size, EVP_sha256(), NULL) == 1 &&
output_size == 32;
}
static bool nonzero_digest(const unsigned char value[32]) {
unsigned char any = 0;
for (size_t index = 0; index < 32; ++index) any |= value[index];
return any != 0;
}
static Lardon3DCalibrationBootstrapV2Result db_result(Lardon3DProjectDbResult result) {
if (result == LARDON3D_PROJECT_DB_CONSTRAINT || result == LARDON3D_PROJECT_DB_NOT_FOUND)
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT;
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_DB_ERROR;
}
static Lardon3DCalibrationBootstrapV2Result bootstrap_v2_publish(
Lardon3DProjectDb *database, uint64_t execution_id,
const unsigned char *artifact, size_t artifact_size,
const unsigned char expected_artifact_sha256[32],
Lardon3DCalibrationBootstrapV2Member *published_members,
size_t member_capacity, bool attach_scope,
Lardon3DCalibrationBootstrapV2Output *output) {
if (!database || execution_id == 0 || !artifact || !expected_artifact_sha256 || !output ||
artifact_size < BOOTSTRAP_V2_HEADER_SIZE ||
artifact_size > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_BYTES)
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_INVALID_ARGUMENT;
memset(output, 0, sizeof(*output));
unsigned char artifact_sha256[32];
if (!sha256(artifact, artifact_size, artifact_sha256))
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_DB_ERROR;
if (memcmp(artifact_sha256, expected_artifact_sha256, 32) != 0)
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_PROVENANCE_MISMATCH;
BootstrapV2Reader reader = {artifact, artifact_size};
unsigned char magic[8];
uint32_t version, model_kind, model_version, group_count, entry_count;
if (!take(&reader, magic, sizeof(magic)) || !read_u32(&reader, &version) ||
!read_u32(&reader, &model_kind) || !read_u32(&reader, &model_version) ||
!read_u32(&reader, &group_count) || !read_u32(&reader, &entry_count) ||
memcmp(magic, bootstrap_v2_magic, sizeof(magic)) != 0 ||
version != LARDON3D_CALIBRATION_BOOTSTRAP_V2_ARTIFACT_VERSION ||
model_kind != LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE ||
model_version != LARDON3D_SPARSE_SFM_CALIBRATION_VERSION || group_count == 0 ||
group_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_GROUPS || entry_count == 0 ||
entry_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_ENTRIES)
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
if (!attach_scope && (!published_members || member_capacity < entry_count))
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_INVALID_ARGUMENT;
size_t expected_size = BOOTSTRAP_V2_HEADER_SIZE + (size_t)group_count * BOOTSTRAP_V2_GROUP_SIZE;
if (entry_count > (SIZE_MAX - expected_size) / BOOTSTRAP_V2_ENTRY_SIZE)
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
expected_size += (size_t)entry_count * BOOTSTRAP_V2_ENTRY_SIZE;
if (artifact_size != expected_size)
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
BootstrapV2Entry *entries = calloc(entry_count, sizeof(*entries));
bool *covered = calloc(entry_count, sizeof(*covered));
if (!entries || !covered) {
free(entries);
free(covered);
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_OUT_OF_MEMORY;
}
Lardon3DCalibrationBootstrapV2Result result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK;
size_t parsed_entries = 0;
unsigned char previous_group_identity[32] = {0};
uint32_t previous_group_version = 0;
for (uint32_t group_index = 0;
group_index < group_count && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK;
++group_index) {
const unsigned char *group_start = reader.bytes;
unsigned char group_identity[32], evidence[6][32];
uint32_t group_version, member_count;
if (!take(&reader, group_identity, 32) || !read_u32(&reader, &group_version)) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
break;
}
for (size_t index = 0; index < 6; ++index)
if (!take(&reader, evidence[index], 32)) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
break;
}
if (result != LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK ||
!read_u32(&reader, &member_count)) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
break;
}
int group_order = group_index == 0 ? -1 :
memcmp(previous_group_identity, group_identity, 32);
if (!nonzero_digest(group_identity) || group_version == 0 || member_count == 0 ||
member_count > entry_count - parsed_entries || group_order > 0 ||
(group_order == 0 && previous_group_version >= group_version)) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
break;
}
for (size_t index = 0; index < 6; ++index)
if (!nonzero_digest(evidence[index])) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
break;
}
memcpy(previous_group_identity, group_identity, 32);
previous_group_version = group_version;
size_t group_first_entry = parsed_entries;
uint32_t previous_item_index = 0;
for (uint32_t member_index = 0;
member_index < member_count && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK;
++member_index) {
BootstrapV2Entry *entry = &entries[parsed_entries];
uint32_t width, height, support_images, support_observations, validation_flags;
double reprojection_rmse_px, maximum_parameter_delta;
if (!read_u32(&reader, &entry->selected_item_index) ||
!read_u64(&reader, &entry->member.image_id) ||
!take(&reader, entry->representation_sha256, 32) || !read_u32(&reader, &width) ||
!read_u32(&reader, &height) || !read_f64(&reader, &entry->calibration.fx) ||
!read_f64(&reader, &entry->calibration.fy) ||
!read_f64(&reader, &entry->calibration.cx) ||
!read_f64(&reader, &entry->calibration.cy) ||
!read_f64(&reader, &entry->calibration.k1) ||
!read_f64(&reader, &entry->calibration.k2) ||
!read_f64(&reader, &entry->calibration.p1) ||
!read_f64(&reader, &entry->calibration.p2) ||
!read_u32(&reader, &support_images) ||
!read_u32(&reader, &support_observations) ||
!read_f64(&reader, &reprojection_rmse_px) ||
!read_f64(&reader, &maximum_parameter_delta) ||
!read_u32(&reader, &validation_flags) ||
entry->selected_item_index >= entry_count || covered[entry->selected_item_index] ||
(member_index > 0 && previous_item_index >= entry->selected_item_index) ||
entry->member.image_id == 0 || !nonzero_digest(entry->representation_sha256) ||
width == 0 || height == 0 || entry->calibration.fx <= 0.0 ||
entry->calibration.fy <= 0.0 || entry->calibration.cx < 0.0 ||
entry->calibration.cy < 0.0 || entry->calibration.cx >= (double)width ||
entry->calibration.cy >= (double)height || support_images == 0 ||
support_observations == 0 || reprojection_rmse_px < 0.0 ||
maximum_parameter_delta < 0.0 || validation_flags != BOOTSTRAP_V2_VALIDATION_FLAGS) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
break;
}
for (size_t prior = 0; prior < parsed_entries; ++prior)
if (entries[prior].member.image_id == entry->member.image_id) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
break;
}
covered[entry->selected_item_index] = true;
previous_item_index = entry->selected_item_index;
entry->calibration.model_kind = model_kind;
entry->calibration.model_version = model_version;
entry->calibration.width = width;
entry->calibration.height = height;
entry->calibration.provenance_kind =
LARDON3D_SPARSE_SFM_PROVENANCE_IMPORTED_TRUSTED;
++parsed_entries;
}
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
unsigned char group_fingerprint[32];
size_t group_size = (size_t)(reader.bytes - group_start);
if (!sha256(group_start, group_size, group_fingerprint)) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_DB_ERROR;
break;
}
/* Group-local hashing preserves independently acquired provenance: an
* unrelated group's addition cannot redefine these calibrations. */
for (size_t index = group_first_entry; index < parsed_entries; ++index)
memcpy(entries[index].calibration.provenance_fingerprint, group_fingerprint, 32);
}
}
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK &&
(parsed_entries != entry_count || reader.remaining != 0))
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
for (uint32_t index = 0;
index < entry_count && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK; ++index)
if (!covered[index]) result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
Lardon3DProjectDbSelectedExecution execution;
Lardon3DProjectDbResult db_status = LARDON3D_PROJECT_DB_OK;
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
db_status = lardon3d_project_db_load_selected_execution(database, execution_id, &execution);
if (db_status != LARDON3D_PROJECT_DB_OK)
result = db_result(db_status);
else if (execution.item_count != entry_count ||
(execution.stage != LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
execution.stage != LARDON3D_SELECTED_EXECUTION_READY))
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT;
}
for (size_t index = 0;
index < parsed_entries && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK; ++index) {
BootstrapV2Entry *entry = &entries[index];
Lardon3DProjectDbSelectedExecutionItem selected;
Lardon3DProjectDbImage image;
Lardon3DProjectDbImageAsset asset;
db_status = lardon3d_project_db_load_selected_execution_item(
database, execution_id, entry->selected_item_index, &selected);
if (db_status != LARDON3D_PROJECT_DB_OK || !selected.has_image ||
selected.image_id != entry->member.image_id) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT;
break;
}
db_status = lardon3d_project_db_load_image(database, entry->member.image_id, &image, &asset);
if (db_status != LARDON3D_PROJECT_DB_OK ||
memcmp(asset.sha256, entry->representation_sha256, 32) != 0) {
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT;
break;
}
}
/* All bytes and immutable selected bindings are verified before this first
* write. Publication uses the existing short, content-addressed DB APIs; a
* crash may retain reusable calibrations but READY remains guarded by the
* single complete-scope attachment below. */
for (size_t index = 0;
index < parsed_entries && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK; ++index) {
Lardon3DSparseCalibration stored;
db_status = lardon3d_sparse_calibration_create(database, &entries[index].calibration,
&stored);
if (db_status != LARDON3D_PROJECT_DB_OK) {
result = db_result(db_status);
break;
}
entries[index].member.calibration_id = stored.calibration_id;
memcpy(entries[index].member.calibration_hash, stored.scientific_hash, 32);
}
Lardon3DSparseCalibrationScope scope;
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
Lardon3DSparseCalibrationMember *members = calloc(entry_count, sizeof(*members));
if (!members)
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_OUT_OF_MEMORY;
else {
for (size_t index = 0; index < entry_count; ++index) members[index] = entries[index].member;
db_status = lardon3d_sparse_calibration_scope_create(database, members, entry_count, &scope);
free(members);
if (db_status != LARDON3D_PROJECT_DB_OK) result = db_result(db_status);
}
}
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK && attach_scope) {
db_status = lardon3d_project_db_assign_selected_calibration_scope(database, execution_id,
scope.scope_id);
if (db_status != LARDON3D_PROJECT_DB_OK) result = db_result(db_status);
}
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
memcpy(output->artifact_sha256, artifact_sha256, 32);
output->scope = scope;
output->calibration_count = entry_count;
output->group_count = group_count;
if (!attach_scope) {
/* Item order, not artifact group order, is the durable composition key.
* Workflow must bind it back to selected_execution_items.capture_id and
* must never reverse-map Capture identity from image_id. */
for (size_t index = 0; index < entry_count; ++index) {
BootstrapV2Entry *entry = &entries[index];
Lardon3DCalibrationBootstrapV2Member *member =
&published_members[entry->selected_item_index];
member->selected_item_index = entry->selected_item_index;
member->image_id = entry->member.image_id;
member->calibration_id = entry->member.calibration_id;
}
}
}
free(entries);
free(covered);
return result;
}
Lardon3DCalibrationBootstrapV2Result
lardon3d_calibration_bootstrap_v2_publish_unattached(
Lardon3DProjectDb *database, uint64_t execution_id,
const unsigned char *artifact, size_t artifact_size,
const unsigned char expected_artifact_sha256[32],
Lardon3DCalibrationBootstrapV2Member *members, size_t member_capacity,
Lardon3DCalibrationBootstrapV2Output *output) {
return bootstrap_v2_publish(database, execution_id, artifact, artifact_size,
expected_artifact_sha256, members,
member_capacity, false, output);
}
Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
Lardon3DProjectDb *database, uint64_t execution_id,
const unsigned char *artifact, size_t artifact_size,
const unsigned char expected_artifact_sha256[32],
Lardon3DCalibrationBootstrapV2Output *output) {
return bootstrap_v2_publish(database, execution_id, artifact, artifact_size,
expected_artifact_sha256, NULL, 0, true, output);
}