#include #include #include #include #include #include #include #include #include #include #include #include #include #include "track_builder_internal.hpp" extern "C" { #include #include #include #include #include #include } namespace { constexpr std::array kScopeMagic = { 'L', '3', 'D', 'T', 'S', 'C', 'P', '1'}; constexpr uint32_t kScopeVersion = 1; constexpr uint64_t kFixedMemory = 4ULL * 1024ULL * 1024ULL; constexpr uint64_t kMaximumScopeBytes = 64ULL * 1024ULL * 1024ULL; struct Context { std::string project_path; Lardon3DProjectDb *database = nullptr; Lardon3DResourceGovernor *governor = nullptr; Lardon3DProjectDbTrackBuilderTask durable{}; std::vector ids; }; void destroy_context(void *userdata) { delete static_cast(userdata); } bool checked_add(uint64_t left, uint64_t right, uint64_t *result) { if (right > std::numeric_limits::max() - left) return false; *result = left + right; return true; } bool checked_mul(uint64_t left, uint64_t right, uint64_t *result) { if (left != 0 && right > std::numeric_limits::max() / left) return false; *result = left * right; return true; } bool compact_memory_estimate(uint64_t raw_edges, uint64_t feature_sets, uint64_t max_match_count, uint64_t *memory) { if (!memory || raw_edges > UINT32_MAX / 2U) return false; const uint64_t nodes = raw_edges * 2U; uint64_t required_slots = 0; if (!checked_add(nodes, nodes / 2U, &required_slots) || !checked_add(required_slots, 1U, &required_slots)) return false; uint64_t slots = required_slots == 0 ? 0 : 8; while (slots < required_slots) { if (!checked_mul(slots, 2U, &slots)) return false; } uint64_t graph_and_peak_node_bytes = 0, edge_bytes = 0; uint64_t identity_bytes = 0, feature_bytes = 0, match_file_peak_bytes = 0; /* WHY/ACCOUNTING: 133 B/node is the simultaneous retained node (16), * DSU/group/conflict scratch (17), flat canonical output (24), worst * per-track publication serialization (44: stored observation capacity 16, * per-track vector capacity/objects up to 12, and publish rows 16), plus * two 16-byte sorted transient publication-validation keys. Identity slots * and raw indexed edges are charged by their actual reserved capacities. 640 B/Feature Set * bounds the metadata projection plus the adapter cache/hash allocation. * WHY/CONTRACT: resolve_gvr materializes exactly one Match File at a time; * charge its largest entry vector as a peak, not the sum across the scope. */ if (!checked_mul(nodes, 133U, &graph_and_peak_node_bytes) || !checked_mul(raw_edges, sizeof(lardon3d::track_builder_internal::Edge), &edge_bytes) || !checked_mul(slots, sizeof(lardon3d::track_builder_internal::IdentitySlot), &identity_bytes) || !checked_mul(feature_sets, 640U, &feature_bytes) || !checked_mul(max_match_count, sizeof(Lardon3DMatchFileEntry), &match_file_peak_bytes)) return false; uint64_t total = kFixedMemory; if (!checked_add(total, graph_and_peak_node_bytes, &total) || !checked_add(total, edge_bytes, &total) || !checked_add(total, identity_bytes, &total) || !checked_add(total, feature_bytes, &total) || !checked_add(total, match_file_peak_bytes, &total)) return false; *memory = total; return true; } bool hash_bytes(const unsigned char *data, size_t size, unsigned char output[32]) { unsigned int length = 0; return EVP_Digest(data, size, output, &length, EVP_sha256(), nullptr) == 1 && length == 32; } bool scope_hash(const std::vector &ids, unsigned char output[32]) { if (ids.size() > (std::numeric_limits::max() - 8U) / 8U) return false; std::vector bytes(8U + ids.size() * 8U); std::memcpy(bytes.data(), "L3DTSIS1", 8); for (size_t i = 0; i < ids.size(); ++i) { uint64_t value = ids[i]; for (size_t byte = 0; byte < 8; ++byte) { bytes[8U + i * 8U + byte] = static_cast(value & 0xffU); value >>= 8U; } } return hash_bytes(bytes.data(), bytes.size(), output); } bool write_all(int fd, const unsigned char *data, size_t size) { while (size != 0) { ssize_t written = write(fd, data, size); if (written <= 0) return false; data += static_cast(written); size -= static_cast(written); } return true; } bool read_all(int fd, unsigned char *data, size_t size) { while (size != 0) { ssize_t read_count = read(fd, data, size); if (read_count <= 0) return false; data += static_cast(read_count); size -= static_cast(read_count); } return true; } bool scope_asset(const std::string &project_path, uint64_t task_id, const std::vector &ids, char relative[4096], uint64_t *size_bytes, unsigned char sha256[32]) { int written = std::snprintf(relative, 4096, ".lardon3d/checkpoints/%llu.scope", static_cast(task_id)); if (written <= 0 || written >= 4096) return false; std::string final_path = project_path + "/" + relative; std::string temporary = final_path + ".tmp"; std::string directory_path = project_path + "/.lardon3d/checkpoints"; std::vector bytes; bytes.reserve(20U + ids.size() * 8U); bytes.insert(bytes.end(), kScopeMagic.begin(), kScopeMagic.end()); for (unsigned shift = 0; shift < 4; ++shift) bytes.push_back(static_cast((kScopeVersion >> (shift * 8U)) & 0xffU)); uint64_t count = ids.size(); for (unsigned shift = 0; shift < 8; ++shift) bytes.push_back(static_cast((count >> (shift * 8U)) & 0xffU)); for (uint64_t id : ids) for (unsigned shift = 0; shift < 8; ++shift) bytes.push_back(static_cast((id >> (shift * 8U)) & 0xffU)); if (!hash_bytes(bytes.data(), bytes.size(), sha256)) return false; int fd = open(temporary.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600); if (fd < 0) return false; bool ok = write_all(fd, bytes.data(), bytes.size()) && fsync(fd) == 0; if (close(fd) != 0) ok = false; if (!ok) { (void)unlink(temporary.c_str()); return false; } ok = rename(temporary.c_str(), final_path.c_str()) == 0; bool final_published = ok; if (ok) { int directory = open(directory_path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (directory < 0) { ok = false; } else { bool synced = fsync(directory) == 0; #ifdef LARDON3D_TRACK_BUILDER_TASK_TESTING const char *fail = std::getenv("LARDON3D_TRACK_BUILDER_TEST_FAIL_SCOPE_DIR_FSYNC"); if (fail && std::strcmp(fail, "1") == 0) synced = false; #endif bool closed = close(directory) == 0; ok = synced && closed; } } if (!ok) { (void)unlink(temporary.c_str()); if (final_published) (void)unlink(final_path.c_str()); } *size_bytes = bytes.size(); return ok; } bool load_scope(const Context &context, std::vector *ids) { std::string path = context.project_path + "/" + context.durable.scope_path; int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW); if (fd < 0 || context.durable.scope_size_bytes > kMaximumScopeBytes) { if (fd >= 0) (void)close(fd); return false; } std::vector bytes(static_cast(context.durable.scope_size_bytes)); bool ok = read_all(fd, bytes.data(), bytes.size()); if (close(fd) != 0) ok = false; unsigned char digest[32]{}; ok = ok && bytes.size() >= 20 && std::memcmp(bytes.data(), kScopeMagic.data(), 8) == 0 && bytes[8] == 1 && bytes[9] == 0 && bytes[10] == 0 && bytes[11] == 0 && hash_bytes(bytes.data(), bytes.size(), digest) && std::memcmp(digest, context.durable.scope_sha256, 32) == 0; if (!ok) return false; uint64_t count = 0; for (unsigned shift = 0; shift < 8; ++shift) count |= static_cast(bytes[12 + shift]) << (shift * 8U); if (count == 0 || count > 10000000ULL || bytes.size() != 20U + count * 8U || count != context.durable.gvr_count) return false; ids->resize(static_cast(count)); for (size_t i = 0; i < ids->size(); ++i) { uint64_t value = 0; for (unsigned shift = 0; shift < 8; ++shift) value |= static_cast(bytes[20 + i * 8 + shift]) << (shift * 8U); if (value == 0 || (i != 0 && (*ids)[i - 1] >= value)) return false; (*ids)[i] = value; } unsigned char scope[32]{}; return scope_hash(*ids, scope) && std::memcmp(scope, context.durable.input_scope_hash, 32) == 0; } bool persist(Context *context, const Lardon3DTask *task) { Lardon3DTaskDurableSnapshot snapshot{}; if (!lardon3d_task_durable_snapshot(task, &snapshot)) return false; char relative[4096]; if (std::snprintf(relative, sizeof(relative), "%s", context->durable.scope_path) <= 0) return false; Lardon3DProjectDbCheckpoint checkpoint{}; std::snprintf(checkpoint.path, sizeof(checkpoint.path), ".lardon3d/checkpoints/%llu.chk", static_cast(snapshot.id)); checkpoint.format_version = LARDON3D_TASK_CHECKPOINT_VERSION; checkpoint.durability = LARDON3D_DB_CHECKPOINT_DURABLE; checkpoint.updated_at = 0; std::string checkpoint_path = context->project_path + "/" + checkpoint.path; Lardon3DTaskCheckpointResult saved = lardon3d_task_checkpoint_save(checkpoint_path.c_str(), &snapshot); if (saved != LARDON3D_TASK_CHECKPOINT_OK && saved != LARDON3D_TASK_CHECKPOINT_PUBLISHED_NOT_DURABLE) return false; checkpoint.durability = saved == LARDON3D_TASK_CHECKPOINT_OK ? LARDON3D_DB_CHECKPOINT_DURABLE : LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE; Lardon3DProjectDbTrackBuilderTask durable = context->durable; #ifdef LARDON3D_TRACK_BUILDER_TASK_TESTING const char *fail = std::getenv("LARDON3D_TRACK_BUILDER_TEST_FAIL_INITIAL_PERSIST"); if (fail && std::strcmp(fail, "1") == 0) return false; #endif return lardon3d_project_db_record_track_builder_task( context->database, &snapshot, LARDON3D_TRACK_BUILDER_TASK_KIND, LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION, &checkpoint, &durable, 0) == LARDON3D_PROJECT_DB_OK; } void cleanup_unowned_creation_assets(const std::string &project_path, const std::string &scope_path, uint64_t task_id) { /* WHY/OWNERSHIP: task_id was freshly allocated and no durable Task row owns * these paths until the initial persist succeeds. This helper is used only * on that pre-record creation path; recovery/existing durable tasks must * never have their scope or checkpoint removed here. */ std::string scope = project_path + "/" + scope_path; std::string checkpoint = project_path + "/.lardon3d/checkpoints/" + std::to_string(task_id) + ".chk"; (void)unlink((scope + ".tmp").c_str()); (void)unlink(scope.c_str()); (void)unlink((checkpoint + ".next").c_str()); (void)unlink((checkpoint + ".tmp").c_str()); (void)unlink(checkpoint.c_str()); } struct UnownedCreationAssets { std::string project_path; std::string scope_path; uint64_t task_id = 0; bool active = true; ~UnownedCreationAssets() { if (active) cleanup_unowned_creation_assets(project_path, scope_path, task_id); } }; bool project_checkpoint(void *userdata) { return lardon3d_task_checkpoint(static_cast(userdata)); } bool run(Lardon3DTask *task, void *userdata) { try { Context *context = static_cast(userdata); if (!lardon3d_task_checkpoint(task)) return false; Lardon3DTrackBuilderProjectRequest request = { context->project_path.c_str(), context->database, context->durable.verifier_kind, context->durable.verifier_version, context->durable.verifier_fingerprint, context->ids.data(), context->ids.size()}; Lardon3DTrackBuilderProjectResult result{}; Lardon3DTrackBuilderProjectStatus status = lardon3d::track_builder_internal::build_project( &request, &result, project_checkpoint, task); if (status == LARDON3D_TRACK_BUILDER_PROJECT_INTERRUPTED) return false; if (status != LARDON3D_TRACK_BUILDER_PROJECT_OK) return lardon3d_task_fail(task, "Track Builder publication impossible."); return lardon3d_task_set_progress(task, 100, result.reused ? "Track Set réutilisé." : "Track Set publié."); } catch (...) { return lardon3d_task_fail(task, "Track Builder runtime exception."); } } void finished(const Lardon3DTask *task, void *userdata) { try { #ifdef LARDON3D_TRACK_BUILDER_TASK_TESTING const char *skip = std::getenv("LARDON3D_TRACK_BUILDER_TEST_SKIP_FINISHED"); if (skip && std::strcmp(skip, "1") == 0) return; #endif (void)persist(static_cast(userdata), task); } catch (...) { /* Terminal checkpoint failure is reported by the durable DB on recovery. */ } } bool make_context(const Lardon3DTaskReconstructionContext *runtime, const Lardon3DProjectDbTrackBuilderTask &durable, Context **output) { if (!runtime || !runtime->project_path || !runtime->project_db || !runtime->resource_governor || durable.builder_version != LARDON3D_TRACK_BUILDER_VERSION || durable.scope_format_version != kScopeVersion) return false; auto *context = new (std::nothrow) Context; if (!context) return false; context->project_path = runtime->project_path; context->database = runtime->project_db; context->governor = runtime->resource_governor; context->durable = durable; if (!load_scope(*context, &context->ids)) { delete context; return false; } unsigned char fingerprint[32]{}; if (!lardon3d_track_builder_fingerprint(fingerprint) || std::memcmp(fingerprint, durable.builder_fingerprint, 32) != 0) { delete context; return false; } *output = context; return true; } } // namespace extern "C" bool lardon3d_track_builder_task_memory_estimate( uint64_t raw_edge_count, uint64_t feature_set_count, uint64_t *memory_bytes) { return compact_memory_estimate(raw_edge_count, feature_set_count, 0, memory_bytes); } extern "C" bool lardon3d_track_builder_task_memory_estimate_with_match_peak( uint64_t raw_edge_count, uint64_t feature_set_count, uint64_t max_match_count, uint64_t *memory_bytes) { return compact_memory_estimate(raw_edge_count, feature_set_count, max_match_count, memory_bytes); } static bool reconstruct_impl( const Lardon3DTaskDurableSnapshot *snapshot, void *userdata, Lardon3DTaskKindBinding *binding) { if (!snapshot || !userdata || !binding) return false; auto *runtime = static_cast(userdata); Lardon3DProjectDbTrackBuilderTask durable{}; if (lardon3d_project_db_load_track_builder_task(runtime->project_db, snapshot->id, &durable) != LARDON3D_PROJECT_DB_OK) return false; Context *context = nullptr; if (!make_context(runtime, durable, &context)) return false; *binding = {}; binding->callback = run; binding->userdata = context; binding->userdata_destroy = destroy_context; binding->finished_callback = finished; binding->finished_userdata = context; return true; } static Lardon3DTask *create_impl( Lardon3DAppState *state, const Lardon3DTrackBuilderTaskConfiguration *configuration, uint64_t *task_id) { if (task_id) *task_id = 0; if (!state || !state->project_loaded || !state->project_db || !state->resource_governor || !configuration || !configuration->project_path || !configuration->verifier_fingerprint || !configuration->gvr_ids || configuration->gvr_count == 0 || configuration->verifier_kind <= 0 || configuration->verifier_version == 0 || (configuration->gvr_count > std::numeric_limits::max() / sizeof(uint64_t)) || configuration->gvr_count > (kMaximumScopeBytes - 20U) / 8U) return nullptr; std::vector ids(configuration->gvr_ids, configuration->gvr_ids + configuration->gvr_count); for (size_t i = 0; i < ids.size(); ++i) if (ids[i] == 0 || (i != 0 && ids[i - 1] >= ids[i])) return nullptr; uint64_t raw_edges = 0; uint64_t max_match_count = 0; std::vector feature_sets; feature_sets.reserve(ids.size() * 2U); for (uint64_t id : ids) { Lardon3DProjectDbGeometricVerificationResult gvr{}; if (lardon3d_project_db_load_geometric_verification_result(state->project_db, id, &gvr) != LARDON3D_PROJECT_DB_OK || !checked_add(raw_edges, gvr.inlier_count, &raw_edges)) return nullptr; Lardon3DProjectDbMatchResult match{}; if (lardon3d_project_db_load_match_result(state->project_db, gvr.match_result_id, &match) != LARDON3D_PROJECT_DB_OK) return nullptr; max_match_count = std::max(max_match_count, static_cast(match.match_count)); feature_sets.push_back(match.feature_set_id_a); feature_sets.push_back(match.feature_set_id_b); } std::sort(feature_sets.begin(), feature_sets.end()); feature_sets.erase(std::unique(feature_sets.begin(), feature_sets.end()), feature_sets.end()); uint64_t memory = 0; if (!compact_memory_estimate(raw_edges, feature_sets.size(), max_match_count, &memory)) return nullptr; unsigned char scope[32]{}, builder[32]{}; if (!scope_hash(ids, scope) || !lardon3d_track_builder_fingerprint(builder)) return nullptr; uint64_t id = 0; if (lardon3d_project_db_allocate_task_id(state->project_db, &id) != LARDON3D_PROJECT_DB_OK) return nullptr; Context *context = new (std::nothrow) Context; if (!context) return nullptr; context->project_path = configuration->project_path; context->database = state->project_db; context->governor = state->resource_governor; context->ids = ids; context->durable.task_id = id; std::snprintf(context->durable.builder_kind, sizeof(context->durable.builder_kind), "track_builder"); context->durable.builder_version = LARDON3D_TRACK_BUILDER_VERSION; std::memcpy(context->durable.builder_fingerprint, builder, 32); context->durable.verifier_kind = configuration->verifier_kind; context->durable.verifier_version = configuration->verifier_version; std::memcpy(context->durable.verifier_fingerprint, configuration->verifier_fingerprint, 32); std::memcpy(context->durable.input_scope_hash, scope, 32); context->durable.gvr_count = ids.size(); context->durable.scope_format_version = kScopeVersion; int scope_written = std::snprintf( context->durable.scope_path, sizeof(context->durable.scope_path), ".lardon3d/checkpoints/%llu.scope", static_cast(id)); if (scope_written <= 0 || static_cast(scope_written) >= sizeof(context->durable.scope_path)) { delete context; return nullptr; } /* Arm before publication so allocation exceptions cannot strand an asset. */ UnownedCreationAssets unowned{context->project_path, context->durable.scope_path, id, true}; if (!scope_asset(context->project_path, id, ids, context->durable.scope_path, &context->durable.scope_size_bytes, context->durable.scope_sha256)) { delete context; return nullptr; } /* Disarm only after the Task row transaction succeeds. Copies keep * exception rollback independent of the Context lifetime. */ Lardon3DResourceEstimate estimate = {memory, 0, 0, 0, 1, 1, 1, 0, 1, LARDON3D_RESOURCE_TASK_CPU}; Lardon3DTask *task = lardon3d_task_create_typed( "Track Builder", &estimate, LARDON3D_TRACK_BUILDER_TASK_KIND, LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION, run, context, destroy_context); if (!task || !lardon3d_task_assign_id(task, id) || !lardon3d_task_set_finished_callback(task, finished, context) || !persist(context, task)) { if (task) lardon3d_task_destroy(task); else delete context; return nullptr; } unowned.active = false; if (task_id) *task_id = id; return task; } extern "C" bool lardon3d_track_builder_task_reconstruct( const Lardon3DTaskDurableSnapshot *snapshot, void *userdata, Lardon3DTaskKindBinding *binding) { try { return reconstruct_impl(snapshot, userdata, binding); } catch (...) { return false; } } extern "C" Lardon3DTask *lardon3d_project_create_track_builder_task( Lardon3DAppState *state, const Lardon3DTrackBuilderTaskConfiguration *configuration, uint64_t *task_id) { try { return create_impl(state, configuration, task_id); } catch (...) { if (task_id) *task_id = 0; return nullptr; } } extern "C" bool lardon3d_project_enqueue_track_builder_task( Lardon3DAppState *state, const Lardon3DTrackBuilderTaskConfiguration *configuration, uint64_t *task_id) { try { Lardon3DTask *task = lardon3d_project_create_track_builder_task( state, configuration, task_id); if (!task || !state || !state->task_queue || !lardon3d_task_queue_add(state->task_queue, task, nullptr)) { if (task) lardon3d_task_destroy(task); return false; } return true; } catch (...) { if (task_id) *task_id = 0; return false; } }