amduat/tests/asl/test_asl_replay.c

292 lines
9.3 KiB
C
Raw Permalink Normal View History

2026-01-17 14:08:41 +01:00
#include "amduat/asl/index_replay.h"
#include "amduat/hash/asl1.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
static void store_u16_le(uint8_t *out, uint16_t value) {
out[0] = (uint8_t)(value & 0xffu);
out[1] = (uint8_t)((value >> 8) & 0xffu);
}
static void store_u32_le(uint8_t *out, uint32_t value) {
out[0] = (uint8_t)(value & 0xffu);
out[1] = (uint8_t)((value >> 8) & 0xffu);
out[2] = (uint8_t)((value >> 16) & 0xffu);
out[3] = (uint8_t)((value >> 24) & 0xffu);
}
static void store_u64_le(uint8_t *out, uint64_t value) {
out[0] = (uint8_t)(value & 0xffu);
out[1] = (uint8_t)((value >> 8) & 0xffu);
out[2] = (uint8_t)((value >> 16) & 0xffu);
out[3] = (uint8_t)((value >> 24) & 0xffu);
out[4] = (uint8_t)((value >> 32) & 0xffu);
out[5] = (uint8_t)((value >> 40) & 0xffu);
out[6] = (uint8_t)((value >> 48) & 0xffu);
out[7] = (uint8_t)((value >> 56) & 0xffu);
}
static size_t build_artifact_ref(uint8_t *out,
size_t out_cap,
amduat_hash_id_t hash_id,
const uint8_t *digest,
size_t digest_len) {
size_t total = 4 + 2 + 2 + digest_len;
if (out_cap < total) {
return 0;
}
store_u32_le(out, (uint32_t)hash_id);
store_u16_le(out + 4, (uint16_t)digest_len);
store_u16_le(out + 6, 0);
memcpy(out + 8, digest, digest_len);
return total;
}
static size_t build_tombstone_payload(uint8_t *out,
size_t out_cap,
amduat_hash_id_t hash_id,
const uint8_t *digest,
size_t digest_len) {
size_t offset = 0;
size_t ref_len = build_artifact_ref(out,
out_cap,
hash_id,
digest,
digest_len);
if (ref_len == 0 || out_cap < ref_len + 8) {
return 0;
}
offset += ref_len;
store_u32_le(out + offset, 0);
offset += 4;
store_u32_le(out + offset, 0);
offset += 4;
return offset;
}
static size_t build_tombstone_lift_payload(uint8_t *out,
size_t out_cap,
amduat_hash_id_t hash_id,
const uint8_t *digest,
size_t digest_len,
uint64_t tombstone_logseq) {
size_t offset = 0;
size_t ref_len = build_artifact_ref(out,
out_cap,
hash_id,
digest,
digest_len);
if (ref_len == 0 || out_cap < ref_len + 8) {
return 0;
}
offset += ref_len;
store_u64_le(out + offset, tombstone_logseq);
offset += 8;
return offset;
}
static size_t build_segment_seal_payload(uint8_t *out,
size_t out_cap,
uint64_t segment_id,
const uint8_t hash[32]) {
if (out_cap < 8 + 32) {
return 0;
}
store_u64_le(out, segment_id);
memcpy(out + 8, hash, 32);
return 8 + 32;
}
static int test_tombstone_lift_cutoff(void) {
amduat_asl_log_record_t records[2];
amduat_asl_replay_state_t state;
uint8_t digest[32];
uint8_t tombstone_payload[4 + 2 + 2 + 32 + 4 + 4];
uint8_t lift_payload[4 + 2 + 2 + 32 + 8];
size_t tombstone_len;
size_t lift_len;
amduat_reference_t ref;
memset(digest, 0x5a, sizeof(digest));
tombstone_len = build_tombstone_payload(tombstone_payload,
sizeof(tombstone_payload),
AMDUAT_HASH_ASL1_ID_SHA256,
digest,
sizeof(digest));
lift_len = build_tombstone_lift_payload(lift_payload,
sizeof(lift_payload),
AMDUAT_HASH_ASL1_ID_SHA256,
digest,
sizeof(digest),
20);
if (tombstone_len == 0 || lift_len == 0) {
fprintf(stderr, "payload build failed\n");
return 1;
}
memset(records, 0, sizeof(records));
records[0].logseq = 20;
records[0].record_type = AMDUAT_ASL_LOG_RECORD_TOMBSTONE;
records[0].payload = amduat_octets(tombstone_payload, tombstone_len);
records[1].logseq = 40;
records[1].record_type = AMDUAT_ASL_LOG_RECORD_TOMBSTONE_LIFT;
records[1].payload = amduat_octets(lift_payload, lift_len);
if (!amduat_asl_replay_init(&state)) {
fprintf(stderr, "replay init failed\n");
return 1;
}
if (!amduat_asl_replay_apply_log(records, 2, 30, &state)) {
fprintf(stderr, "replay apply failed\n");
amduat_asl_replay_free(&state);
return 1;
}
if (state.tombstones_len != 1) {
fprintf(stderr, "tombstone not applied\n");
amduat_asl_replay_free(&state);
return 1;
}
ref = amduat_reference(AMDUAT_HASH_ASL1_ID_SHA256,
amduat_octets(digest, sizeof(digest)));
if (state.tombstones[0].tombstone_logseq != 20 ||
!amduat_reference_eq(state.tombstones[0].ref, ref)) {
fprintf(stderr, "tombstone mismatch\n");
amduat_asl_replay_free(&state);
return 1;
}
amduat_asl_replay_free(&state);
if (!amduat_asl_replay_init(&state)) {
fprintf(stderr, "replay init failed\n");
return 1;
}
if (!amduat_asl_replay_apply_log(records, 2, 40, &state)) {
fprintf(stderr, "replay apply failed\n");
amduat_asl_replay_free(&state);
return 1;
}
if (state.tombstones_len != 0) {
fprintf(stderr, "tombstone lift failed\n");
amduat_asl_replay_free(&state);
return 1;
}
amduat_asl_replay_free(&state);
return 0;
}
static int test_unknown_record_skip(void) {
amduat_asl_log_record_t records[2];
amduat_asl_replay_state_t state;
uint8_t unknown_payload[1] = {0x01};
uint8_t seal_payload[8 + 32];
uint8_t seal_hash[32];
size_t seal_len;
memset(seal_hash, 0xab, sizeof(seal_hash));
seal_len = build_segment_seal_payload(seal_payload,
sizeof(seal_payload),
9,
seal_hash);
if (seal_len == 0) {
fprintf(stderr, "seal payload build failed\n");
return 1;
}
memset(records, 0, sizeof(records));
records[0].logseq = 10;
records[0].record_type = 0x99;
records[0].payload = amduat_octets(unknown_payload, sizeof(unknown_payload));
records[1].logseq = 11;
records[1].record_type = AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL;
records[1].payload = amduat_octets(seal_payload, seal_len);
if (!amduat_asl_replay_init(&state)) {
fprintf(stderr, "replay init failed\n");
return 1;
}
if (!amduat_asl_replay_apply_log(records, 2, 11, &state)) {
fprintf(stderr, "replay apply failed\n");
amduat_asl_replay_free(&state);
return 1;
}
if (state.segments_len != 1) {
fprintf(stderr, "segment seal missing\n");
amduat_asl_replay_free(&state);
return 1;
}
amduat_asl_replay_free(&state);
return 0;
}
static int test_multiple_seals_latest(void) {
amduat_asl_log_record_t records[2];
amduat_asl_replay_state_t state;
uint8_t seal_payload_a[8 + 32];
uint8_t seal_payload_b[8 + 32];
uint8_t hash_a[32];
uint8_t hash_b[32];
size_t seal_len_a;
size_t seal_len_b;
memset(hash_a, 0x11, sizeof(hash_a));
memset(hash_b, 0x22, sizeof(hash_b));
seal_len_a = build_segment_seal_payload(seal_payload_a,
sizeof(seal_payload_a),
5,
hash_a);
seal_len_b = build_segment_seal_payload(seal_payload_b,
sizeof(seal_payload_b),
5,
hash_b);
if (seal_len_a == 0 || seal_len_b == 0) {
fprintf(stderr, "seal payload build failed\n");
return 1;
}
memset(records, 0, sizeof(records));
records[0].logseq = 10;
records[0].record_type = AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL;
records[0].payload = amduat_octets(seal_payload_a, seal_len_a);
records[1].logseq = 20;
records[1].record_type = AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL;
records[1].payload = amduat_octets(seal_payload_b, seal_len_b);
if (!amduat_asl_replay_init(&state)) {
fprintf(stderr, "replay init failed\n");
return 1;
}
if (!amduat_asl_replay_apply_log(records, 2, 20, &state)) {
fprintf(stderr, "replay apply failed\n");
amduat_asl_replay_free(&state);
return 1;
}
if (state.segments_len != 1) {
fprintf(stderr, "segment seal count mismatch\n");
amduat_asl_replay_free(&state);
return 1;
}
if (memcmp(state.segments[0].segment_hash, hash_b, sizeof(hash_b)) != 0) {
fprintf(stderr, "segment seal not updated\n");
amduat_asl_replay_free(&state);
return 1;
}
amduat_asl_replay_free(&state);
return 0;
}
int main(void) {
if (test_tombstone_lift_cutoff() != 0) {
return 1;
}
if (test_unknown_record_skip() != 0) {
return 1;
}
if (test_multiple_seals_latest() != 0) {
return 1;
}
return 0;
}