amduat/tests/asl/test_asl_index_replay.c

933 lines
27 KiB
C
Raw Permalink Normal View History

#include "amduat/asl/asl_store_index_fs.h"
#include "amduat/asl/index_replay.h"
#include "amduat/asl/ref_derive.h"
#include "amduat/asl/store.h"
#include "amduat/enc/asl_core_index.h"
#include "amduat/enc/asl_log.h"
#include "amduat/enc/asl1_core.h"
#include "amduat/hash/asl1.h"
#include "asl_store_index_fs_layout.h"
#include <dirent.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.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 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;
bool needs_sep;
size_t total_len;
char *buffer;
size_t offset;
if (base == NULL || segment == NULL || out_path == NULL) {
return false;
}
if (base[0] == '\0' || segment[0] == '\0') {
return false;
}
base_len = strlen(base);
seg_len = strlen(segment);
needs_sep = base[base_len - 1u] != '/';
total_len = base_len + (needs_sep ? 1u : 0u) + seg_len + 1u;
buffer = (char *)malloc(total_len);
if (buffer == NULL) {
return false;
}
offset = 0u;
memcpy(buffer + offset, base, base_len);
offset += base_len;
if (needs_sep) {
buffer[offset++] = '/';
}
memcpy(buffer + offset, segment, seg_len);
offset += seg_len;
buffer[offset] = '\0';
*out_path = buffer;
return true;
}
static bool remove_tree(const char *path) {
struct stat st;
DIR *dir;
struct dirent *entry;
if (path == NULL) {
return false;
}
if (lstat(path, &st) != 0) {
return errno == ENOENT;
}
if (!S_ISDIR(st.st_mode)) {
return unlink(path) == 0;
}
dir = opendir(path);
if (dir == NULL) {
return false;
}
while ((entry = readdir(dir)) != NULL) {
char *child = NULL;
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0) {
continue;
}
if (!join_path(path, entry->d_name, &child)) {
closedir(dir);
return false;
}
if (!remove_tree(child)) {
free(child);
closedir(dir);
return false;
}
free(child);
}
if (closedir(dir) != 0) {
return false;
}
return rmdir(path) == 0;
}
static char *make_temp_root(void) {
char *templ;
const char template_prefix[] = "/tmp/amduat_test_asl_index_replay_XXXXXX";
templ = (char *)malloc(sizeof(template_prefix));
if (templ == NULL) {
return NULL;
}
memcpy(templ, template_prefix, sizeof(template_prefix));
if (mkdtemp(templ) == NULL) {
free(templ);
return NULL;
}
return templ;
}
static bool ensure_dir(const char *path) {
if (mkdir(path, 0700) == 0) {
return true;
}
return errno == EEXIST;
}
static bool write_file(const char *path, const uint8_t *data, size_t len) {
FILE *file;
size_t written;
file = fopen(path, "wb");
if (file == NULL) {
return false;
}
written = 0u;
if (len != 0) {
written = fwrite(data, 1u, len, file);
}
if (written != len) {
fclose(file);
return false;
}
return fclose(file) == 0;
}
static bool read_file(const char *path, uint8_t **out_bytes, size_t *out_len) {
FILE *file;
long size;
uint8_t *buffer;
if (out_bytes == NULL || out_len == NULL) {
return false;
}
*out_bytes = NULL;
*out_len = 0u;
file = fopen(path, "rb");
if (file == NULL) {
return false;
}
if (fseek(file, 0, SEEK_END) != 0) {
fclose(file);
return false;
}
size = ftell(file);
if (size < 0) {
fclose(file);
return false;
}
if (fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return false;
}
buffer = (uint8_t *)malloc((size_t)size);
if (buffer == NULL) {
fclose(file);
return false;
}
if (size != 0 && fread(buffer, 1u, (size_t)size, file) != (size_t)size) {
free(buffer);
fclose(file);
return false;
}
if (fclose(file) != 0) {
free(buffer);
return false;
}
*out_bytes = buffer;
*out_len = (size_t)size;
return true;
}
static bool prepare_index_tree(const char *root) {
char *index_path = NULL;
char *segments_path = NULL;
char *blocks_path = NULL;
bool ok = false;
if (!amduat_asl_store_index_fs_layout_build_index_path(root,
&index_path) ||
!amduat_asl_store_index_fs_layout_build_segments_path(root,
&segments_path) ||
!amduat_asl_store_index_fs_layout_build_blocks_path(root,
&blocks_path)) {
goto cleanup;
}
if (!ensure_dir(index_path) ||
!ensure_dir(segments_path) ||
!ensure_dir(blocks_path)) {
goto cleanup;
}
ok = true;
cleanup:
free(index_path);
free(segments_path);
free(blocks_path);
return ok;
}
static bool write_log(const char *root,
amduat_asl_log_record_t *records,
size_t record_count) {
char *log_path = NULL;
amduat_octets_t bytes;
bool ok = false;
if (!amduat_enc_asl_log_encode_v1(records, record_count, &bytes)) {
return false;
}
if (!amduat_asl_store_index_fs_layout_build_log_path(root, &log_path)) {
free((void *)bytes.data);
return false;
}
ok = write_file(log_path, bytes.data, bytes.len);
free((void *)bytes.data);
free(log_path);
return ok;
}
static bool load_log_records(const char *root,
amduat_asl_log_record_t **out_records,
size_t *out_count) {
char *log_path = NULL;
uint8_t *bytes = NULL;
size_t len = 0;
bool ok = false;
if (!amduat_asl_store_index_fs_layout_build_log_path(root, &log_path)) {
return false;
}
if (!read_file(log_path, &bytes, &len)) {
free(log_path);
return false;
}
ok = amduat_enc_asl_log_decode_v1(amduat_octets(bytes, len),
out_records,
out_count);
free(bytes);
free(log_path);
return ok;
}
static bool write_block_file(const char *root,
uint64_t block_id,
amduat_octets_t bytes) {
char *block_path = NULL;
bool ok = false;
if (!amduat_asl_store_index_fs_layout_build_block_path(root,
block_id,
&block_path)) {
return false;
}
ok = write_file(block_path, bytes.data, bytes.len);
free(block_path);
return ok;
}
static bool write_segment_file(const char *root,
uint64_t segment_id,
const amduat_asl_core_index_segment_t *segment,
uint8_t out_hash[32]) {
char *segment_path = NULL;
amduat_octets_t encoded;
bool ok = false;
if (!amduat_enc_asl_core_index_encode_v1(segment, &encoded)) {
return false;
}
if (!amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256,
encoded,
out_hash,
32)) {
free((void *)encoded.data);
return false;
}
if (!amduat_asl_store_index_fs_layout_build_segment_path(root,
segment_id,
&segment_path)) {
free((void *)encoded.data);
return false;
}
ok = write_file(segment_path, encoded.data, encoded.len);
free((void *)encoded.data);
free(segment_path);
return ok;
}
static bool build_segment_for_artifact(
amduat_asl_core_index_segment_t *segment,
amduat_asl_index_record_t *record,
amduat_asl_extent_record_t *extent,
amduat_reference_t ref,
amduat_octets_t artifact_bytes,
uint64_t snapshot_id,
uint64_t block_id) {
if (segment == NULL || record == NULL || extent == NULL) {
return false;
}
memset(segment, 0, sizeof(*segment));
memset(record, 0, sizeof(*record));
memset(extent, 0, sizeof(*extent));
segment->header.snapshot_min = snapshot_id;
segment->header.snapshot_max = snapshot_id;
segment->header.segment_domain_id = 0;
segment->header.segment_visibility = 1;
segment->header.federation_version = 0;
segment->header.flags = 0;
segment->header.reserved0 = 0;
record->hash_id = ref.hash_id;
record->digest_len = (uint16_t)ref.digest.len;
record->extent_count = 1;
record->total_length = (uint32_t)artifact_bytes.len;
record->domain_id = 0;
record->visibility = 1;
record->has_cross_domain_source = 0;
record->cross_domain_source = 0;
record->flags = 0;
extent->block_id = block_id;
extent->offset = 0;
extent->length = (uint32_t)artifact_bytes.len;
segment->records = record;
segment->record_count = 1;
segment->digests = ref.digest;
segment->extents = extent;
segment->extent_count = 1;
segment->footer.seal_snapshot = snapshot_id;
segment->footer.seal_time_ns = snapshot_id;
return true;
}
static bool write_artifact_segment(const char *root,
amduat_artifact_t artifact,
uint64_t segment_id,
uint64_t block_id,
uint64_t snapshot_id,
uint8_t out_hash[32],
amduat_reference_t *out_ref) {
amduat_reference_t ref;
amduat_octets_t artifact_bytes;
amduat_asl_core_index_segment_t segment;
amduat_asl_index_record_t record;
amduat_asl_extent_record_t extent;
if (out_ref == NULL) {
return false;
}
if (!amduat_asl_ref_derive(artifact,
AMDUAT_ENC_ASL1_CORE_V1,
AMDUAT_HASH_ASL1_ID_SHA256,
&ref,
&artifact_bytes)) {
return false;
}
if (!write_block_file(root, block_id, artifact_bytes)) {
amduat_octets_free(&artifact_bytes);
amduat_reference_free(&ref);
return false;
}
if (!build_segment_for_artifact(&segment,
&record,
&extent,
ref,
artifact_bytes,
snapshot_id,
block_id) ||
!write_segment_file(root, segment_id, &segment, out_hash)) {
amduat_octets_free(&artifact_bytes);
amduat_reference_free(&ref);
return false;
}
amduat_octets_free(&artifact_bytes);
*out_ref = ref;
return true;
}
static bool replay_state_equal(const amduat_asl_replay_state_t *lhs,
const amduat_asl_replay_state_t *rhs) {
size_t i;
if (lhs->segments_len != rhs->segments_len ||
lhs->tombstones_len != rhs->tombstones_len ||
lhs->state.log_position != rhs->state.log_position) {
return false;
}
for (i = 0; i < lhs->segments_len; ++i) {
const amduat_asl_segment_seal_t *left = &lhs->segments[i];
const amduat_asl_segment_seal_t *right = &rhs->segments[i];
if (left->segment_id != right->segment_id ||
memcmp(left->segment_hash, right->segment_hash,
sizeof(left->segment_hash)) != 0) {
return false;
}
}
for (i = 0; i < lhs->tombstones_len; ++i) {
const amduat_asl_tombstone_entry_t *left = &lhs->tombstones[i];
const amduat_asl_tombstone_entry_t *right = &rhs->tombstones[i];
if (left->tombstone_logseq != right->tombstone_logseq ||
!amduat_reference_eq(left->ref, right->ref)) {
return false;
}
}
return true;
}
static int test_replay_determinism(void) {
char *root;
amduat_artifact_t artifact_a;
amduat_artifact_t artifact_b;
amduat_reference_t ref_a;
amduat_reference_t ref_b;
uint8_t hash_a[32];
uint8_t hash_b[32];
uint8_t seal_payload_a[8 + 32];
uint8_t seal_payload_b[8 + 32];
size_t seal_len_a;
size_t seal_len_b;
amduat_asl_log_record_t records[2];
amduat_asl_log_record_t *loaded_records = NULL;
size_t loaded_count = 0;
amduat_asl_replay_state_t first;
amduat_asl_replay_state_t second;
uint8_t payload_a[4] = {0x01, 0x02, 0x03, 0x04};
uint8_t payload_b[4] = {0x10, 0x20, 0x30, 0x40};
int exit_code = 1;
ref_a = amduat_reference(0u, amduat_octets(NULL, 0u));
ref_b = amduat_reference(0u, amduat_octets(NULL, 0u));
root = make_temp_root();
if (root == NULL) {
fprintf(stderr, "temp root failed\n");
return 1;
}
if (!prepare_index_tree(root)) {
fprintf(stderr, "prepare index tree failed\n");
goto cleanup;
}
artifact_a = amduat_artifact(amduat_octets(payload_a, sizeof(payload_a)));
artifact_b = amduat_artifact(amduat_octets(payload_b, sizeof(payload_b)));
if (!write_artifact_segment(root, artifact_a, 1, 1, 1, hash_a, &ref_a) ||
!write_artifact_segment(root, artifact_b, 2, 2, 2, hash_b, &ref_b)) {
fprintf(stderr, "write artifact segments failed\n");
goto cleanup;
}
seal_len_a = build_segment_seal_payload(seal_payload_a,
sizeof(seal_payload_a),
1,
hash_a);
seal_len_b = build_segment_seal_payload(seal_payload_b,
sizeof(seal_payload_b),
2,
hash_b);
if (seal_len_a == 0 || seal_len_b == 0) {
fprintf(stderr, "seal payload build failed\n");
goto cleanup;
}
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 (!write_log(root, records, 2)) {
fprintf(stderr, "write log failed\n");
goto cleanup;
}
if (!load_log_records(root, &loaded_records, &loaded_count)) {
fprintf(stderr, "log decode failed\n");
goto cleanup;
}
if (!amduat_asl_replay_init(&first) ||
!amduat_asl_replay_init(&second)) {
fprintf(stderr, "replay init failed\n");
goto cleanup;
}
if (!amduat_asl_replay_apply_log(loaded_records,
loaded_count,
20,
&first) ||
!amduat_asl_replay_apply_log(loaded_records,
loaded_count,
20,
&second)) {
fprintf(stderr, "replay apply failed\n");
amduat_asl_replay_free(&first);
amduat_asl_replay_free(&second);
goto cleanup;
}
if (!replay_state_equal(&first, &second)) {
fprintf(stderr, "replay state mismatch\n");
amduat_asl_replay_free(&first);
amduat_asl_replay_free(&second);
goto cleanup;
}
amduat_asl_replay_free(&first);
amduat_asl_replay_free(&second);
exit_code = 0;
cleanup:
if (loaded_records != NULL) {
amduat_enc_asl_log_free(loaded_records, loaded_count);
}
amduat_reference_free(&ref_a);
amduat_reference_free(&ref_b);
if (!remove_tree(root)) {
fprintf(stderr, "cleanup failed\n");
exit_code = 1;
}
free(root);
return exit_code;
}
static int test_tombstone_lift_boundary(void) {
char *root;
amduat_artifact_t artifact;
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];
amduat_asl_store_config_t config;
amduat_asl_store_index_fs_t fs;
amduat_asl_store_t store;
amduat_asl_index_state_t cutoff;
amduat_artifact_t loaded;
amduat_asl_store_error_t err;
uint8_t payload[5] = {0x11, 0x22, 0x33, 0x44, 0x55};
int exit_code = 1;
ref = amduat_reference(0u, amduat_octets(NULL, 0u));
root = make_temp_root();
if (root == NULL) {
fprintf(stderr, "temp root failed\n");
return 1;
}
if (!prepare_index_tree(root)) {
fprintf(stderr, "prepare index tree failed\n");
goto cleanup;
}
artifact = amduat_artifact(amduat_octets(payload, sizeof(payload)));
if (!write_artifact_segment(root, artifact, 5, 5, 5, hash, &ref)) {
fprintf(stderr, "write artifact segment failed\n");
goto cleanup;
}
seal_len = build_segment_seal_payload(seal_payload,
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,
ref.digest.data,
ref.digest.len);
lift_len = build_tombstone_lift_payload(lift_payload,
sizeof(lift_payload),
ref.hash_id,
ref.digest.data,
ref.digest.len,
20);
if (seal_len == 0 || anchor_len == 0 || tombstone_len == 0 ||
lift_len == 0) {
fprintf(stderr, "log payload build failed\n");
goto cleanup;
}
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, seal_len);
records[1].logseq = 20;
records[1].record_type = AMDUAT_ASL_LOG_RECORD_TOMBSTONE;
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(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);
if (!write_log(root, records, 4)) {
fprintf(stderr, "write log failed\n");
goto cleanup;
}
memset(&config, 0, sizeof(config));
config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1;
config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256;
if (!amduat_asl_store_index_fs_init(&fs, config, root)) {
fprintf(stderr, "index fs init failed\n");
goto cleanup;
}
amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs);
cutoff.snapshot_id = 0;
cutoff.log_position = 30;
loaded = amduat_artifact(amduat_octets(NULL, 0u));
err = amduat_asl_store_get_indexed(&store, ref, cutoff, &loaded);
if (err != AMDUAT_ASL_STORE_ERR_NOT_FOUND) {
fprintf(stderr, "tombstone cutoff expected not found: %d\n", err);
amduat_artifact_free(&loaded);
goto cleanup;
}
cutoff.log_position = 40;
loaded = amduat_artifact(amduat_octets(NULL, 0u));
err = amduat_asl_store_get_indexed(&store, ref, cutoff, &loaded);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "lift lookup failed: %d\n", err);
amduat_artifact_free(&loaded);
goto cleanup;
}
if (!amduat_artifact_eq(artifact, loaded)) {
fprintf(stderr, "lifted artifact mismatch\n");
amduat_artifact_free(&loaded);
goto cleanup;
}
amduat_artifact_free(&loaded);
exit_code = 0;
cleanup:
amduat_reference_free(&ref);
if (!remove_tree(root)) {
fprintf(stderr, "cleanup failed\n");
exit_code = 1;
}
free(root);
return exit_code;
}
static int test_crash_recovery_unsealed(void) {
char *root;
amduat_artifact_t artifact_a;
amduat_artifact_t artifact_b;
amduat_reference_t ref_a;
amduat_reference_t ref_b;
uint8_t hash_a[32];
uint8_t hash_b[32];
uint8_t seal_payload[8 + 32];
size_t seal_len;
amduat_asl_log_record_t records[1];
amduat_asl_log_record_t *loaded_records = NULL;
size_t loaded_count = 0;
amduat_asl_replay_state_t replay_state;
amduat_asl_store_config_t config;
amduat_asl_store_index_fs_t fs;
amduat_asl_store_t store;
amduat_asl_index_state_t current;
amduat_artifact_t loaded;
amduat_asl_store_error_t err;
uint8_t payload_a[3] = {0x61, 0x62, 0x63};
uint8_t payload_b[3] = {0x64, 0x65, 0x66};
int exit_code = 1;
ref_a = amduat_reference(0u, amduat_octets(NULL, 0u));
ref_b = amduat_reference(0u, amduat_octets(NULL, 0u));
root = make_temp_root();
if (root == NULL) {
fprintf(stderr, "temp root failed\n");
return 1;
}
if (!prepare_index_tree(root)) {
fprintf(stderr, "prepare index tree failed\n");
goto cleanup;
}
artifact_a = amduat_artifact(amduat_octets(payload_a, sizeof(payload_a)));
artifact_b = amduat_artifact(amduat_octets(payload_b, sizeof(payload_b)));
if (!write_artifact_segment(root, artifact_a, 1, 1, 1, hash_a, &ref_a) ||
!write_artifact_segment(root, artifact_b, 9, 9, 9, hash_b, &ref_b)) {
fprintf(stderr, "write artifact segments failed\n");
goto cleanup;
}
seal_len = build_segment_seal_payload(seal_payload,
sizeof(seal_payload),
1,
hash_a);
if (seal_len == 0) {
fprintf(stderr, "seal payload build failed\n");
goto cleanup;
}
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, seal_len);
if (!write_log(root, records, 1)) {
fprintf(stderr, "write log failed\n");
goto cleanup;
}
memset(&config, 0, sizeof(config));
config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1;
config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256;
if (!amduat_asl_store_index_fs_init(&fs, config, root)) {
fprintf(stderr, "index fs init failed\n");
goto cleanup;
}
amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs);
if (!amduat_asl_index_current_state(&store, &current)) {
fprintf(stderr, "current state failed\n");
goto cleanup;
}
loaded = amduat_artifact(amduat_octets(NULL, 0u));
err = amduat_asl_store_get_indexed(&store, ref_b, current, &loaded);
if (err != AMDUAT_ASL_STORE_ERR_NOT_FOUND) {
fprintf(stderr, "unsealed segment should be ignored: %d\n", err);
amduat_artifact_free(&loaded);
goto cleanup;
}
if (!load_log_records(root, &loaded_records, &loaded_count)) {
fprintf(stderr, "log decode failed\n");
goto cleanup;
}
if (!amduat_asl_replay_init(&replay_state)) {
fprintf(stderr, "replay init failed\n");
goto cleanup;
}
if (!amduat_asl_replay_apply_log(loaded_records,
loaded_count,
current.log_position,
&replay_state)) {
fprintf(stderr, "replay apply failed\n");
amduat_asl_replay_free(&replay_state);
goto cleanup;
}
if (replay_state.segments_len != 1 ||
replay_state.segments[0].segment_id != 1 ||
memcmp(replay_state.segments[0].segment_hash, hash_a,
sizeof(hash_a)) != 0) {
fprintf(stderr, "replay should only include sealed segment\n");
amduat_asl_replay_free(&replay_state);
goto cleanup;
}
amduat_asl_replay_free(&replay_state);
exit_code = 0;
cleanup:
if (loaded_records != NULL) {
amduat_enc_asl_log_free(loaded_records, loaded_count);
}
amduat_reference_free(&ref_a);
amduat_reference_free(&ref_b);
if (!remove_tree(root)) {
fprintf(stderr, "cleanup failed\n");
exit_code = 1;
}
free(root);
return exit_code;
}
int main(void) {
if (test_replay_determinism() != 0) {
return 1;
}
if (test_tombstone_lift_boundary() != 0) {
return 1;
}
if (test_crash_recovery_unsealed() != 0) {
return 1;
}
return 0;
}