914 lines
26 KiB
C
914 lines
26 KiB
C
#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 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 tombstone_payload[4 + 2 + 2 + 32 + 4 + 4];
|
|
uint8_t lift_payload[4 + 2 + 2 + 32 + 8];
|
|
size_t seal_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);
|
|
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 || 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(NULL, 0u);
|
|
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, ¤t)) {
|
|
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;
|
|
}
|