From b506cc6c7cbffaaeacef641355c72b853a1a74d0 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sat, 17 Jan 2026 21:34:24 +0100 Subject: [PATCH] Extend FER/1 receipts and TGK store support --- CMakeLists.txt | 13 + docs/spec-clarifications.md | 64 + include/amduat/enc/fer1_receipt.h | 38 + include/amduat/fer/receipt.h | 42 + include/amduat/tgk/tgk_store_asl_index_fs.h | 38 + .../asl_store_index_fs/asl_store_index_fs.c | 7 + .../tgk_store_asl_index_fs.c | 1699 +++++++++++++++++ src/near_core/asl/index_replay.c | 28 + src/near_core/enc/asl_core_index.c | 6 + src/near_core/enc/asl_log.c | 7 + src/near_core/enc/fer1_receipt.c | 850 +++++++++ src/near_core/fer/receipt.c | 212 +- tests/asl/test_asl_index_replay.c | 23 +- tests/enc/test_fer1_receipt.c | 497 +++++ 14 files changed, 3517 insertions(+), 7 deletions(-) create mode 100644 include/amduat/tgk/tgk_store_asl_index_fs.h create mode 100644 src/adapters/tgk_store_asl_index_fs/tgk_store_asl_index_fs.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 56a5f9a..7ec2184 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,10 @@ set(AMDUAT_TGK_STORE_FS_SRCS src/adapters/tgk_store_fs/tgk_store_fs.c ) +set(AMDUAT_TGK_STORE_ASL_INDEX_FS_SRCS + src/adapters/tgk_store_asl_index_fs/tgk_store_asl_index_fs.c +) + amduat_add_lib(util SRCS ${AMDUAT_UTIL_SRCS}) amduat_add_lib(asl SRCS ${AMDUAT_ASL_SRCS}) @@ -173,6 +177,12 @@ amduat_link(tgk_store_mem amduat_tgk amduat_asl amduat_enc amduat_hash_asl1 amdu amduat_add_lib(tgk_store_fs SRCS ${AMDUAT_TGK_STORE_FS_SRCS}) amduat_link(tgk_store_fs amduat_tgk_store_mem amduat_tgk amduat_asl_store_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util) +amduat_add_lib(tgk_store_asl_index_fs SRCS ${AMDUAT_TGK_STORE_ASL_INDEX_FS_SRCS}) +target_include_directories(amduat_tgk_store_asl_index_fs_obj + PRIVATE ${AMDUAT_ROOT}/src/adapters/asl_store_index_fs +) +amduat_link(tgk_store_asl_index_fs amduat_tgk amduat_asl_store_index_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util) + add_executable(amduat_asl_cli src/tools/amduat_asl_cli.c) target_include_directories(amduat_asl_cli PRIVATE ${AMDUAT_INTERNAL_DIR} @@ -394,6 +404,9 @@ target_link_libraries(amduat_test_asl_store_index_fs PRIVATE amduat_asl_store_index_fs amduat_format pthread ) add_test(NAME asl_store_index_fs COMMAND amduat_test_asl_store_index_fs) +set_tests_properties(asl_store_index_fs PROPERTIES + ENVIRONMENT "AMDUAT_ASL_PERF_COUNT=100;AMDUAT_ASL_STRESS_SECS=20" +) add_executable(amduat_test_pel_program_dag_exec tests/pel/test_pel_program_dag_exec.c) diff --git a/docs/spec-clarifications.md b/docs/spec-clarifications.md index 112fd2d..b96995e 100644 --- a/docs/spec-clarifications.md +++ b/docs/spec-clarifications.md @@ -110,3 +110,67 @@ Decision: References: - `tier1/asl-tgk-execution-plan-1.md` - `tier1/enc-asl-tgk-exec-plan-1.md` + +## Publish/Unpublish Scope (ASL/LOG + ASL/SYSTEM) + +Decision: +- `ARTIFACT_PUBLISH` and `ARTIFACT_UNPUBLISH` are treated as reserved record + types in the core replay path and do not alter ASL index state. +- Publishing is modeled as moving artifacts and index segments between stores, + advancing the destination store's snapshot/log. + +Implications: +- Core replay ignores publish/unpublish records. +- Any visibility policy tied to publishing is handled by higher-level tooling + or system-layer orchestration, not ASL/1 core semantics. + +References: +- `tier1/asl-log-1.md` +- `tier1/asl-system-1.md` + +## Receipt Output Reference Fallback (FER/1 + PEL/1) + +Decision: +- When a PEL run produces no output artifacts (e.g. failed execution), the + receipt's `output_ref` falls back to the stored PEL result artifact reference. + +Implications: +- Receipts can be emitted for both successful and failed runs using a single + canonical output reference. +- Callers using `amduat_fer1_receipt_from_pel_run` should expect `output_ref` + to match `result_ref` when `output_refs_len == 0`. + +References: +- `tier1/enc-fer1-receipt-1.md` +- `tier1/srs.md` + +## FER/1 v1.1 Determinism and Validation (FER/1 + SRS) + +Decision: +- `run_id` is a deterministic hash over stable inputs only and MUST exclude + timestamps, logs, or mutable metadata. +- Typed logs are optional; if present they MUST be ordered and size-bounded. +- Limits are a single required record when the `limits` TLV is present. +- Executor set verification is strict when a policy-provided set exists. + +Concrete rules: +- `run_id = H("AMDUAT:RUN\0" || EncRef(function) || EncRef(input_manifest) || + EncRef(environment) || EncRef(executor_fingerprint))`, where `EncRef` is + `ENC/ASL1-CORE` canonical bytes and `executor_fingerprint` is the canonical + digest reference. No other fields are included. +- `logs` (if present): order by `(kind, cid)` byte-lexicographically; cap to + 64 entries; cap total log payload references to 1 MiB aggregate of capsule + bytes. Reject out-of-order or oversized sets. +- `limits` (if present): exactly one TLV containing all numeric fields + (`cpu_ms`, `wall_ms`, `max_rss_kib`, `io_reads`, `io_writes`) with fixed + units. Reject missing or duplicate fields. +- Executor set validation: + - If an expected executor set is supplied by policy, receipt executor_refs + MUST match it exactly (same members, byte-order, no extras). + - Otherwise, validate strict ordering and uniqueness, and require + `parity_len == executor_refs_len` with aligned ordering and `output_ref` + equality for every parity entry. + +References: +- `tier1/srs.md` +- `tier1/enc-fer1-receipt-1.md` diff --git a/include/amduat/enc/fer1_receipt.h b/include/amduat/enc/fer1_receipt.h index 9b802af..5fad05d 100644 --- a/include/amduat/enc/fer1_receipt.h +++ b/include/amduat/enc/fer1_receipt.h @@ -11,6 +11,10 @@ extern "C" { #endif enum { FER1_RECEIPT_ENC_V1 = 0x0301u }; +enum { FER1_RECEIPT_ENC_V1_1 = 0x0302u }; + +enum { AMDUAT_FER1_VERSION_1 = 0x0001u }; +enum { AMDUAT_FER1_VERSION_1_1 = 0x0101u }; enum { TYPE_TAG_FER1_RECEIPT_1 = 0x00000301u }; enum { AMDUAT_TYPE_TAG_FER1_RECEIPT_1 = TYPE_TAG_FER1_RECEIPT_1 }; @@ -23,6 +27,20 @@ typedef struct { amduat_octets_t parity_digest; } amduat_fer1_parity_entry_t; +typedef struct { + uint32_t kind; + amduat_reference_t log_ref; + amduat_octets_t sha256; +} amduat_fer1_log_entry_t; + +typedef struct { + uint64_t cpu_ms; + uint64_t wall_ms; + uint64_t max_rss_kib; + uint64_t io_reads; + uint64_t io_writes; +} amduat_fer1_limits_t; + typedef struct { uint16_t fer1_version; amduat_reference_t function_ref; @@ -36,14 +54,34 @@ typedef struct { size_t parity_len; uint64_t started_at; uint64_t completed_at; + bool has_executor_fingerprint_ref; + amduat_reference_t executor_fingerprint_ref; + bool has_run_id; + amduat_octets_t run_id; + bool has_limits; + amduat_fer1_limits_t limits; + amduat_fer1_log_entry_t *logs; + size_t logs_len; + bool has_determinism; + uint8_t determinism_level; + bool has_rng_seed; + amduat_octets_t rng_seed; + bool has_signature; + amduat_octets_t signature; } amduat_fer1_receipt_t; bool amduat_enc_fer1_receipt_encode_v1( const amduat_fer1_receipt_t *receipt, amduat_octets_t *out_bytes); +bool amduat_enc_fer1_receipt_encode_v1_1( + const amduat_fer1_receipt_t *receipt, + amduat_octets_t *out_bytes); bool amduat_enc_fer1_receipt_decode_v1( amduat_octets_t bytes, amduat_fer1_receipt_t *out_receipt); +bool amduat_enc_fer1_receipt_decode_v1_1( + amduat_octets_t bytes, + amduat_fer1_receipt_t *out_receipt); void amduat_enc_fer1_receipt_free(amduat_fer1_receipt_t *receipt); #ifdef __cplusplus diff --git a/include/amduat/fer/receipt.h b/include/amduat/fer/receipt.h index 1002d42..ea99456 100644 --- a/include/amduat/fer/receipt.h +++ b/include/amduat/fer/receipt.h @@ -2,6 +2,8 @@ #define AMDUAT_FER_RECEIPT_H #include "amduat/asl/core.h" +#include "amduat/enc/fer1_receipt.h" +#include "amduat/pel/run.h" #include "amduat/pel/surf.h" #include @@ -24,6 +26,46 @@ bool amduat_fer1_receipt_from_pel_result( uint64_t completed_at, amduat_artifact_t *out_artifact); +bool amduat_fer1_receipt_from_pel_run( + const amduat_pel_run_result_t *pel_run, + amduat_reference_t input_manifest_ref, + amduat_reference_t environment_ref, + amduat_octets_t evaluator_id, + amduat_reference_t executor_ref, + bool has_sbom_ref, + amduat_reference_t sbom_ref, + amduat_octets_t parity_digest, + uint64_t started_at, + uint64_t completed_at, + amduat_artifact_t *out_artifact); + +bool amduat_fer1_receipt_from_pel_run_v1_1( + const amduat_pel_run_result_t *pel_run, + amduat_reference_t input_manifest_ref, + amduat_reference_t environment_ref, + amduat_octets_t evaluator_id, + amduat_reference_t executor_ref, + bool has_sbom_ref, + amduat_reference_t sbom_ref, + amduat_octets_t parity_digest, + uint64_t started_at, + uint64_t completed_at, + bool has_executor_fingerprint_ref, + amduat_reference_t executor_fingerprint_ref, + bool has_run_id, + amduat_octets_t run_id, + bool has_limits, + amduat_fer1_limits_t limits, + const amduat_fer1_log_entry_t *logs, + size_t logs_len, + bool has_determinism, + uint8_t determinism_level, + bool has_rng_seed, + amduat_octets_t rng_seed, + bool has_signature, + amduat_octets_t signature, + amduat_artifact_t *out_artifact); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/include/amduat/tgk/tgk_store_asl_index_fs.h b/include/amduat/tgk/tgk_store_asl_index_fs.h new file mode 100644 index 0000000..200b6b1 --- /dev/null +++ b/include/amduat/tgk/tgk_store_asl_index_fs.h @@ -0,0 +1,38 @@ +#ifndef AMDUAT_TGK_TGK_STORE_ASL_INDEX_FS_H +#define AMDUAT_TGK_TGK_STORE_ASL_INDEX_FS_H + +/* TGK/1 projection over ASL index/log (filesystem backend). */ + +#include "amduat/asl/asl_store_index_fs.h" +#include "amduat/tgk/store.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + amduat_asl_store_index_fs_t *asl_fs; + amduat_asl_store_t asl_store; + amduat_tgk_store_config_t config; + amduat_asl_index_state_t pinned_state; + bool pinned; + bool use_shards; +} amduat_tgk_store_asl_index_fs_t; + +bool amduat_tgk_store_asl_index_fs_init( + amduat_tgk_store_asl_index_fs_t *store, + amduat_tgk_store_config_t config, + amduat_asl_store_index_fs_t *asl_fs); + +void amduat_tgk_store_asl_index_fs_free( + amduat_tgk_store_asl_index_fs_t *store); + +amduat_tgk_store_ops_t amduat_tgk_store_asl_index_fs_ops(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_TGK_TGK_STORE_ASL_INDEX_FS_H */ diff --git a/src/adapters/asl_store_index_fs/asl_store_index_fs.c b/src/adapters/asl_store_index_fs/asl_store_index_fs.c index 3ec1c42..40ba2dd 100644 --- a/src/adapters/asl_store_index_fs/asl_store_index_fs.c +++ b/src/adapters/asl_store_index_fs/asl_store_index_fs.c @@ -1076,6 +1076,7 @@ static amduat_asl_store_error_t amduat_asl_store_index_fs_stream_log_apply( size_t header_size = 0u; uint8_t prev_hash[AMDUAT_ASL_STORE_INDEX_FS_LOG_HASH_LEN]; uint64_t last_logseq = 0u; + bool seen_record = false; if (replay_state == NULL || out_last_logseq == NULL || out_last_hash == NULL) { @@ -1126,6 +1127,11 @@ static amduat_asl_store_error_t amduat_asl_store_index_fs_stream_log_apply( } logseq = amduat_asl_store_index_fs_load_u64_le(record_header); + if (seen_record && logseq <= last_logseq) { + free(payload); + close(fd); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } record_type = amduat_asl_store_index_fs_log_load_u32_le(record_header + 8u); payload_len = @@ -1181,6 +1187,7 @@ static amduat_asl_store_error_t amduat_asl_store_index_fs_stream_log_apply( memcpy(prev_hash, record_hash, sizeof(prev_hash)); last_logseq = logseq; + seen_record = true; free(payload); } diff --git a/src/adapters/tgk_store_asl_index_fs/tgk_store_asl_index_fs.c b/src/adapters/tgk_store_asl_index_fs/tgk_store_asl_index_fs.c new file mode 100644 index 0000000..7695b15 --- /dev/null +++ b/src/adapters/tgk_store_asl_index_fs/tgk_store_asl_index_fs.c @@ -0,0 +1,1699 @@ +#include "amduat/tgk/tgk_store_asl_index_fs.h" + +#include "asl_store_index_fs_layout.h" +#include "amduat/asl/index_replay.h" +#include "amduat/asl/index_snapshot.h" +#include "amduat/asl/io.h" +#include "amduat/enc/asl1_core_codec.h" +#include "amduat/enc/asl_core_index.h" +#include "amduat/enc/asl_log.h" +#include "amduat/enc/tgk1_edge.h" +#include "amduat/hash/asl1.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + AMDUAT_TGK_ASL_INDEX_SEGMENT_HASH_LEN = 32, + AMDUAT_TGK_ASL_INDEX_SCAN_PAGE_SIZE = 256u, + AMDUAT_TGK_ASL_INDEX_SHARD_SHIFT = 48 +}; + +typedef struct { + uint64_t segment_id; + uint8_t segment_hash[AMDUAT_TGK_ASL_INDEX_SEGMENT_HASH_LEN]; + uint64_t logseq; +} amduat_tgk_asl_index_segment_entry_t; + +typedef struct { + amduat_reference_t ref; + uint64_t logseq; + bool occupied; +} amduat_tgk_asl_index_ref_entry_t; + +typedef struct { + amduat_tgk_asl_index_ref_entry_t *entries; + size_t capacity; + size_t len; +} amduat_tgk_asl_index_ref_set_t; + +typedef struct { + uint64_t logseq; + amduat_reference_t ref; +} amduat_tgk_asl_index_page_token_t; + +static bool amduat_tgk_asl_index_use_shards(const char *root_path) { + char *shards_path = NULL; + struct stat st; + bool ok = false; + + if (!amduat_asl_store_index_fs_layout_build_shards_path(root_path, + &shards_path)) { + return false; + } + ok = stat(shards_path, &st) == 0 && S_ISDIR(st.st_mode); + free(shards_path); + return ok; +} + +static bool amduat_tgk_asl_index_get_state( + amduat_tgk_store_asl_index_fs_t *store, + amduat_asl_index_state_t *out_state) { + if (store == NULL || out_state == NULL) { + return false; + } + if (store->pinned) { + *out_state = store->pinned_state; + return true; + } + return amduat_asl_index_current_state(&store->asl_store, out_state); +} + +static uint64_t amduat_tgk_asl_index_ref_hash(amduat_reference_t ref) { + uint64_t hash = 14695981039346656037ull; + uint8_t hash_id_bytes[2]; + + hash_id_bytes[0] = (uint8_t)(ref.hash_id & 0xffu); + hash_id_bytes[1] = (uint8_t)((ref.hash_id >> 8) & 0xffu); + hash ^= hash_id_bytes[0]; + hash *= 1099511628211ull; + hash ^= hash_id_bytes[1]; + hash *= 1099511628211ull; + if (ref.digest.len != 0u && ref.digest.data != NULL) { + size_t i; + for (i = 0; i < ref.digest.len; ++i) { + hash ^= ref.digest.data[i]; + hash *= 1099511628211ull; + } + } + return hash; +} + +static int amduat_tgk_asl_index_ref_cmp(amduat_reference_t a, + amduat_reference_t b) { + size_t min_len; + int cmp; + + if (a.hash_id < b.hash_id) { + return -1; + } + if (a.hash_id > b.hash_id) { + return 1; + } + min_len = a.digest.len < b.digest.len ? a.digest.len : b.digest.len; + if (min_len != 0u && a.digest.data != NULL && b.digest.data != NULL) { + cmp = memcmp(a.digest.data, b.digest.data, min_len); + if (cmp != 0) { + return cmp; + } + } else if (min_len != 0u) { + return (a.digest.data == NULL) ? -1 : 1; + } + if (a.digest.len < b.digest.len) { + return -1; + } + if (a.digest.len > b.digest.len) { + return 1; + } + return 0; +} + +static bool amduat_tgk_asl_index_ref_set_init( + amduat_tgk_asl_index_ref_set_t *set, + size_t initial_capacity) { + size_t cap = 1u; + + if (set == NULL) { + return false; + } + while (cap < initial_capacity) { + cap <<= 1u; + if (cap == 0u) { + return false; + } + } + set->entries = (amduat_tgk_asl_index_ref_entry_t *)calloc( + cap, sizeof(*set->entries)); + if (set->entries == NULL) { + return false; + } + set->capacity = cap; + set->len = 0u; + return true; +} + +static void amduat_tgk_asl_index_ref_set_free( + amduat_tgk_asl_index_ref_set_t *set) { + size_t i; + + if (set == NULL) { + return; + } + if (set->entries != NULL) { + for (i = 0; i < set->capacity; ++i) { + if (set->entries[i].occupied) { + amduat_reference_free(&set->entries[i].ref); + } + } + } + free(set->entries); + set->entries = NULL; + set->capacity = 0u; + set->len = 0u; +} + +static bool amduat_tgk_asl_index_ref_set_resize( + amduat_tgk_asl_index_ref_set_t *set, + size_t new_capacity) { + amduat_tgk_asl_index_ref_entry_t *old_entries; + size_t old_capacity; + size_t i; + + if (set == NULL || new_capacity == 0u) { + return false; + } + + old_entries = set->entries; + old_capacity = set->capacity; + + set->entries = (amduat_tgk_asl_index_ref_entry_t *)calloc( + new_capacity, sizeof(*set->entries)); + if (set->entries == NULL) { + set->entries = old_entries; + return false; + } + set->capacity = new_capacity; + set->len = 0u; + + for (i = 0; i < old_capacity; ++i) { + if (old_entries[i].occupied) { + amduat_reference_t ref = old_entries[i].ref; + uint64_t logseq = old_entries[i].logseq; + size_t mask = new_capacity - 1u; + size_t slot = (size_t)(amduat_tgk_asl_index_ref_hash(ref) & mask); + + while (set->entries[slot].occupied) { + slot = (slot + 1u) & mask; + } + set->entries[slot].ref = ref; + set->entries[slot].logseq = logseq; + set->entries[slot].occupied = true; + set->len += 1u; + } + } + free(old_entries); + return true; +} + +static amduat_tgk_asl_index_ref_entry_t * +amduat_tgk_asl_index_ref_set_find( + amduat_tgk_asl_index_ref_set_t *set, + amduat_reference_t ref) { + size_t mask; + size_t slot; + + if (set == NULL || set->capacity == 0u) { + return NULL; + } + mask = set->capacity - 1u; + slot = (size_t)(amduat_tgk_asl_index_ref_hash(ref) & mask); + while (set->entries[slot].occupied) { + if (amduat_reference_eq(set->entries[slot].ref, ref)) { + return &set->entries[slot]; + } + slot = (slot + 1u) & mask; + } + return NULL; +} + +static bool amduat_tgk_asl_index_ref_set_insert( + amduat_tgk_asl_index_ref_set_t *set, + amduat_reference_t ref, + uint64_t logseq) { + size_t mask; + size_t slot; + amduat_reference_t stored; + + if (set == NULL) { + return false; + } + if (set->len * 10u >= set->capacity * 7u) { + size_t new_capacity = set->capacity == 0u ? 16u : set->capacity * 2u; + if (new_capacity < set->capacity) { + return false; + } + if (!amduat_tgk_asl_index_ref_set_resize(set, new_capacity)) { + return false; + } + } + + mask = set->capacity - 1u; + slot = (size_t)(amduat_tgk_asl_index_ref_hash(ref) & mask); + while (set->entries[slot].occupied) { + if (amduat_reference_eq(set->entries[slot].ref, ref)) { + return true; + } + slot = (slot + 1u) & mask; + } + + if (!amduat_reference_clone(ref, &stored)) { + return false; + } + set->entries[slot].ref = stored; + set->entries[slot].logseq = logseq; + set->entries[slot].occupied = true; + set->len += 1u; + return true; +} + +static bool amduat_tgk_asl_index_read_file(const char *path, + uint8_t **out_bytes, + size_t *out_len) { + struct stat st; + + if (path == NULL || out_bytes == NULL || out_len == NULL) { + return false; + } + *out_bytes = NULL; + *out_len = 0u; + if (stat(path, &st) != 0) { + if (errno == ENOENT || errno == ENOTDIR) { + return true; + } + return false; + } + if (!S_ISREG(st.st_mode)) { + return false; + } + return amduat_asl_read_path(path, out_bytes, out_len); +} + +static bool amduat_tgk_asl_index_load_log_records( + const char *root_path, + amduat_asl_log_record_t **out_records, + size_t *out_count) { + char *log_path = NULL; + uint8_t *log_bytes = NULL; + size_t log_len = 0u; + bool ok = false; + + if (out_records == NULL || out_count == NULL) { + return false; + } + *out_records = NULL; + *out_count = 0u; + + if (!amduat_asl_store_index_fs_layout_build_log_path(root_path, &log_path)) { + return false; + } + if (!amduat_tgk_asl_index_read_file(log_path, &log_bytes, &log_len)) { + free(log_path); + return false; + } + free(log_path); + + if (log_bytes == NULL || log_len == 0u) { + free(log_bytes); + return true; + } + + ok = amduat_enc_asl_log_decode_v1(amduat_octets(log_bytes, log_len), + out_records, + out_count); + free(log_bytes); + return ok; +} + +static bool amduat_tgk_asl_index_parse_segment_seal( + amduat_octets_t payload, + amduat_asl_segment_seal_t *out_seal) { + if (payload.len < 8u + 32u || payload.data == NULL || out_seal == NULL) { + return false; + } + out_seal->segment_id = (uint64_t)payload.data[0] | + ((uint64_t)payload.data[1] << 8) | + ((uint64_t)payload.data[2] << 16) | + ((uint64_t)payload.data[3] << 24) | + ((uint64_t)payload.data[4] << 32) | + ((uint64_t)payload.data[5] << 40) | + ((uint64_t)payload.data[6] << 48) | + ((uint64_t)payload.data[7] << 56); + memcpy(out_seal->segment_hash, payload.data + 8u, 32u); + return true; +} + +static bool amduat_tgk_asl_index_build_segments( + const amduat_asl_log_record_t *records, + size_t record_count, + uint64_t log_position, + amduat_tgk_asl_index_segment_entry_t **out_segments, + size_t *out_len) { + size_t cap = 0u; + size_t len = 0u; + amduat_tgk_asl_index_segment_entry_t *segments = NULL; + size_t i; + + if (out_segments == NULL || out_len == NULL) { + return false; + } + *out_segments = NULL; + *out_len = 0u; + + if (records == NULL || record_count == 0u) { + return true; + } + + for (i = 0; i < record_count; ++i) { + const amduat_asl_log_record_t *record = &records[i]; + size_t j; + amduat_asl_segment_seal_t seal; + bool found = false; + + if (record->logseq > log_position) { + break; + } + if (record->record_type != AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL) { + continue; + } + if (!amduat_tgk_asl_index_parse_segment_seal(record->payload, &seal)) { + continue; + } + for (j = 0; j < len; ++j) { + if (segments[j].segment_id == seal.segment_id) { + memcpy(segments[j].segment_hash, seal.segment_hash, 32u); + segments[j].logseq = record->logseq; + found = true; + break; + } + } + if (found) { + continue; + } + if (len == cap) { + size_t new_cap = cap == 0u ? 16u : cap * 2u; + amduat_tgk_asl_index_segment_entry_t *next = + (amduat_tgk_asl_index_segment_entry_t *)realloc( + segments, new_cap * sizeof(*segments)); + if (next == NULL) { + free(segments); + return false; + } + segments = next; + cap = new_cap; + } + segments[len].segment_id = seal.segment_id; + memcpy(segments[len].segment_hash, seal.segment_hash, 32u); + segments[len].logseq = record->logseq; + len += 1u; + } + + *out_segments = segments; + *out_len = len; + return true; +} + +static int amduat_tgk_asl_index_segment_cmp(const void *a, const void *b) { + const amduat_tgk_asl_index_segment_entry_t *seg_a = + (const amduat_tgk_asl_index_segment_entry_t *)a; + const amduat_tgk_asl_index_segment_entry_t *seg_b = + (const amduat_tgk_asl_index_segment_entry_t *)b; + + if (seg_a->logseq < seg_b->logseq) { + return -1; + } + if (seg_a->logseq > seg_b->logseq) { + return 1; + } + if (seg_a->segment_id < seg_b->segment_id) { + return -1; + } + if (seg_a->segment_id > seg_b->segment_id) { + return 1; + } + return memcmp(seg_a->segment_hash, seg_b->segment_hash, 32u); +} + +static size_t amduat_tgk_asl_index_find_log_start( + const amduat_asl_log_record_t *records, + size_t record_count, + uint64_t anchor_logseq) { + size_t i; + + if (records == NULL || record_count == 0u) { + return 0u; + } + for (i = 0u; i < record_count; ++i) { + if (records[i].logseq > anchor_logseq) { + return i; + } + } + return record_count; +} + +static bool amduat_tgk_asl_index_init_replay( + const char *root_path, + amduat_asl_snapshot_id_t snapshot_id, + const amduat_asl_log_record_t *records, + size_t record_count, + uint64_t log_position, + amduat_asl_replay_state_t *out_state) { + amduat_asl_snapshot_manifest_t manifest; + char *manifest_path = NULL; + uint8_t manifest_hash[32]; + size_t replay_start = 0u; + + if (out_state == NULL) { + return false; + } + if (!amduat_asl_replay_init(out_state)) { + return false; + } + + if (snapshot_id == 0u) { + return amduat_asl_replay_apply_log(records, + record_count, + log_position, + out_state); + } + + if (!amduat_asl_store_index_fs_layout_build_snapshot_manifest_path( + root_path, snapshot_id, &manifest_path)) { + amduat_asl_replay_free(out_state); + return false; + } + if (!amduat_asl_snapshot_manifest_read(manifest_path, + &manifest, + manifest_hash)) { + free(manifest_path); + amduat_asl_replay_free(out_state); + return false; + } + free(manifest_path); + + if (manifest.anchor_logseq > log_position) { + amduat_asl_snapshot_manifest_free(&manifest); + amduat_asl_replay_free(out_state); + return false; + } + + out_state->segments = manifest.segments; + out_state->segments_len = manifest.segments_len; + out_state->tombstones = manifest.tombstones; + out_state->tombstones_len = manifest.tombstones_len; + out_state->state.snapshot_id = manifest.snapshot_id; + out_state->state.log_position = manifest.anchor_logseq; + manifest.segments = NULL; + manifest.segments_len = 0u; + manifest.tombstones = NULL; + manifest.tombstones_len = 0u; + amduat_asl_snapshot_manifest_free(&manifest); + + replay_start = amduat_tgk_asl_index_find_log_start(records, + record_count, + out_state->state.log_position); + + if (!amduat_asl_replay_apply_log(records + replay_start, + record_count - replay_start, + log_position, + out_state)) { + amduat_asl_replay_free(out_state); + return false; + } + return true; +} + +static bool amduat_tgk_asl_index_is_tombstoned( + const amduat_asl_replay_state_t *state, + amduat_reference_t ref) { + size_t i; + + if (state == NULL) { + return false; + } + for (i = 0; i < state->tombstones_len; ++i) { + if (amduat_reference_eq(state->tombstones[i].ref, ref)) { + return true; + } + } + return false; +} + +static uint16_t amduat_tgk_asl_index_segment_shard(uint64_t segment_id) { + return (uint16_t)(segment_id >> AMDUAT_TGK_ASL_INDEX_SHARD_SHIFT); +} + +static bool amduat_tgk_asl_index_build_segment_path( + const amduat_tgk_store_asl_index_fs_t *store, + uint64_t segment_id, + char **out_path) { + uint16_t shard_id; + + if (store == NULL || out_path == NULL) { + return false; + } + shard_id = amduat_tgk_asl_index_segment_shard(segment_id); + if (store->use_shards) { + return amduat_asl_store_index_fs_layout_build_shard_segment_path( + store->asl_fs->root_path, shard_id, segment_id, out_path); + } + return amduat_asl_store_index_fs_layout_build_segment_path( + store->asl_fs->root_path, segment_id, out_path); +} + +static bool amduat_tgk_asl_index_build_block_path( + const amduat_tgk_store_asl_index_fs_t *store, + uint64_t block_id, + char **out_path) { + uint16_t shard_id; + + if (store == NULL || out_path == NULL) { + return false; + } + shard_id = amduat_tgk_asl_index_segment_shard(block_id); + if (store->use_shards) { + return amduat_asl_store_index_fs_layout_build_shard_block_path( + store->asl_fs->root_path, shard_id, block_id, out_path); + } + return amduat_asl_store_index_fs_layout_build_block_path( + store->asl_fs->root_path, block_id, out_path); +} + +static bool amduat_tgk_asl_index_load_segment( + const amduat_tgk_store_asl_index_fs_t *store, + const amduat_tgk_asl_index_segment_entry_t *entry, + amduat_asl_core_index_segment_t *out_segment) { + char *segment_path = NULL; + uint8_t *segment_bytes = NULL; + size_t segment_len = 0u; + uint8_t segment_hash[32]; + bool ok; + + if (store == NULL || entry == NULL || out_segment == NULL) { + return false; + } + if (!amduat_tgk_asl_index_build_segment_path(store, + entry->segment_id, + &segment_path)) { + return false; + } + ok = amduat_tgk_asl_index_read_file(segment_path, + &segment_bytes, + &segment_len); + free(segment_path); + if (!ok || segment_bytes == NULL) { + free(segment_bytes); + return false; + } + + if (!amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256, + amduat_octets(segment_bytes, segment_len), + segment_hash, + sizeof(segment_hash))) { + free(segment_bytes); + return false; + } + if (memcmp(segment_hash, entry->segment_hash, sizeof(segment_hash)) != 0) { + free(segment_bytes); + return false; + } + if (!amduat_enc_asl_core_index_decode_v1( + amduat_octets(segment_bytes, segment_len), out_segment)) { + free(segment_bytes); + return false; + } + free(segment_bytes); + return true; +} + +static bool amduat_tgk_asl_index_record_ref( + const amduat_asl_core_index_segment_t *segment, + const amduat_asl_index_record_t *record, + amduat_reference_t *out_ref) { + size_t offset; + const uint8_t *digest; + + if (segment == NULL || record == NULL || out_ref == NULL) { + return false; + } + if (record->digest_offset < segment->header.digests_offset) { + return false; + } + offset = (size_t)(record->digest_offset - segment->header.digests_offset); + if (offset > segment->digests.len || + record->digest_len > segment->digests.len - offset) { + return false; + } + digest = segment->digests.data + offset; + *out_ref = amduat_reference((amduat_hash_id_t)record->hash_id, + amduat_octets(digest, record->digest_len)); + return true; +} + +static bool amduat_tgk_asl_index_read_block_range( + const char *path, + uint32_t offset, + uint32_t length, + uint8_t *out) { + int fd; + ssize_t nread; + size_t total = 0u; + + fd = open(path, O_RDONLY); + if (fd < 0) { + return false; + } + if (lseek(fd, (off_t)offset, SEEK_SET) < 0) { + close(fd); + return false; + } + while (total < length) { + nread = read(fd, out + total, length - total); + if (nread < 0) { + if (errno == EINTR) { + continue; + } + close(fd); + return false; + } + if (nread == 0) { + close(fd); + return false; + } + total += (size_t)nread; + } + close(fd); + return true; +} + +static bool amduat_tgk_asl_index_materialize_artifact( + const amduat_tgk_store_asl_index_fs_t *store, + const amduat_asl_core_index_segment_t *segment, + const amduat_asl_index_record_t *record, + amduat_artifact_t *out_artifact) { + size_t extents_base; + size_t i; + uint32_t total_len; + uint8_t *buffer; + size_t cursor = 0u; + + if (store == NULL || segment == NULL || record == NULL || + out_artifact == NULL) { + return false; + } + if (record->extent_count == 0u) { + return false; + } + if (record->extents_offset < segment->header.extents_offset) { + return false; + } + if ((record->extents_offset - segment->header.extents_offset) % + AMDUAT_ASL_CORE_INDEX_EXTENT_SIZE != + 0u) { + return false; + } + extents_base = (size_t)((record->extents_offset - + segment->header.extents_offset) / + AMDUAT_ASL_CORE_INDEX_EXTENT_SIZE); + if (extents_base + record->extent_count > segment->extent_count) { + return false; + } + + total_len = record->total_length; + buffer = (uint8_t *)malloc(total_len); + if (buffer == NULL) { + return false; + } + + for (i = 0; i < record->extent_count; ++i) { + const amduat_asl_extent_record_t *extent = + &segment->extents[extents_base + i]; + char *block_path = NULL; + if (extent->length == 0u) { + free(buffer); + return false; + } + if (!amduat_tgk_asl_index_build_block_path(store, + extent->block_id, + &block_path)) { + free(buffer); + return false; + } + if (!amduat_tgk_asl_index_read_block_range(block_path, + extent->offset, + extent->length, + buffer + cursor)) { + free(block_path); + free(buffer); + return false; + } + free(block_path); + cursor += extent->length; + } + + if (cursor != total_len) { + free(buffer); + return false; + } + + if (!amduat_enc_asl1_core_decode_artifact_v1( + amduat_octets(buffer, total_len), out_artifact)) { + free(buffer); + return false; + } + free(buffer); + return true; +} + +static bool amduat_tgk_asl_index_edge_tag_supported( + amduat_tgk_store_config_t config, + uint32_t tag_id) { + size_t i; + + for (i = 0; i < config.tgk_profiles.edge_tags_len; ++i) { + if (config.tgk_profiles.edge_tags[i] == tag_id) { + return true; + } + } + return false; +} + +static bool amduat_tgk_asl_index_edge_type_supported( + amduat_tgk_store_config_t config, + amduat_tgk_edge_type_id_t type_id) { + size_t i; + + for (i = 0; i < config.tgk_profiles.edge_types_len; ++i) { + if (config.tgk_profiles.edge_types[i] == type_id) { + return true; + } + } + return false; +} + +static bool amduat_tgk_asl_index_encoding_supported( + amduat_asl_encoding_profile_id_t profile_id) { + switch (profile_id) { + case TGK1_EDGE_ENC_V1: + return true; + default: + return false; + } +} + +static bool amduat_tgk_asl_index_decode_profile( + amduat_asl_encoding_profile_id_t profile_id, + amduat_octets_t bytes, + amduat_tgk_edge_body_t *out_body) { + switch (profile_id) { + case TGK1_EDGE_ENC_V1: + return amduat_enc_tgk1_edge_decode_v1(bytes, out_body); + default: + return false; + } +} + +static amduat_tgk_graph_error_t amduat_tgk_asl_index_decode_edge( + amduat_tgk_store_config_t config, + amduat_artifact_t artifact, + amduat_tgk_edge_body_t *out_body) { + size_t i; + bool saw_supported = false; + bool have_body = false; + amduat_tgk_edge_body_t resolved; + + if (out_body == NULL) { + return GS_ERR_UNSUPPORTED; + } + memset(out_body, 0, sizeof(*out_body)); + + if (!artifact.has_type_tag || + !amduat_tgk_asl_index_edge_tag_supported(config, + artifact.type_tag.tag_id)) { + return GS_ERR_NOT_EDGE; + } + if (artifact.bytes.len != 0u && artifact.bytes.data == NULL) { + return GS_ERR_NOT_EDGE; + } + if (config.tgk_profiles.encodings_len != 0u && + config.tgk_profiles.encodings == NULL) { + return GS_ERR_UNSUPPORTED; + } + + memset(&resolved, 0, sizeof(resolved)); + for (i = 0; i < config.tgk_profiles.encodings_len; ++i) { + amduat_asl_encoding_profile_id_t profile_id = + config.tgk_profiles.encodings[i]; + amduat_tgk_edge_body_t candidate; + + if (!amduat_tgk_asl_index_encoding_supported(profile_id)) { + continue; + } + saw_supported = true; + + if (!amduat_tgk_asl_index_decode_profile(profile_id, + artifact.bytes, + &candidate)) { + continue; + } + if (!amduat_tgk_edge_body_has_endpoints(&candidate)) { + amduat_tgk_edge_body_free(&candidate); + if (have_body) { + amduat_tgk_edge_body_free(&resolved); + } + return GS_ERR_INTEGRITY; + } + if (!amduat_tgk_asl_index_edge_type_supported(config, candidate.type)) { + amduat_tgk_edge_body_free(&candidate); + continue; + } + if (!have_body) { + resolved = candidate; + have_body = true; + continue; + } + if (!amduat_tgk_edge_body_eq(&resolved, &candidate)) { + amduat_tgk_edge_body_free(&candidate); + amduat_tgk_edge_body_free(&resolved); + return GS_ERR_INTEGRITY; + } + amduat_tgk_edge_body_free(&candidate); + } + if (!saw_supported) { + return GS_ERR_UNSUPPORTED; + } + if (!have_body) { + return GS_ERR_NOT_EDGE; + } + *out_body = resolved; + return 0; +} + +static bool amduat_tgk_asl_index_type_filter_match( + amduat_tgk_edge_type_filter_t filter, + amduat_tgk_edge_type_id_t type_id) { + size_t i; + + if (filter.types_len == 0) { + return true; + } + if (filter.types == NULL) { + return false; + } + for (i = 0; i < filter.types_len; ++i) { + if (filter.types[i] == type_id) { + return true; + } + } + return false; +} + +static bool amduat_tgk_asl_index_node_in_list( + amduat_reference_t node, + const amduat_reference_t *list, + size_t len) { + size_t i; + + for (i = 0; i < len; ++i) { + if (amduat_reference_eq(node, list[i])) { + return true; + } + } + return false; +} + +static int amduat_tgk_asl_index_edge_cmp(const void *a, const void *b) { + const amduat_tgk_graph_edge_view_t *edge_a = + (const amduat_tgk_graph_edge_view_t *)a; + const amduat_tgk_graph_edge_view_t *edge_b = + (const amduat_tgk_graph_edge_view_t *)b; + + return amduat_tgk_asl_index_ref_cmp(edge_a->edge_ref, edge_b->edge_ref); +} + +static void amduat_tgk_asl_index_edge_array_free( + amduat_tgk_graph_edge_view_t *edges, + size_t len) { + size_t i; + + if (edges == NULL) { + return; + } + for (i = 0; i < len; ++i) { + amduat_reference_free(&edges[i].edge_ref); + amduat_tgk_edge_body_free(&edges[i].body); + } + free(edges); +} + +static bool amduat_tgk_asl_index_encode_page_token( + uint64_t logseq, + amduat_reference_t ref, + amduat_octets_t *out_token) { + amduat_octets_t ref_bytes; + uint8_t *buffer; + size_t ref_len; + + if (out_token == NULL) { + return false; + } + out_token->data = NULL; + out_token->len = 0u; + + ref_bytes = amduat_octets(NULL, 0u); + if (!amduat_enc_asl1_core_encode_reference_v1(ref, &ref_bytes)) { + return false; + } + ref_len = ref_bytes.len; + if (ref_len > SIZE_MAX - 8u) { + amduat_octets_free(&ref_bytes); + return false; + } + buffer = (uint8_t *)malloc(8u + ref_len); + if (buffer == NULL) { + amduat_octets_free(&ref_bytes); + return false; + } + buffer[0] = (uint8_t)(logseq & 0xffu); + buffer[1] = (uint8_t)((logseq >> 8) & 0xffu); + buffer[2] = (uint8_t)((logseq >> 16) & 0xffu); + buffer[3] = (uint8_t)((logseq >> 24) & 0xffu); + buffer[4] = (uint8_t)((logseq >> 32) & 0xffu); + buffer[5] = (uint8_t)((logseq >> 40) & 0xffu); + buffer[6] = (uint8_t)((logseq >> 48) & 0xffu); + buffer[7] = (uint8_t)((logseq >> 56) & 0xffu); + memcpy(buffer + 8u, ref_bytes.data, ref_len); + amduat_octets_free(&ref_bytes); + out_token->data = buffer; + out_token->len = 8u + ref_len; + return true; +} + +static bool amduat_tgk_asl_index_decode_page_token( + amduat_octets_t token, + amduat_tgk_asl_index_page_token_t *out_token) { + if (out_token == NULL) { + return false; + } + if (token.len <= 8u || token.data == NULL) { + return false; + } + out_token->logseq = (uint64_t)token.data[0] | + ((uint64_t)token.data[1] << 8) | + ((uint64_t)token.data[2] << 16) | + ((uint64_t)token.data[3] << 24) | + ((uint64_t)token.data[4] << 32) | + ((uint64_t)token.data[5] << 40) | + ((uint64_t)token.data[6] << 48) | + ((uint64_t)token.data[7] << 56); + if (!amduat_enc_asl1_core_decode_reference_v1( + amduat_octets(token.data + 8u, token.len - 8u), + &out_token->ref)) { + return false; + } + return true; +} + +static bool amduat_tgk_asl_index_token_before_edge( + const amduat_tgk_asl_index_page_token_t *token, + uint64_t logseq, + amduat_reference_t ref) { + int cmp; + + if (token == NULL) { + return true; + } + if (logseq < token->logseq) { + return false; + } + if (logseq > token->logseq) { + return true; + } + cmp = amduat_tgk_asl_index_ref_cmp(ref, token->ref); + return cmp > 0; +} + +static bool amduat_tgk_asl_index_scan_edges_internal( + amduat_tgk_store_asl_index_fs_t *store, + amduat_tgk_edge_type_filter_t type_filter, + const amduat_reference_t *node, + bool check_from, + bool check_to, + amduat_octets_t page_token, + bool has_page_token, + size_t limit, + amduat_tgk_graph_scan_result_t *out_scan) { + amduat_asl_index_state_t state; + amduat_asl_log_record_t *records = NULL; + size_t record_count = 0u; + amduat_tgk_asl_index_segment_entry_t *segments = NULL; + size_t segments_len = 0u; + amduat_asl_replay_state_t replay_state; + amduat_tgk_asl_index_ref_set_t visible_refs; + amduat_tgk_asl_index_page_token_t token; + bool have_token = false; + bool ok = false; + size_t i; + + if (out_scan == NULL) { + return false; + } + out_scan->edges.edges = NULL; + out_scan->edges.len = 0u; + out_scan->next_page_token = amduat_octets(NULL, 0u); + out_scan->has_next_page = false; + + if (store == NULL) { + return false; + } + if (!amduat_tgk_asl_index_get_state(store, &state)) { + return false; + } + + if (!amduat_tgk_asl_index_load_log_records(store->asl_fs->root_path, + &records, + &record_count)) { + return false; + } + + if (has_page_token) { + if (!amduat_tgk_asl_index_decode_page_token(page_token, &token)) { + amduat_enc_asl_log_free(records, record_count); + return false; + } + have_token = true; + } + + if (!amduat_tgk_asl_index_build_segments(records, + record_count, + state.log_position, + &segments, + &segments_len)) { + amduat_enc_asl_log_free(records, record_count); + if (have_token) { + amduat_reference_free(&token.ref); + } + return false; + } + if (segments_len != 0u) { + qsort(segments, segments_len, sizeof(*segments), + amduat_tgk_asl_index_segment_cmp); + } + + if (!amduat_tgk_asl_index_init_replay(store->asl_fs->root_path, + state.snapshot_id, + records, + record_count, + state.log_position, + &replay_state)) { + free(segments); + amduat_enc_asl_log_free(records, record_count); + if (have_token) { + amduat_reference_free(&token.ref); + } + return false; + } + + if (!amduat_tgk_asl_index_ref_set_init(&visible_refs, segments_len * 4u)) { + amduat_asl_replay_free(&replay_state); + free(segments); + amduat_enc_asl_log_free(records, record_count); + if (have_token) { + amduat_reference_free(&token.ref); + } + return false; + } + + for (i = segments_len; i > 0u; --i) { + const amduat_tgk_asl_index_segment_entry_t *entry = &segments[i - 1u]; + amduat_asl_core_index_segment_t segment; + size_t r; + + if (!amduat_tgk_asl_index_load_segment(store, entry, &segment)) { + continue; + } + for (r = 0; r < segment.record_count; ++r) { + const amduat_asl_index_record_t *record = &segment.records[r]; + amduat_reference_t ref; + + if ((record->flags & AMDUAT_ASL_INDEX_FLAG_TOMBSTONE) != 0u) { + continue; + } + if (!amduat_tgk_asl_index_record_ref(&segment, record, &ref)) { + continue; + } + if (amduat_tgk_asl_index_is_tombstoned(&replay_state, ref)) { + continue; + } + if (amduat_tgk_asl_index_ref_set_find(&visible_refs, ref) != NULL) { + continue; + } + if (!amduat_tgk_asl_index_ref_set_insert(&visible_refs, + ref, + entry->logseq)) { + amduat_enc_asl_core_index_free(&segment); + goto cleanup; + } + } + amduat_enc_asl_core_index_free(&segment); + } + + for (i = 0; i < segments_len; ++i) { + const amduat_tgk_asl_index_segment_entry_t *entry = &segments[i]; + amduat_asl_core_index_segment_t segment; + amduat_tgk_graph_edge_view_t *segment_edges = NULL; + size_t seg_count = 0u; + size_t seg_cap = 0u; + size_t r; + + if (!amduat_tgk_asl_index_load_segment(store, entry, &segment)) { + continue; + } + for (r = 0; r < segment.record_count; ++r) { + const amduat_asl_index_record_t *record = &segment.records[r]; + amduat_reference_t ref; + amduat_tgk_asl_index_ref_entry_t *visible; + amduat_artifact_t artifact; + amduat_tgk_edge_body_t body; + amduat_tgk_graph_error_t edge_err; + + if ((record->flags & AMDUAT_ASL_INDEX_FLAG_TOMBSTONE) != 0u) { + continue; + } + if (!amduat_tgk_asl_index_record_ref(&segment, record, &ref)) { + continue; + } + visible = amduat_tgk_asl_index_ref_set_find(&visible_refs, ref); + if (visible == NULL || visible->logseq != entry->logseq) { + continue; + } + if (amduat_tgk_asl_index_is_tombstoned(&replay_state, ref)) { + continue; + } + + if (!amduat_tgk_asl_index_materialize_artifact(store, + &segment, + record, + &artifact)) { + continue; + } + edge_err = amduat_tgk_asl_index_decode_edge(store->config, + artifact, + &body); + amduat_artifact_free(&artifact); + if (edge_err != 0) { + continue; + } + if (!amduat_tgk_asl_index_type_filter_match(type_filter, body.type)) { + amduat_tgk_edge_body_free(&body); + continue; + } + if (node != NULL) { + bool matched = false; + if (check_from && + amduat_tgk_asl_index_node_in_list(*node, + body.from, + body.from_len)) { + matched = true; + } + if (check_to && + amduat_tgk_asl_index_node_in_list(*node, + body.to, + body.to_len)) { + matched = true; + } + if (!matched) { + amduat_tgk_edge_body_free(&body); + continue; + } + } + + if (seg_count == seg_cap) { + size_t new_cap = seg_cap == 0u ? 16u : seg_cap * 2u; + amduat_tgk_graph_edge_view_t *next = + (amduat_tgk_graph_edge_view_t *)realloc( + segment_edges, new_cap * sizeof(*segment_edges)); + if (next == NULL) { + amduat_tgk_edge_body_free(&body); + amduat_enc_asl_core_index_free(&segment); + amduat_tgk_asl_index_edge_array_free(segment_edges, seg_count); + goto cleanup; + } + segment_edges = next; + seg_cap = new_cap; + } + segment_edges[seg_count].edge_ref = + amduat_reference(ref.hash_id, ref.digest); + if (!amduat_reference_clone(ref, &segment_edges[seg_count].edge_ref)) { + amduat_tgk_edge_body_free(&body); + amduat_enc_asl_core_index_free(&segment); + amduat_tgk_asl_index_edge_array_free(segment_edges, seg_count); + goto cleanup; + } + segment_edges[seg_count].body = body; + seg_count += 1u; + } + amduat_enc_asl_core_index_free(&segment); + + if (seg_count != 0u) { + qsort(segment_edges, seg_count, sizeof(*segment_edges), + amduat_tgk_asl_index_edge_cmp); + } + + for (r = 0; r < seg_count; ++r) { + amduat_tgk_graph_edge_view_t *edge = &segment_edges[r]; + if (have_token && + !amduat_tgk_asl_index_token_before_edge(&token, + entry->logseq, + edge->edge_ref)) { + amduat_reference_free(&edge->edge_ref); + amduat_tgk_edge_body_free(&edge->body); + continue; + } + if (out_scan->edges.len == limit) { + out_scan->has_next_page = true; + if (!amduat_tgk_asl_index_encode_page_token(entry->logseq, + edge->edge_ref, + &out_scan->next_page_token)) { + amduat_reference_free(&edge->edge_ref); + amduat_tgk_edge_body_free(&edge->body); + amduat_tgk_asl_index_edge_array_free(segment_edges, seg_count); + goto cleanup; + } + amduat_reference_free(&edge->edge_ref); + amduat_tgk_edge_body_free(&edge->body); + { + size_t k; + for (k = r + 1u; k < seg_count; ++k) { + amduat_reference_free(&segment_edges[k].edge_ref); + amduat_tgk_edge_body_free(&segment_edges[k].body); + } + } + free(segment_edges); + goto cleanup; + } + { + amduat_tgk_graph_edge_view_t *next = + (amduat_tgk_graph_edge_view_t *)realloc( + out_scan->edges.edges, + (out_scan->edges.len + 1u) * + sizeof(*out_scan->edges.edges)); + if (next == NULL) { + amduat_reference_free(&edge->edge_ref); + amduat_tgk_edge_body_free(&edge->body); + amduat_tgk_asl_index_edge_array_free(segment_edges, seg_count); + goto cleanup; + } + out_scan->edges.edges = next; + } + out_scan->edges.edges[out_scan->edges.len] = *edge; + out_scan->edges.len += 1u; + edge->edge_ref = amduat_reference(0, amduat_octets(NULL, 0u)); + memset(&edge->body, 0, sizeof(edge->body)); + } + amduat_tgk_asl_index_edge_array_free(segment_edges, seg_count); + } + + ok = true; + +cleanup: + amduat_tgk_asl_index_ref_set_free(&visible_refs); + amduat_asl_replay_free(&replay_state); + free(segments); + amduat_enc_asl_log_free(records, record_count); + if (have_token) { + amduat_reference_free(&token.ref); + } + if (!ok) { + amduat_tgk_graph_scan_result_free(out_scan); + } + return ok; +} + +static bool amduat_tgk_store_asl_index_fs_get_config( + void *ctx, + amduat_tgk_store_config_t *out_config) { + amduat_tgk_store_asl_index_fs_t *store = + (amduat_tgk_store_asl_index_fs_t *)ctx; + + if (store == NULL || out_config == NULL) { + return false; + } + *out_config = store->config; + return true; +} + +static bool amduat_tgk_store_asl_index_fs_snapshot_id( + void *ctx, + amduat_tgk_snapshot_id_t *out_id) { + amduat_tgk_store_asl_index_fs_t *store = + (amduat_tgk_store_asl_index_fs_t *)ctx; + amduat_asl_index_state_t state; + + if (store == NULL || out_id == NULL) { + return false; + } + if (!amduat_tgk_asl_index_get_state(store, &state)) { + return false; + } + *out_id = (amduat_tgk_snapshot_id_t)state.log_position; + return true; +} + +static bool amduat_tgk_store_asl_index_fs_snapshot( + void *ctx, + amduat_tgk_store_snapshot_t *out_snapshot) { + amduat_tgk_store_asl_index_fs_t *store = + (amduat_tgk_store_asl_index_fs_t *)ctx; + amduat_tgk_store_asl_index_fs_t *snap; + amduat_asl_index_state_t state; + + if (store == NULL || out_snapshot == NULL) { + return false; + } + if (!amduat_tgk_asl_index_get_state(store, &state)) { + return false; + } + + snap = (amduat_tgk_store_asl_index_fs_t *)calloc(1u, sizeof(*snap)); + if (snap == NULL) { + return false; + } + *snap = *store; + snap->pinned = true; + snap->pinned_state = state; + + amduat_tgk_store_init(&out_snapshot->store, + snap->config, + amduat_tgk_store_asl_index_fs_ops(), + snap); + out_snapshot->release = free; + return true; +} + +static amduat_tgk_graph_error_t amduat_tgk_store_asl_index_fs_resolve_edge( + void *ctx, + amduat_reference_t ref, + amduat_tgk_edge_body_t *out_body) { + amduat_tgk_store_asl_index_fs_t *store = + (amduat_tgk_store_asl_index_fs_t *)ctx; + amduat_asl_index_state_t state; + amduat_artifact_t artifact; + amduat_asl_store_error_t err; + amduat_tgk_graph_error_t edge_err; + + if (store == NULL || out_body == NULL) { + return GS_ERR_UNSUPPORTED; + } + if (!amduat_tgk_asl_index_get_state(store, &state)) { + return GS_ERR_ARTIFACT_ERROR; + } + + artifact = amduat_artifact(amduat_octets(NULL, 0u)); + err = amduat_asl_store_get_indexed(&store->asl_store, + ref, + state, + &artifact); + if (err != AMDUAT_ASL_STORE_OK) { + amduat_artifact_free(&artifact); + return GS_ERR_ARTIFACT_ERROR; + } + edge_err = amduat_tgk_asl_index_decode_edge(store->config, + artifact, + out_body); + amduat_artifact_free(&artifact); + return edge_err; +} + +static bool amduat_tgk_store_asl_index_fs_edges_from( + void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges) { + amduat_tgk_store_asl_index_fs_t *store = + (amduat_tgk_store_asl_index_fs_t *)ctx; + amduat_tgk_graph_scan_result_t scan; + + if (store == NULL || out_edges == NULL) { + return false; + } + if (!amduat_tgk_asl_index_scan_edges_internal(store, + type_filter, + &node, + true, + false, + amduat_octets(NULL, 0u), + false, + SIZE_MAX, + &scan)) { + return false; + } + *out_edges = scan.edges; + free((void *)scan.next_page_token.data); + return true; +} + +static bool amduat_tgk_store_asl_index_fs_edges_to( + void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges) { + amduat_tgk_store_asl_index_fs_t *store = + (amduat_tgk_store_asl_index_fs_t *)ctx; + amduat_tgk_graph_scan_result_t scan; + + if (store == NULL || out_edges == NULL) { + return false; + } + if (!amduat_tgk_asl_index_scan_edges_internal(store, + type_filter, + &node, + false, + true, + amduat_octets(NULL, 0u), + false, + SIZE_MAX, + &scan)) { + return false; + } + *out_edges = scan.edges; + free((void *)scan.next_page_token.data); + return true; +} + +static bool amduat_tgk_store_asl_index_fs_edges_incident( + void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges) { + amduat_tgk_store_asl_index_fs_t *store = + (amduat_tgk_store_asl_index_fs_t *)ctx; + amduat_tgk_graph_scan_result_t scan; + + if (store == NULL || out_edges == NULL) { + return false; + } + if (!amduat_tgk_asl_index_scan_edges_internal(store, + type_filter, + &node, + true, + true, + amduat_octets(NULL, 0u), + false, + SIZE_MAX, + &scan)) { + return false; + } + *out_edges = scan.edges; + free((void *)scan.next_page_token.data); + return true; +} + +static bool amduat_tgk_store_asl_index_fs_scan_edges( + void *ctx, + amduat_tgk_edge_type_filter_t type_filter, + amduat_octets_t page_token, + bool has_page_token, + amduat_tgk_graph_scan_result_t *out_scan) { + amduat_tgk_store_asl_index_fs_t *store = + (amduat_tgk_store_asl_index_fs_t *)ctx; + + return amduat_tgk_asl_index_scan_edges_internal(store, + type_filter, + NULL, + false, + false, + page_token, + has_page_token, + AMDUAT_TGK_ASL_INDEX_SCAN_PAGE_SIZE, + out_scan); +} + +static bool amduat_tgk_store_asl_index_fs_neighbors( + void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_direction_t direction, + amduat_tgk_node_list_t *out_nodes) { + amduat_tgk_store_asl_index_fs_t *store = + (amduat_tgk_store_asl_index_fs_t *)ctx; + amduat_tgk_graph_edge_view_list_t edges; + amduat_reference_t *nodes = NULL; + size_t nodes_len = 0u; + size_t i; + size_t j; + + if (store == NULL || out_nodes == NULL) { + return false; + } + + if (direction == AMDUAT_TGK_GRAPH_DIR_OUT) { + if (!amduat_tgk_store_asl_index_fs_edges_from(ctx, + node, + type_filter, + &edges)) { + return false; + } + } else if (direction == AMDUAT_TGK_GRAPH_DIR_IN) { + if (!amduat_tgk_store_asl_index_fs_edges_to(ctx, + node, + type_filter, + &edges)) { + return false; + } + } else { + if (!amduat_tgk_store_asl_index_fs_edges_incident(ctx, + node, + type_filter, + &edges)) { + return false; + } + } + + for (i = 0; i < edges.len; ++i) { + const amduat_tgk_edge_body_t *body = &edges.edges[i].body; + const amduat_reference_t *list = NULL; + size_t list_len = 0u; + + if (direction == AMDUAT_TGK_GRAPH_DIR_IN) { + list = body->from; + list_len = body->from_len; + } else if (direction == AMDUAT_TGK_GRAPH_DIR_OUT) { + list = body->to; + list_len = body->to_len; + } else { + list = body->from; + list_len = body->from_len; + } + + for (j = 0; j < list_len; ++j) { + size_t k; + bool seen = false; + for (k = 0; k < nodes_len; ++k) { + if (amduat_reference_eq(nodes[k], list[j])) { + seen = true; + break; + } + } + if (seen) { + continue; + } + nodes = (amduat_reference_t *)realloc(nodes, + (nodes_len + 1u) * + sizeof(*nodes)); + if (nodes == NULL) { + amduat_tgk_graph_edge_view_list_free(&edges); + return false; + } + if (!amduat_reference_clone(list[j], &nodes[nodes_len])) { + amduat_tgk_graph_edge_view_list_free(&edges); + return false; + } + nodes_len += 1u; + } + + if (direction == AMDUAT_TGK_GRAPH_DIR_BOTH) { + list = body->to; + list_len = body->to_len; + for (j = 0; j < list_len; ++j) { + size_t k; + bool seen = false; + for (k = 0; k < nodes_len; ++k) { + if (amduat_reference_eq(nodes[k], list[j])) { + seen = true; + break; + } + } + if (seen) { + continue; + } + nodes = (amduat_reference_t *)realloc(nodes, + (nodes_len + 1u) * + sizeof(*nodes)); + if (nodes == NULL) { + amduat_tgk_graph_edge_view_list_free(&edges); + return false; + } + if (!amduat_reference_clone(list[j], &nodes[nodes_len])) { + amduat_tgk_graph_edge_view_list_free(&edges); + return false; + } + nodes_len += 1u; + } + } + } + + amduat_tgk_graph_edge_view_list_free(&edges); + out_nodes->nodes = nodes; + out_nodes->len = nodes_len; + return true; +} + +bool amduat_tgk_store_asl_index_fs_init( + amduat_tgk_store_asl_index_fs_t *store, + amduat_tgk_store_config_t config, + amduat_asl_store_index_fs_t *asl_fs) { + if (store == NULL || asl_fs == NULL) { + return false; + } + memset(store, 0, sizeof(*store)); + store->asl_fs = asl_fs; + store->config = config; + store->pinned = false; + store->use_shards = amduat_tgk_asl_index_use_shards(asl_fs->root_path); + amduat_asl_store_init(&store->asl_store, + asl_fs->config, + amduat_asl_store_index_fs_ops(), + asl_fs); + return true; +} + +void amduat_tgk_store_asl_index_fs_free( + amduat_tgk_store_asl_index_fs_t *store) { + if (store == NULL) { + return; + } + memset(store, 0, sizeof(*store)); +} + +amduat_tgk_store_ops_t amduat_tgk_store_asl_index_fs_ops(void) { + amduat_tgk_store_ops_t ops; + + memset(&ops, 0, sizeof(ops)); + ops.get_config = amduat_tgk_store_asl_index_fs_get_config; + ops.snapshot_id = amduat_tgk_store_asl_index_fs_snapshot_id; + ops.snapshot = amduat_tgk_store_asl_index_fs_snapshot; + ops.resolve_edge = amduat_tgk_store_asl_index_fs_resolve_edge; + ops.edges_from = amduat_tgk_store_asl_index_fs_edges_from; + ops.edges_to = amduat_tgk_store_asl_index_fs_edges_to; + ops.edges_incident = amduat_tgk_store_asl_index_fs_edges_incident; + ops.scan_edges = amduat_tgk_store_asl_index_fs_scan_edges; + ops.neighbors = amduat_tgk_store_asl_index_fs_neighbors; + return ops; +} diff --git a/src/near_core/asl/index_replay.c b/src/near_core/asl/index_replay.c index 6eedbbb..46ee4ba 100644 --- a/src/near_core/asl/index_replay.c +++ b/src/near_core/asl/index_replay.c @@ -149,6 +149,26 @@ static bool amduat_asl_replay_parse_tombstone_lift( return true; } +static bool amduat_asl_replay_parse_snapshot_anchor( + amduat_octets_t payload, + amduat_asl_snapshot_id_t *out_snapshot_id) { + amduat_asl_replay_cursor_t cur; + uint64_t snapshot_id; + + if (payload.len < 8u + 32u || payload.data == NULL || + out_snapshot_id == NULL) { + return false; + } + cur.data = payload.data; + cur.len = payload.len; + cur.offset = 0; + if (!amduat_asl_replay_read_u64_le(&cur, &snapshot_id)) { + return false; + } + *out_snapshot_id = snapshot_id; + return true; +} + static bool amduat_asl_replay_update_segment( amduat_asl_replay_state_t *state, const amduat_asl_segment_seal_t *seal) { @@ -273,6 +293,7 @@ bool amduat_asl_replay_apply_log( amduat_asl_segment_seal_t seal; amduat_reference_t ref; uint64_t tombstone_logseq; + amduat_asl_snapshot_id_t snapshot_id; if (record->logseq > log_position) { break; @@ -303,6 +324,13 @@ bool amduat_asl_replay_apply_log( } amduat_asl_replay_remove_tombstone(state, ref, tombstone_logseq); break; + case AMDUAT_ASL_LOG_RECORD_SNAPSHOT_ANCHOR: + if (!amduat_asl_replay_parse_snapshot_anchor(record->payload, + &snapshot_id)) { + return false; + } + state->state.snapshot_id = snapshot_id; + break; default: break; } diff --git a/src/near_core/enc/asl_core_index.c b/src/near_core/enc/asl_core_index.c index 5fe06ce..3e97f19 100644 --- a/src/near_core/enc/asl_core_index.c +++ b/src/near_core/enc/asl_core_index.c @@ -215,6 +215,9 @@ bool amduat_enc_asl_core_index_encode_v1( segment->header.reserved0 != 0) { return false; } + if (segment->header.snapshot_min > segment->header.snapshot_max) { + return false; + } if (segment->header.segment_visibility > 1) { return false; } @@ -504,6 +507,9 @@ bool amduat_enc_asl_core_index_decode_v1( if (header.flags != 0) { return false; } + if (header.snapshot_min > header.snapshot_max) { + return false; + } legacy_defaults = header.version < AMDUAT_ASL_CORE_INDEX_VERSION; if (!legacy_defaults) { diff --git a/src/near_core/enc/asl_log.c b/src/near_core/enc/asl_log.c index 8185cce..e62eb1a 100644 --- a/src/near_core/enc/asl_log.c +++ b/src/near_core/enc/asl_log.c @@ -257,6 +257,7 @@ bool amduat_enc_asl_log_decode_v1(amduat_octets_t bytes, amduat_asl_log_record_t *records; size_t record_count; size_t record_capacity; + uint64_t last_logseq; if (out_records == NULL || out_count == NULL) { return false; @@ -306,6 +307,7 @@ bool amduat_enc_asl_log_decode_v1(amduat_octets_t bytes, records = NULL; record_count = 0; record_capacity = 0; + last_logseq = 0u; memset(prev_hash, 0, sizeof(prev_hash)); while (cur.offset < cur.len) { @@ -322,6 +324,10 @@ bool amduat_enc_asl_log_decode_v1(amduat_octets_t bytes, amduat_enc_asl_log_free(records, record_count); return false; } + if (record_count != 0u && logseq <= last_logseq) { + amduat_enc_asl_log_free(records, record_count); + return false; + } if (cur.len - cur.offset < payload_len + AMDUAT_ASL_LOG_HASH_LEN) { amduat_enc_asl_log_free(records, record_count); return false; @@ -346,6 +352,7 @@ bool amduat_enc_asl_log_decode_v1(amduat_octets_t bytes, return false; } memcpy(prev_hash, record_hash, AMDUAT_ASL_LOG_HASH_LEN); + last_logseq = logseq; if (amduat_asl_log_is_known_record_type(record_type)) { amduat_asl_log_record_t *slot; diff --git a/src/near_core/enc/fer1_receipt.c b/src/near_core/enc/fer1_receipt.c index 65d5503..b3c0667 100644 --- a/src/near_core/enc/fer1_receipt.c +++ b/src/near_core/enc/fer1_receipt.c @@ -8,6 +8,19 @@ #include #include +enum { + AMDUAT_FER1_TLV_EXECUTOR_FINGERPRINT = 0x0001u, + AMDUAT_FER1_TLV_RUN_ID = 0x0002u, + AMDUAT_FER1_TLV_LOGS = 0x0003u, + AMDUAT_FER1_TLV_LIMITS = 0x0004u, + AMDUAT_FER1_TLV_DETERMINISM = 0x0005u, + AMDUAT_FER1_TLV_SIGNATURE = 0x0006u +}; + +enum { + AMDUAT_FER1_LOGS_MAX = 64 +}; + typedef struct { const uint8_t *data; size_t len; @@ -177,6 +190,14 @@ static bool amduat_write_encoded_ref(uint8_t *buffer, return true; } +static int amduat_log_entry_cmp(const amduat_fer1_log_entry_t *a, + const amduat_fer1_log_entry_t *b) { + if (a->kind != b->kind) { + return a->kind < b->kind ? -1 : 1; + } + return amduat_reference_cmp(a->log_ref, b->log_ref); +} + static bool amduat_read_encoded_ref(amduat_cursor_t *cur, amduat_reference_t *out_ref) { uint32_t ref_len_u32; @@ -231,10 +252,42 @@ void amduat_enc_fer1_receipt_free(amduat_fer1_receipt_t *receipt) { free(receipt->parity); } + if (receipt->has_executor_fingerprint_ref) { + amduat_reference_free(&receipt->executor_fingerprint_ref); + } + if (receipt->has_run_id) { + amduat_octets_free(&receipt->run_id); + } + if (receipt->logs != NULL) { + for (i = 0; i < receipt->logs_len; ++i) { + amduat_reference_free(&receipt->logs[i].log_ref); + amduat_octets_free(&receipt->logs[i].sha256); + } + free(receipt->logs); + } + if (receipt->has_rng_seed) { + amduat_octets_free(&receipt->rng_seed); + } + if (receipt->has_signature) { + amduat_octets_free(&receipt->signature); + } + receipt->executor_refs = NULL; receipt->executor_refs_len = 0; receipt->parity = NULL; receipt->parity_len = 0; + receipt->has_executor_fingerprint_ref = false; + receipt->has_run_id = false; + receipt->run_id = amduat_octets(NULL, 0u); + receipt->has_limits = false; + receipt->logs = NULL; + receipt->logs_len = 0; + receipt->has_determinism = false; + receipt->determinism_level = 0u; + receipt->has_rng_seed = false; + receipt->rng_seed = amduat_octets(NULL, 0u); + receipt->has_signature = false; + receipt->signature = amduat_octets(NULL, 0u); } bool amduat_enc_fer1_receipt_encode_v1( @@ -444,6 +497,431 @@ bool amduat_enc_fer1_receipt_encode_v1( return true; } +bool amduat_enc_fer1_receipt_encode_v1_1( + const amduat_fer1_receipt_t *receipt, + amduat_octets_t *out_bytes) { + size_t total_len = 0; + size_t offset = 0; + size_t ext_len = 0; + uint8_t *buffer; + size_t i; + + if (receipt == NULL || out_bytes == NULL) { + return false; + } + out_bytes->data = NULL; + out_bytes->len = 0; + + if (receipt->fer1_version != AMDUAT_FER1_VERSION_1_1) { + return false; + } + if (receipt->evaluator_id.len != 0 && receipt->evaluator_id.data == NULL) { + return false; + } + if (receipt->executor_refs_len != 0 && receipt->executor_refs == NULL) { + return false; + } + if (receipt->parity_len != 0 && receipt->parity == NULL) { + return false; + } + if (receipt->executor_refs_len != receipt->parity_len) { + return false; + } + if (receipt->evaluator_id.len > UINT32_MAX) { + return false; + } + if (receipt->started_at > receipt->completed_at) { + return false; + } + if (receipt->has_run_id && + receipt->run_id.len != 0 && + receipt->run_id.data == NULL) { + return false; + } + if (receipt->has_run_id && receipt->run_id.len > UINT32_MAX) { + return false; + } + if (receipt->has_rng_seed && + receipt->rng_seed.len != 0 && + receipt->rng_seed.data == NULL) { + return false; + } + if (receipt->has_rng_seed && receipt->rng_seed.len > UINT32_MAX) { + return false; + } + if (receipt->has_signature && + receipt->signature.len != 0 && + receipt->signature.data == NULL) { + return false; + } + if (receipt->has_signature && receipt->signature.len > UINT32_MAX) { + return false; + } + if (receipt->logs_len != 0 && receipt->logs == NULL) { + return false; + } + if (receipt->logs_len > AMDUAT_FER1_LOGS_MAX) { + return false; + } + for (i = 1; i < receipt->executor_refs_len; ++i) { + if (amduat_reference_cmp(receipt->executor_refs[i - 1], + receipt->executor_refs[i]) > 0) { + return false; + } + } + for (i = 0; i < receipt->parity_len; ++i) { + if (!amduat_reference_eq(receipt->parity[i].executor_ref, + receipt->executor_refs[i])) { + return false; + } + if (i > 0 && + amduat_reference_cmp(receipt->parity[i - 1].executor_ref, + receipt->parity[i].executor_ref) > 0) { + return false; + } + } + for (i = 0; i < receipt->logs_len; ++i) { + const amduat_fer1_log_entry_t *entry = &receipt->logs[i]; + if (entry->sha256.len != 0 && entry->sha256.data == NULL) { + return false; + } + if (entry->sha256.len > UINT32_MAX) { + return false; + } + if (i > 0 && + amduat_log_entry_cmp(&receipt->logs[i - 1], entry) > 0) { + return false; + } + } + + { + size_t enc_len; + if (!amduat_add_size(&total_len, 2) || + !amduat_encoded_ref_len(receipt->function_ref, &enc_len) || + !amduat_add_size(&total_len, enc_len) || + !amduat_encoded_ref_len(receipt->input_manifest_ref, &enc_len) || + !amduat_add_size(&total_len, enc_len) || + !amduat_encoded_ref_len(receipt->environment_ref, &enc_len) || + !amduat_add_size(&total_len, enc_len)) { + return false; + } + } + + if (!amduat_add_size(&total_len, 4 + receipt->evaluator_id.len)) { + return false; + } + + { + size_t enc_len; + if (!amduat_encoded_ref_len(receipt->output_ref, &enc_len) || + !amduat_add_size(&total_len, enc_len)) { + return false; + } + } + + if (!amduat_add_size(&total_len, 4)) { + return false; + } + for (i = 0; i < receipt->executor_refs_len; ++i) { + size_t enc_len; + if (!amduat_encoded_ref_len(receipt->executor_refs[i], &enc_len) || + !amduat_add_size(&total_len, enc_len)) { + return false; + } + } + + if (!amduat_add_size(&total_len, 4)) { + return false; + } + for (i = 0; i < receipt->parity_len; ++i) { + size_t enc_len; + const amduat_fer1_parity_entry_t *entry = &receipt->parity[i]; + if (entry->parity_digest.len != 0 && + entry->parity_digest.data == NULL) { + return false; + } + if (entry->parity_digest.len > UINT32_MAX) { + return false; + } + if (!amduat_reference_eq(entry->output_ref, receipt->output_ref)) { + return false; + } + if (!amduat_encoded_ref_len(entry->executor_ref, &enc_len) || + !amduat_add_size(&total_len, enc_len) || + !amduat_encoded_ref_len(entry->output_ref, &enc_len) || + !amduat_add_size(&total_len, enc_len) || + !amduat_add_size(&total_len, 1)) { + return false; + } + if (entry->has_sbom_ref) { + if (!amduat_encoded_ref_len(entry->sbom_ref, &enc_len) || + !amduat_add_size(&total_len, enc_len)) { + return false; + } + } + if (!amduat_add_size(&total_len, 4 + entry->parity_digest.len)) { + return false; + } + } + + if (!amduat_add_size(&total_len, 8 + 8)) { + return false; + } + + if (receipt->has_executor_fingerprint_ref) { + size_t enc_len; + if (!amduat_encoded_ref_len(receipt->executor_fingerprint_ref, &enc_len)) { + return false; + } + if (!amduat_add_size(&ext_len, 2 + 4 + enc_len)) { + return false; + } + } + + if (receipt->has_run_id) { + if (!amduat_add_size(&ext_len, 2 + 4 + 4 + receipt->run_id.len)) { + return false; + } + } + + if (receipt->has_limits) { + if (!amduat_add_size(&ext_len, 2 + 4 + (5u * 8u))) { + return false; + } + } + + if (receipt->logs_len != 0) { + size_t logs_len = 4; + for (i = 0; i < receipt->logs_len; ++i) { + size_t enc_len; + if (!amduat_encoded_ref_len(receipt->logs[i].log_ref, &enc_len)) { + return false; + } + if (!amduat_add_size(&logs_len, 4) || + !amduat_add_size(&logs_len, enc_len) || + !amduat_add_size(&logs_len, 4 + receipt->logs[i].sha256.len)) { + return false; + } + } + if (!amduat_add_size(&ext_len, 2 + 4 + logs_len)) { + return false; + } + } + + if (receipt->has_determinism) { + if (!amduat_add_size(&ext_len, + 2 + 4 + 1 + 4 + receipt->rng_seed.len)) { + return false; + } + } + + if (receipt->has_signature) { + if (!amduat_add_size(&ext_len, 2 + 4 + receipt->signature.len)) { + return false; + } + } + + if (ext_len > UINT32_MAX) { + return false; + } + if (!amduat_add_size(&total_len, 4 + ext_len)) { + return false; + } + + buffer = (uint8_t *)malloc(total_len); + if (buffer == NULL) { + return false; + } + + amduat_store_u16_be(buffer + offset, receipt->fer1_version); + offset += 2; + + if (!amduat_write_encoded_ref(buffer, total_len, &offset, + receipt->function_ref) || + !amduat_write_encoded_ref(buffer, total_len, &offset, + receipt->input_manifest_ref) || + !amduat_write_encoded_ref(buffer, total_len, &offset, + receipt->environment_ref)) { + free(buffer); + return false; + } + + amduat_store_u32_be(buffer + offset, (uint32_t)receipt->evaluator_id.len); + offset += 4; + if (receipt->evaluator_id.len != 0) { + memcpy(buffer + offset, receipt->evaluator_id.data, + receipt->evaluator_id.len); + offset += receipt->evaluator_id.len; + } + + if (!amduat_write_encoded_ref(buffer, total_len, &offset, + receipt->output_ref)) { + free(buffer); + return false; + } + + amduat_store_u32_be(buffer + offset, (uint32_t)receipt->executor_refs_len); + offset += 4; + for (i = 0; i < receipt->executor_refs_len; ++i) { + if (!amduat_write_encoded_ref(buffer, total_len, &offset, + receipt->executor_refs[i])) { + free(buffer); + return false; + } + } + + amduat_store_u32_be(buffer + offset, (uint32_t)receipt->parity_len); + offset += 4; + for (i = 0; i < receipt->parity_len; ++i) { + const amduat_fer1_parity_entry_t *entry = &receipt->parity[i]; + if (!amduat_write_encoded_ref(buffer, total_len, &offset, + entry->executor_ref) || + !amduat_write_encoded_ref(buffer, total_len, &offset, + entry->output_ref)) { + free(buffer); + return false; + } + buffer[offset++] = entry->has_sbom_ref ? 0x01u : 0x00u; + if (entry->has_sbom_ref) { + if (!amduat_write_encoded_ref(buffer, total_len, &offset, + entry->sbom_ref)) { + free(buffer); + return false; + } + } + amduat_store_u32_be(buffer + offset, (uint32_t)entry->parity_digest.len); + offset += 4; + if (entry->parity_digest.len != 0) { + memcpy(buffer + offset, entry->parity_digest.data, + entry->parity_digest.len); + offset += entry->parity_digest.len; + } + } + + amduat_store_u64_be(buffer + offset, receipt->started_at); + offset += 8; + amduat_store_u64_be(buffer + offset, receipt->completed_at); + offset += 8; + + amduat_store_u32_be(buffer + offset, (uint32_t)ext_len); + offset += 4; + + if (receipt->has_executor_fingerprint_ref) { + size_t enc_len; + if (!amduat_encoded_ref_len(receipt->executor_fingerprint_ref, &enc_len)) { + free(buffer); + return false; + } + amduat_store_u16_be(buffer + offset, + AMDUAT_FER1_TLV_EXECUTOR_FINGERPRINT); + offset += 2; + amduat_store_u32_be(buffer + offset, (uint32_t)enc_len); + offset += 4; + if (!amduat_write_encoded_ref(buffer, total_len, &offset, + receipt->executor_fingerprint_ref)) { + free(buffer); + return false; + } + } + + if (receipt->has_run_id) { + amduat_store_u16_be(buffer + offset, AMDUAT_FER1_TLV_RUN_ID); + offset += 2; + amduat_store_u32_be(buffer + offset, + (uint32_t)(4u + receipt->run_id.len)); + offset += 4; + amduat_store_u32_be(buffer + offset, (uint32_t)receipt->run_id.len); + offset += 4; + if (receipt->run_id.len != 0) { + memcpy(buffer + offset, receipt->run_id.data, receipt->run_id.len); + offset += receipt->run_id.len; + } + } + + if (receipt->logs_len != 0) { + size_t logs_len = 4; + for (i = 0; i < receipt->logs_len; ++i) { + size_t enc_len; + if (!amduat_encoded_ref_len(receipt->logs[i].log_ref, &enc_len)) { + free(buffer); + return false; + } + logs_len += 4 + enc_len + 4 + receipt->logs[i].sha256.len; + } + + amduat_store_u16_be(buffer + offset, AMDUAT_FER1_TLV_LOGS); + offset += 2; + amduat_store_u32_be(buffer + offset, (uint32_t)logs_len); + offset += 4; + amduat_store_u32_be(buffer + offset, (uint32_t)receipt->logs_len); + offset += 4; + for (i = 0; i < receipt->logs_len; ++i) { + const amduat_fer1_log_entry_t *entry = &receipt->logs[i]; + amduat_store_u32_be(buffer + offset, entry->kind); + offset += 4; + if (!amduat_write_encoded_ref(buffer, total_len, &offset, + entry->log_ref)) { + free(buffer); + return false; + } + amduat_store_u32_be(buffer + offset, (uint32_t)entry->sha256.len); + offset += 4; + if (entry->sha256.len != 0) { + memcpy(buffer + offset, entry->sha256.data, entry->sha256.len); + offset += entry->sha256.len; + } + } + } + + if (receipt->has_limits) { + amduat_store_u16_be(buffer + offset, AMDUAT_FER1_TLV_LIMITS); + offset += 2; + amduat_store_u32_be(buffer + offset, (uint32_t)(5u * 8u)); + offset += 4; + amduat_store_u64_be(buffer + offset, receipt->limits.cpu_ms); + offset += 8; + amduat_store_u64_be(buffer + offset, receipt->limits.wall_ms); + offset += 8; + amduat_store_u64_be(buffer + offset, receipt->limits.max_rss_kib); + offset += 8; + amduat_store_u64_be(buffer + offset, receipt->limits.io_reads); + offset += 8; + amduat_store_u64_be(buffer + offset, receipt->limits.io_writes); + offset += 8; + } + + if (receipt->has_determinism) { + amduat_store_u16_be(buffer + offset, AMDUAT_FER1_TLV_DETERMINISM); + offset += 2; + amduat_store_u32_be(buffer + offset, + (uint32_t)(1u + 4u + receipt->rng_seed.len)); + offset += 4; + buffer[offset++] = receipt->determinism_level; + amduat_store_u32_be(buffer + offset, (uint32_t)receipt->rng_seed.len); + offset += 4; + if (receipt->rng_seed.len != 0) { + memcpy(buffer + offset, receipt->rng_seed.data, receipt->rng_seed.len); + offset += receipt->rng_seed.len; + } + } + + if (receipt->has_signature) { + amduat_store_u16_be(buffer + offset, AMDUAT_FER1_TLV_SIGNATURE); + offset += 2; + amduat_store_u32_be(buffer + offset, + (uint32_t)receipt->signature.len); + offset += 4; + if (receipt->signature.len != 0) { + memcpy(buffer + offset, receipt->signature.data, receipt->signature.len); + offset += receipt->signature.len; + } + } + + out_bytes->data = buffer; + out_bytes->len = total_len; + return true; +} + bool amduat_enc_fer1_receipt_decode_v1( amduat_octets_t bytes, amduat_fer1_receipt_t *out_receipt) { @@ -627,3 +1105,375 @@ bool amduat_enc_fer1_receipt_decode_v1( return true; } + +bool amduat_enc_fer1_receipt_decode_v1_1( + amduat_octets_t bytes, + amduat_fer1_receipt_t *out_receipt) { + amduat_cursor_t cur; + uint16_t fer1_version; + uint32_t len_u32; + uint32_t executor_count; + uint32_t parity_count; + uint32_t ext_len; + size_t i; + + if (out_receipt == NULL) { + return false; + } + if (bytes.len != 0 && bytes.data == NULL) { + return false; + } + + memset(out_receipt, 0, sizeof(*out_receipt)); + + cur.data = bytes.data; + cur.len = bytes.len; + cur.offset = 0; + + if (!amduat_read_u16(&cur, &fer1_version)) { + return false; + } + if (fer1_version != AMDUAT_FER1_VERSION_1_1) { + return false; + } + out_receipt->fer1_version = fer1_version; + + if (!amduat_read_encoded_ref(&cur, &out_receipt->function_ref) || + !amduat_read_encoded_ref(&cur, &out_receipt->input_manifest_ref) || + !amduat_read_encoded_ref(&cur, &out_receipt->environment_ref)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + + if (!amduat_read_u32(&cur, &len_u32)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (cur.len - cur.offset < len_u32) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (len_u32 != 0) { + amduat_octets_t src = amduat_octets(cur.data + cur.offset, len_u32); + if (!amduat_octets_clone(src, &out_receipt->evaluator_id)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + cur.offset += len_u32; + } + + if (!amduat_read_encoded_ref(&cur, &out_receipt->output_ref)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + + if (!amduat_read_u32(&cur, &executor_count)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (executor_count != 0) { + if (executor_count > SIZE_MAX / sizeof(amduat_reference_t)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + out_receipt->executor_refs = + (amduat_reference_t *)calloc(executor_count, + sizeof(amduat_reference_t)); + if (out_receipt->executor_refs == NULL) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + out_receipt->executor_refs_len = executor_count; + for (i = 0; i < out_receipt->executor_refs_len; ++i) { + if (!amduat_read_encoded_ref(&cur, &out_receipt->executor_refs[i])) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + for (i = 1; i < out_receipt->executor_refs_len; ++i) { + if (amduat_reference_cmp(out_receipt->executor_refs[i - 1], + out_receipt->executor_refs[i]) > 0) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + + if (!amduat_read_u32(&cur, &parity_count)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (parity_count > SIZE_MAX / sizeof(amduat_fer1_parity_entry_t)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (parity_count != 0) { + out_receipt->parity = + (amduat_fer1_parity_entry_t *)calloc(parity_count, + sizeof(amduat_fer1_parity_entry_t)); + if (out_receipt->parity == NULL) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + out_receipt->parity_len = parity_count; + for (i = 0; i < out_receipt->parity_len; ++i) { + amduat_fer1_parity_entry_t *entry = &out_receipt->parity[i]; + uint8_t has_sbom_ref; + + if (!amduat_read_encoded_ref(&cur, &entry->executor_ref) || + !amduat_read_encoded_ref(&cur, &entry->output_ref)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (!amduat_read_u8(&cur, &has_sbom_ref)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (has_sbom_ref != 0x00u && has_sbom_ref != 0x01u) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + entry->has_sbom_ref = has_sbom_ref == 0x01u; + if (entry->has_sbom_ref) { + if (!amduat_read_encoded_ref(&cur, &entry->sbom_ref)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + if (!amduat_read_u32(&cur, &len_u32)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (cur.len - cur.offset < len_u32) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (len_u32 != 0) { + amduat_octets_t src = amduat_octets(cur.data + cur.offset, len_u32); + if (!amduat_octets_clone(src, &entry->parity_digest)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + cur.offset += len_u32; + } + } + for (i = 0; i < out_receipt->parity_len; ++i) { + if (!amduat_reference_eq(out_receipt->parity[i].executor_ref, + out_receipt->executor_refs[i])) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (i > 0 && + amduat_reference_cmp(out_receipt->parity[i - 1].executor_ref, + out_receipt->parity[i].executor_ref) > 0) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (!amduat_reference_eq(out_receipt->parity[i].output_ref, + out_receipt->output_ref)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + + if (!amduat_read_u64(&cur, &out_receipt->started_at) || + !amduat_read_u64(&cur, &out_receipt->completed_at)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (out_receipt->started_at > out_receipt->completed_at) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + + if (!amduat_read_u32(&cur, &ext_len)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (cur.len - cur.offset < ext_len) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + + { + size_t end_offset = cur.offset + ext_len; + while (cur.offset < end_offset) { + uint16_t tag; + uint32_t tag_len; + amduat_cursor_t tlv_cur; + + if (!amduat_read_u16(&cur, &tag) || + !amduat_read_u32(&cur, &tag_len)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (tag_len > end_offset - cur.offset) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + + tlv_cur.data = cur.data + cur.offset; + tlv_cur.len = tag_len; + tlv_cur.offset = 0; + + switch (tag) { + case AMDUAT_FER1_TLV_EXECUTOR_FINGERPRINT: + if (out_receipt->has_executor_fingerprint_ref || + !amduat_read_encoded_ref(&tlv_cur, + &out_receipt->executor_fingerprint_ref) || + tlv_cur.offset != tlv_cur.len) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + out_receipt->has_executor_fingerprint_ref = true; + break; + case AMDUAT_FER1_TLV_RUN_ID: { + uint32_t run_len; + if (out_receipt->has_run_id || + !amduat_read_u32(&tlv_cur, &run_len) || + tlv_cur.len - tlv_cur.offset < run_len || + tlv_cur.offset + run_len != tlv_cur.len) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (run_len != 0) { + amduat_octets_t src = + amduat_octets(tlv_cur.data + tlv_cur.offset, run_len); + if (!amduat_octets_clone(src, &out_receipt->run_id)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + out_receipt->has_run_id = true; + break; + } + case AMDUAT_FER1_TLV_LOGS: { + uint32_t count; + if (!amduat_read_u32(&tlv_cur, &count) || + count > AMDUAT_FER1_LOGS_MAX || + count > SIZE_MAX / sizeof(amduat_fer1_log_entry_t)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (count != 0) { + out_receipt->logs = + (amduat_fer1_log_entry_t *)calloc( + count, sizeof(amduat_fer1_log_entry_t)); + if (out_receipt->logs == NULL) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + out_receipt->logs_len = count; + for (i = 0; i < out_receipt->logs_len; ++i) { + amduat_fer1_log_entry_t *entry = &out_receipt->logs[i]; + uint32_t sha_len; + if (!amduat_read_u32(&tlv_cur, &entry->kind) || + !amduat_read_encoded_ref(&tlv_cur, &entry->log_ref) || + !amduat_read_u32(&tlv_cur, &sha_len) || + tlv_cur.len - tlv_cur.offset < sha_len) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (sha_len != 0) { + amduat_octets_t src = + amduat_octets(tlv_cur.data + tlv_cur.offset, sha_len); + if (!amduat_octets_clone(src, &entry->sha256)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + tlv_cur.offset += sha_len; + if (i > 0 && + amduat_log_entry_cmp(&out_receipt->logs[i - 1], + entry) > 0) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + if (tlv_cur.offset != tlv_cur.len) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + break; + } + case AMDUAT_FER1_TLV_LIMITS: + if (out_receipt->has_limits || + tlv_cur.len != 5u * 8u || + !amduat_read_u64(&tlv_cur, &out_receipt->limits.cpu_ms) || + !amduat_read_u64(&tlv_cur, &out_receipt->limits.wall_ms) || + !amduat_read_u64(&tlv_cur, &out_receipt->limits.max_rss_kib) || + !amduat_read_u64(&tlv_cur, &out_receipt->limits.io_reads) || + !amduat_read_u64(&tlv_cur, &out_receipt->limits.io_writes) || + tlv_cur.offset != tlv_cur.len) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + out_receipt->has_limits = true; + break; + case AMDUAT_FER1_TLV_DETERMINISM: { + uint8_t level; + uint32_t seed_len; + if (out_receipt->has_determinism || + !amduat_read_u8(&tlv_cur, &level) || + !amduat_read_u32(&tlv_cur, &seed_len) || + tlv_cur.len - tlv_cur.offset < seed_len) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (seed_len != 0) { + amduat_octets_t src = + amduat_octets(tlv_cur.data + tlv_cur.offset, seed_len); + if (!amduat_octets_clone(src, &out_receipt->rng_seed)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + out_receipt->has_rng_seed = true; + } + tlv_cur.offset += seed_len; + if (tlv_cur.offset != tlv_cur.len) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + out_receipt->determinism_level = level; + out_receipt->has_determinism = true; + break; + } + case AMDUAT_FER1_TLV_SIGNATURE: { + if (out_receipt->has_signature) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + if (tlv_cur.len != 0) { + amduat_octets_t src = + amduat_octets(tlv_cur.data + tlv_cur.offset, tlv_cur.len); + if (!amduat_octets_clone(src, &out_receipt->signature)) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + tlv_cur.offset = tlv_cur.len; + out_receipt->has_signature = true; + break; + } + default: + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + + cur.offset += tag_len; + } + if (cur.offset != end_offset) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + } + + if (cur.offset != cur.len) { + amduat_enc_fer1_receipt_free(out_receipt); + return false; + } + + return true; +} diff --git a/src/near_core/fer/receipt.c b/src/near_core/fer/receipt.c index bc887b9..fedc04b 100644 --- a/src/near_core/fer/receipt.c +++ b/src/near_core/fer/receipt.c @@ -1,11 +1,13 @@ #include "amduat/fer/receipt.h" #include "amduat/enc/fer1_receipt.h" +#include "amduat/pel/run.h" #include -bool amduat_fer1_receipt_from_pel_result( +static bool amduat_fer1_receipt_from_pel_result_with_output_ref( const amduat_pel_surface_execution_result_t *pel_result, + amduat_reference_t output_ref, amduat_reference_t input_manifest_ref, amduat_reference_t environment_ref, amduat_octets_t evaluator_id, @@ -29,9 +31,6 @@ bool amduat_fer1_receipt_from_pel_result( if (pel_result == NULL) { return false; } - if (pel_result->output_refs_len != 1 || pel_result->output_refs == NULL) { - return false; - } if (evaluator_id.len != 0 && evaluator_id.data == NULL) { return false; } @@ -45,7 +44,7 @@ bool amduat_fer1_receipt_from_pel_result( receipt.input_manifest_ref = input_manifest_ref; receipt.environment_ref = environment_ref; receipt.evaluator_id = evaluator_id; - receipt.output_ref = pel_result->output_refs[0]; + receipt.output_ref = output_ref; receipt.started_at = started_at; receipt.completed_at = completed_at; @@ -73,3 +72,206 @@ bool amduat_fer1_receipt_from_pel_result( amduat_type_tag(AMDUAT_TYPE_TAG_FER1_RECEIPT_1)); return true; } + +bool amduat_fer1_receipt_from_pel_result( + const amduat_pel_surface_execution_result_t *pel_result, + amduat_reference_t input_manifest_ref, + amduat_reference_t environment_ref, + amduat_octets_t evaluator_id, + amduat_reference_t executor_ref, + bool has_sbom_ref, + amduat_reference_t sbom_ref, + amduat_octets_t parity_digest, + uint64_t started_at, + uint64_t completed_at, + amduat_artifact_t *out_artifact) { + if (pel_result == NULL) { + return false; + } + if (pel_result->output_refs_len != 1 || pel_result->output_refs == NULL) { + return false; + } + + return amduat_fer1_receipt_from_pel_result_with_output_ref( + pel_result, + pel_result->output_refs[0], + input_manifest_ref, + environment_ref, + evaluator_id, + executor_ref, + has_sbom_ref, + sbom_ref, + parity_digest, + started_at, + completed_at, + out_artifact); +} + +bool amduat_fer1_receipt_from_pel_run( + const amduat_pel_run_result_t *pel_run, + amduat_reference_t input_manifest_ref, + amduat_reference_t environment_ref, + amduat_octets_t evaluator_id, + amduat_reference_t executor_ref, + bool has_sbom_ref, + amduat_reference_t sbom_ref, + amduat_octets_t parity_digest, + uint64_t started_at, + uint64_t completed_at, + amduat_artifact_t *out_artifact) { + amduat_reference_t output_ref; + + if (pel_run == NULL || !pel_run->has_result_value) { + return false; + } + + if (pel_run->output_refs_len == 1 && pel_run->output_refs != NULL) { + output_ref = pel_run->output_refs[0]; + } else if (pel_run->output_refs_len == 0) { + output_ref = pel_run->result_ref; + } else { + return false; + } + + return amduat_fer1_receipt_from_pel_result_with_output_ref( + &pel_run->result_value, + output_ref, + input_manifest_ref, + environment_ref, + evaluator_id, + executor_ref, + has_sbom_ref, + sbom_ref, + parity_digest, + started_at, + completed_at, + out_artifact); +} + +bool amduat_fer1_receipt_from_pel_run_v1_1( + const amduat_pel_run_result_t *pel_run, + amduat_reference_t input_manifest_ref, + amduat_reference_t environment_ref, + amduat_octets_t evaluator_id, + amduat_reference_t executor_ref, + bool has_sbom_ref, + amduat_reference_t sbom_ref, + amduat_octets_t parity_digest, + uint64_t started_at, + uint64_t completed_at, + bool has_executor_fingerprint_ref, + amduat_reference_t executor_fingerprint_ref, + bool has_run_id, + amduat_octets_t run_id, + bool has_limits, + amduat_fer1_limits_t limits, + const amduat_fer1_log_entry_t *logs, + size_t logs_len, + bool has_determinism, + uint8_t determinism_level, + bool has_rng_seed, + amduat_octets_t rng_seed, + bool has_signature, + amduat_octets_t signature, + amduat_artifact_t *out_artifact) { + amduat_fer1_receipt_t receipt; + amduat_fer1_parity_entry_t parity; + amduat_reference_t executor_refs[1]; + amduat_reference_t output_ref; + amduat_octets_t receipt_bytes; + + if (out_artifact == NULL) { + return false; + } + *out_artifact = amduat_artifact(amduat_octets(NULL, 0u)); + + if (pel_run == NULL || !pel_run->has_result_value) { + return false; + } + if (pel_run->output_refs_len == 1 && pel_run->output_refs != NULL) { + output_ref = pel_run->output_refs[0]; + } else if (pel_run->output_refs_len == 0) { + output_ref = pel_run->result_ref; + } else { + return false; + } + if (evaluator_id.len != 0 && evaluator_id.data == NULL) { + return false; + } + if (parity_digest.len != 0 && parity_digest.data == NULL) { + return false; + } + if (has_run_id && run_id.len != 0 && run_id.data == NULL) { + return false; + } + if (has_rng_seed && rng_seed.len != 0 && rng_seed.data == NULL) { + return false; + } + if (has_signature && signature.len != 0 && signature.data == NULL) { + return false; + } + if (logs_len != 0 && logs == NULL) { + return false; + } + + memset(&receipt, 0, sizeof(receipt)); + receipt.fer1_version = AMDUAT_FER1_VERSION_1_1; + receipt.function_ref = pel_run->result_value.program_ref; + receipt.input_manifest_ref = input_manifest_ref; + receipt.environment_ref = environment_ref; + receipt.evaluator_id = evaluator_id; + receipt.output_ref = output_ref; + receipt.started_at = started_at; + receipt.completed_at = completed_at; + + executor_refs[0] = executor_ref; + receipt.executor_refs = executor_refs; + receipt.executor_refs_len = 1; + + memset(&parity, 0, sizeof(parity)); + parity.executor_ref = executor_ref; + parity.output_ref = receipt.output_ref; + parity.has_sbom_ref = has_sbom_ref; + if (has_sbom_ref) { + parity.sbom_ref = sbom_ref; + } + parity.parity_digest = parity_digest; + receipt.parity = &parity; + receipt.parity_len = 1; + + receipt.has_executor_fingerprint_ref = has_executor_fingerprint_ref; + if (has_executor_fingerprint_ref) { + receipt.executor_fingerprint_ref = executor_fingerprint_ref; + } + receipt.has_run_id = has_run_id; + if (has_run_id) { + receipt.run_id = run_id; + } + receipt.has_limits = has_limits; + if (has_limits) { + receipt.limits = limits; + } + receipt.logs = (amduat_fer1_log_entry_t *)logs; + receipt.logs_len = logs_len; + receipt.has_determinism = has_determinism; + if (has_determinism) { + receipt.determinism_level = determinism_level; + } + receipt.has_rng_seed = has_rng_seed; + if (has_rng_seed) { + receipt.rng_seed = rng_seed; + } + receipt.has_signature = has_signature; + if (has_signature) { + receipt.signature = signature; + } + + if (!amduat_enc_fer1_receipt_encode_v1_1(&receipt, &receipt_bytes)) { + return false; + } + + *out_artifact = amduat_artifact_with_type( + amduat_octets(receipt_bytes.data, receipt_bytes.len), + amduat_type_tag(AMDUAT_TYPE_TAG_FER1_RECEIPT_1)); + return true; +} diff --git a/tests/asl/test_asl_index_replay.c b/tests/asl/test_asl_index_replay.c index ff03d75..c18d0a8 100644 --- a/tests/asl/test_asl_index_replay.c +++ b/tests/asl/test_asl_index_replay.c @@ -113,6 +113,18 @@ static size_t build_segment_seal_payload(uint8_t *out, return 8 + 32; } +static size_t build_snapshot_anchor_payload(uint8_t *out, + size_t out_cap, + uint64_t snapshot_id, + const uint8_t root_hash[32]) { + if (out_cap < 8 + 32) { + return 0; + } + store_u64_le(out, snapshot_id); + memcpy(out + 8, root_hash, 32); + return 8 + 32; +} + static bool join_path(const char *base, const char *segment, char **out_path) { size_t base_len; size_t seg_len; @@ -657,9 +669,11 @@ static int test_tombstone_lift_boundary(void) { amduat_reference_t ref; uint8_t hash[32]; uint8_t seal_payload[8 + 32]; + uint8_t anchor_payload[8 + 32]; uint8_t tombstone_payload[4 + 2 + 2 + 32 + 4 + 4]; uint8_t lift_payload[4 + 2 + 2 + 32 + 8]; size_t seal_len; + size_t anchor_len; size_t tombstone_len; size_t lift_len; amduat_asl_log_record_t records[4]; @@ -693,6 +707,10 @@ static int test_tombstone_lift_boundary(void) { sizeof(seal_payload), 5, hash); + anchor_len = build_snapshot_anchor_payload(anchor_payload, + sizeof(anchor_payload), + 5, + hash); tombstone_len = build_tombstone_payload(tombstone_payload, sizeof(tombstone_payload), ref.hash_id, @@ -704,7 +722,8 @@ static int test_tombstone_lift_boundary(void) { ref.digest.data, ref.digest.len, 20); - if (seal_len == 0 || tombstone_len == 0 || lift_len == 0) { + if (seal_len == 0 || anchor_len == 0 || tombstone_len == 0 || + lift_len == 0) { fprintf(stderr, "log payload build failed\n"); goto cleanup; } @@ -718,7 +737,7 @@ static int test_tombstone_lift_boundary(void) { records[1].payload = amduat_octets(tombstone_payload, tombstone_len); records[2].logseq = 30; records[2].record_type = AMDUAT_ASL_LOG_RECORD_SNAPSHOT_ANCHOR; - records[2].payload = amduat_octets(NULL, 0u); + records[2].payload = amduat_octets(anchor_payload, anchor_len); records[3].logseq = 40; records[3].record_type = AMDUAT_ASL_LOG_RECORD_TOMBSTONE_LIFT; records[3].payload = amduat_octets(lift_payload, lift_len); diff --git a/tests/enc/test_fer1_receipt.c b/tests/enc/test_fer1_receipt.c index c4d3080..bc63663 100644 --- a/tests/enc/test_fer1_receipt.c +++ b/tests/enc/test_fer1_receipt.c @@ -1,5 +1,6 @@ #include "amduat/enc/fer1_receipt.h" #include "amduat/fer/receipt.h" +#include "amduat/pel/run.h" #include #include @@ -102,6 +103,7 @@ static bool bytes_equal(amduat_octets_t bytes, static int test_receipt_round_trip(void) { amduat_fer1_receipt_t receipt; amduat_octets_t encoded; + amduat_octets_t mutated; amduat_fer1_receipt_t decoded; amduat_reference_t executor_refs[2]; amduat_fer1_parity_entry_t parity[2]; @@ -260,6 +262,492 @@ static int test_receipt_helper(void) { return 0; } +static int test_receipt_helper_failed_run(void) { + amduat_pel_run_result_t pel_run; + amduat_artifact_t artifact; + amduat_fer1_receipt_t decoded; + uint8_t f0[32], i0[32], e0[32], r0[32]; + uint8_t ex0[32]; + int exit_code = 1; + + memset(&pel_run, 0, sizeof(pel_run)); + pel_run.result_ref = make_ref(0x77, r0); + pel_run.output_refs = NULL; + pel_run.output_refs_len = 0; + pel_run.has_result_value = true; + pel_run.result_value.pel1_version = 1; + pel_run.result_value.program_ref = make_ref(0x11, f0); + + if (!amduat_fer1_receipt_from_pel_run( + &pel_run, + make_ref(0x22, i0), + make_ref(0x33, e0), + amduat_octets("tester", 6), + make_ref(0x50, ex0), + false, + amduat_reference(0, amduat_octets(NULL, 0)), + amduat_octets(NULL, 0), + 10, + 20, + &artifact)) { + fprintf(stderr, "failed run helper failed\n"); + return exit_code; + } + + if (!amduat_enc_fer1_receipt_decode_v1(artifact.bytes, &decoded)) { + fprintf(stderr, "failed run helper decode failed\n"); + amduat_artifact_free(&artifact); + return exit_code; + } + + if (!amduat_reference_eq(decoded.output_ref, pel_run.result_ref) || + !amduat_reference_eq(decoded.function_ref, + pel_run.result_value.program_ref)) { + fprintf(stderr, "failed run helper decoded refs mismatch\n"); + goto cleanup_decoded; + } + + exit_code = 0; + +cleanup_decoded: + amduat_enc_fer1_receipt_free(&decoded); + amduat_artifact_free(&artifact); + return exit_code; +} + +static int test_receipt_round_trip_v1_1(void) { + amduat_fer1_receipt_t receipt; + amduat_octets_t encoded; + amduat_fer1_receipt_t decoded; + amduat_octets_t mutated; + amduat_reference_t executor_refs[1]; + amduat_fer1_parity_entry_t parity[1]; + amduat_fer1_log_entry_t logs[2]; + uint8_t f0[32], i0[32], e0[32], o0[32]; + uint8_t ex0[32], fp0[32], lr0[32], lr1[32]; + uint8_t digest0[] = {0xaa, 0xbb, 0xcc}; + uint8_t run_id[] = {0x01, 0x02, 0x03, 0x04}; + uint8_t rng_seed[] = {0x09, 0x08, 0x07}; + uint8_t signature[] = {0xde, 0xad, 0xbe, 0xef}; + int exit_code = 1; + + memset(&receipt, 0, sizeof(receipt)); + receipt.fer1_version = AMDUAT_FER1_VERSION_1_1; + receipt.function_ref = make_ref(0x11, f0); + receipt.input_manifest_ref = make_ref(0x22, i0); + receipt.environment_ref = make_ref(0x33, e0); + receipt.evaluator_id = amduat_octets("tester", 6); + receipt.output_ref = make_ref(0x44, o0); + receipt.started_at = 10; + receipt.completed_at = 20; + + executor_refs[0] = make_ref(0x50, ex0); + receipt.executor_refs = executor_refs; + receipt.executor_refs_len = 1; + + memset(parity, 0, sizeof(parity)); + parity[0].executor_ref = executor_refs[0]; + parity[0].output_ref = receipt.output_ref; + parity[0].has_sbom_ref = false; + parity[0].parity_digest = amduat_octets(digest0, sizeof(digest0)); + receipt.parity = parity; + receipt.parity_len = 1; + + receipt.has_executor_fingerprint_ref = true; + receipt.executor_fingerprint_ref = make_ref(0x66, fp0); + receipt.has_run_id = true; + receipt.run_id = amduat_octets(run_id, sizeof(run_id)); + receipt.has_limits = true; + receipt.limits.cpu_ms = 1; + receipt.limits.wall_ms = 2; + receipt.limits.max_rss_kib = 3; + receipt.limits.io_reads = 4; + receipt.limits.io_writes = 5; + + memset(logs, 0, sizeof(logs)); + logs[0].kind = 1; + logs[0].log_ref = make_ref(0x70, lr0); + logs[0].sha256 = amduat_octets(digest0, sizeof(digest0)); + logs[1].kind = 2; + logs[1].log_ref = make_ref(0x71, lr1); + logs[1].sha256 = amduat_octets(NULL, 0); + receipt.logs = logs; + receipt.logs_len = 2; + receipt.has_determinism = true; + receipt.determinism_level = 2; + receipt.has_rng_seed = true; + receipt.rng_seed = amduat_octets(rng_seed, sizeof(rng_seed)); + receipt.has_signature = true; + receipt.signature = amduat_octets(signature, sizeof(signature)); + + if (!amduat_enc_fer1_receipt_encode_v1_1(&receipt, &encoded)) { + fprintf(stderr, "encode v1.1 failed\n"); + return exit_code; + } + + if (!amduat_enc_fer1_receipt_decode_v1_1(encoded, &decoded)) { + fprintf(stderr, "decode v1.1 failed\n"); + goto cleanup; + } + + if (decoded.fer1_version != AMDUAT_FER1_VERSION_1_1 || + !amduat_reference_eq(decoded.function_ref, receipt.function_ref) || + !amduat_reference_eq(decoded.input_manifest_ref, + receipt.input_manifest_ref) || + !amduat_reference_eq(decoded.environment_ref, + receipt.environment_ref) || + !amduat_reference_eq(decoded.output_ref, receipt.output_ref)) { + fprintf(stderr, "decoded v1.1 refs mismatch\n"); + goto cleanup_decoded; + } + + if (!decoded.has_executor_fingerprint_ref || + !amduat_reference_eq(decoded.executor_fingerprint_ref, + receipt.executor_fingerprint_ref) || + !decoded.has_run_id || + !amduat_octets_eq(decoded.run_id, receipt.run_id) || + !decoded.has_determinism || + decoded.determinism_level != receipt.determinism_level || + !decoded.has_rng_seed || + !amduat_octets_eq(decoded.rng_seed, receipt.rng_seed) || + !decoded.has_signature || + !amduat_octets_eq(decoded.signature, receipt.signature) || + !decoded.has_limits || + decoded.limits.cpu_ms != receipt.limits.cpu_ms || + decoded.limits.wall_ms != receipt.limits.wall_ms || + decoded.limits.max_rss_kib != receipt.limits.max_rss_kib || + decoded.limits.io_reads != receipt.limits.io_reads || + decoded.limits.io_writes != receipt.limits.io_writes) { + fprintf(stderr, "decoded v1.1 fields mismatch\n"); + goto cleanup_decoded; + } + + if (decoded.logs_len != receipt.logs_len || + !amduat_reference_eq(decoded.logs[0].log_ref, logs[0].log_ref) || + decoded.logs[0].kind != logs[0].kind || + !amduat_octets_eq(decoded.logs[0].sha256, logs[0].sha256) || + !amduat_reference_eq(decoded.logs[1].log_ref, logs[1].log_ref) || + decoded.logs[1].kind != logs[1].kind || + decoded.logs[1].sha256.len != 0) { + fprintf(stderr, "decoded v1.1 logs mismatch\n"); + goto cleanup_decoded; + } + + exit_code = 0; + +cleanup_decoded: + amduat_enc_fer1_receipt_free(&decoded); +cleanup: + free((void *)encoded.data); + return exit_code; +} + +static int test_receipt_v1_1_reject_duplicate_tag(void) { + amduat_fer1_receipt_t receipt; + amduat_octets_t encoded; + amduat_fer1_receipt_t decoded; + amduat_octets_t mutated; + amduat_reference_t executor_refs[1]; + amduat_fer1_parity_entry_t parity[1]; + amduat_fer1_log_entry_t logs[1]; + uint8_t f0[32], i0[32], e0[32], o0[32]; + uint8_t ex0[32], fp0[32], lr0[32]; + uint8_t digest0[] = {0xaa, 0xbb, 0xcc}; + uint8_t run_id[] = {0x01, 0x02, 0x03, 0x04}; + size_t offset = 0; + size_t ext_len = 0; + size_t ext_offset = 0; + size_t tlv_offset = 0; + uint32_t len_u32 = 0; + size_t count = 0; + size_t i = 0; + int exit_code = 1; + + memset(&receipt, 0, sizeof(receipt)); + receipt.fer1_version = AMDUAT_FER1_VERSION_1_1; + receipt.function_ref = make_ref(0x11, f0); + receipt.input_manifest_ref = make_ref(0x22, i0); + receipt.environment_ref = make_ref(0x33, e0); + receipt.evaluator_id = amduat_octets("tester", 6); + receipt.output_ref = make_ref(0x44, o0); + receipt.started_at = 10; + receipt.completed_at = 20; + + executor_refs[0] = make_ref(0x50, ex0); + receipt.executor_refs = executor_refs; + receipt.executor_refs_len = 1; + + memset(parity, 0, sizeof(parity)); + parity[0].executor_ref = executor_refs[0]; + parity[0].output_ref = receipt.output_ref; + parity[0].has_sbom_ref = false; + parity[0].parity_digest = amduat_octets(digest0, sizeof(digest0)); + receipt.parity = parity; + receipt.parity_len = 1; + + receipt.has_executor_fingerprint_ref = true; + receipt.executor_fingerprint_ref = make_ref(0x66, fp0); + receipt.has_run_id = true; + receipt.run_id = amduat_octets(run_id, sizeof(run_id)); + receipt.has_limits = true; + receipt.limits.cpu_ms = 1; + receipt.limits.wall_ms = 2; + receipt.limits.max_rss_kib = 3; + receipt.limits.io_reads = 4; + receipt.limits.io_writes = 5; + + memset(logs, 0, sizeof(logs)); + logs[0].kind = 1; + logs[0].log_ref = make_ref(0x70, lr0); + logs[0].sha256 = amduat_octets(digest0, sizeof(digest0)); + receipt.logs = logs; + receipt.logs_len = 1; + + if (!amduat_enc_fer1_receipt_encode_v1_1(&receipt, &encoded)) { + fprintf(stderr, "encode v1.1 failed\n"); + return exit_code; + } + + if (encoded.len < 2) { + fprintf(stderr, "encoded v1.1 too short\n"); + goto cleanup; + } + + mutated = amduat_octets(NULL, 0u); + if (encoded.len != 0) { + uint8_t *buffer = (uint8_t *)malloc(encoded.len); + if (buffer == NULL) { + fprintf(stderr, "encoded v1.1 alloc failed\n"); + goto cleanup; + } + memcpy(buffer, encoded.data, encoded.len); + mutated = amduat_octets(buffer, encoded.len); + } + + if (mutated.data == NULL || mutated.len != encoded.len) { + fprintf(stderr, "encoded v1.1 clone failed\n"); + goto cleanup; + } + + if (encoded.len - offset < 2) { + fprintf(stderr, "encoded v1.1 header too short\n"); + goto cleanup; + } + offset += 2; + + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 function_ref missing\n"); + goto cleanup; + } + len_u32 = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + len_u32; + if (offset > encoded.len) { + fprintf(stderr, "encoded v1.1 function_ref truncated\n"); + goto cleanup; + } + + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 input_manifest_ref missing\n"); + goto cleanup; + } + len_u32 = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + len_u32; + if (offset > encoded.len) { + fprintf(stderr, "encoded v1.1 input_manifest_ref truncated\n"); + goto cleanup; + } + + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 environment_ref missing\n"); + goto cleanup; + } + len_u32 = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + len_u32; + if (offset > encoded.len) { + fprintf(stderr, "encoded v1.1 environment_ref truncated\n"); + goto cleanup; + } + + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 evaluator_id missing\n"); + goto cleanup; + } + len_u32 = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + len_u32; + if (offset > encoded.len) { + fprintf(stderr, "encoded v1.1 evaluator_id truncated\n"); + goto cleanup; + } + + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 output_ref missing\n"); + goto cleanup; + } + len_u32 = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + len_u32; + if (offset > encoded.len) { + fprintf(stderr, "encoded v1.1 output_ref truncated\n"); + goto cleanup; + } + + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 executor_count missing\n"); + goto cleanup; + } + len_u32 = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4; + count = (size_t)len_u32; + for (i = 0; i < count; ++i) { + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 executor_ref missing\n"); + goto cleanup; + } + uint32_t ref_len = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + ref_len; + if (offset > encoded.len) { + fprintf(stderr, "encoded v1.1 executor_ref truncated\n"); + goto cleanup; + } + } + + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 parity_count missing\n"); + goto cleanup; + } + len_u32 = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4; + count = (size_t)len_u32; + for (i = 0; i < count; ++i) { + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 parity executor_ref missing\n"); + goto cleanup; + } + uint32_t ref_len = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + ref_len; + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 parity output_ref missing\n"); + goto cleanup; + } + ref_len = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + ref_len; + if (offset >= encoded.len) { + fprintf(stderr, "encoded v1.1 parity truncated\n"); + goto cleanup; + } + if (encoded.data[offset] == 0x01u) { + offset += 1; + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 parity sbom missing\n"); + goto cleanup; + } + ref_len = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + ref_len; + } else { + offset += 1; + } + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 parity digest missing\n"); + goto cleanup; + } + uint32_t digest_len = (uint32_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + offset += 4 + digest_len; + if (offset > encoded.len) { + fprintf(stderr, "encoded v1.1 parity digest truncated\n"); + goto cleanup; + } + } + + if (offset + 16 > encoded.len) { + fprintf(stderr, "encoded v1.1 timestamps missing\n"); + goto cleanup; + } + offset += 16; + + if (offset + 4 > encoded.len) { + fprintf(stderr, "encoded v1.1 ext_len missing\n"); + goto cleanup; + } + ext_len = (size_t)((encoded.data[offset] << 24) | + (encoded.data[offset + 1] << 16) | + (encoded.data[offset + 2] << 8) | + encoded.data[offset + 3]); + ext_offset = offset + 4; + if (ext_offset + ext_len > encoded.len) { + fprintf(stderr, "encoded v1.1 ext payload truncated\n"); + goto cleanup; + } + + tlv_offset = ext_offset; + if (tlv_offset + 6 > encoded.len) { + fprintf(stderr, "encoded v1.1 tlv header missing\n"); + goto cleanup; + } + tlv_offset += 2; + len_u32 = (uint32_t)((encoded.data[tlv_offset] << 24) | + (encoded.data[tlv_offset + 1] << 16) | + (encoded.data[tlv_offset + 2] << 8) | + encoded.data[tlv_offset + 3]); + tlv_offset += 4 + len_u32; + if (tlv_offset + 2 > encoded.len) { + fprintf(stderr, "encoded v1.1 second tlv missing\n"); + goto cleanup; + } + + ((uint8_t *)mutated.data)[tlv_offset] = 0x00u; + ((uint8_t *)mutated.data)[tlv_offset + 1] = 0x01u; + + if (amduat_enc_fer1_receipt_decode_v1_1(mutated, &decoded)) { + fprintf(stderr, "duplicate tlv accepted\n"); + amduat_enc_fer1_receipt_free(&decoded); + goto cleanup; + } + + exit_code = 0; + +cleanup: + if (mutated.data != NULL) { + free((void *)mutated.data); + } + free((void *)encoded.data); + return exit_code; +} + int main(void) { if (test_receipt_round_trip() != 0) { return 1; @@ -270,5 +758,14 @@ int main(void) { if (test_receipt_helper() != 0) { return 1; } + if (test_receipt_helper_failed_run() != 0) { + return 1; + } + if (test_receipt_round_trip_v1_1() != 0) { + return 1; + } + if (test_receipt_v1_1_reject_duplicate_tag() != 0) { + return 1; + } return 0; }