Add filesystem-backed ASL index store

This commit is contained in:
Carl Niklas Rydberg 2026-01-17 14:08:41 +01:00
parent 68371b1554
commit 347d2e4dda
9 changed files with 2597 additions and 0 deletions

View file

@ -63,6 +63,7 @@ set(AMDUAT_ASL_SRCS
src/kernel/asl/core.c src/kernel/asl/core.c
src/near_core/asl/artifact_io.c src/near_core/asl/artifact_io.c
src/near_core/asl/io.c src/near_core/asl/io.c
src/near_core/asl/index_replay.c
src/near_core/asl/parse.c src/near_core/asl/parse.c
src/near_core/asl/ref_io.c src/near_core/asl/ref_io.c
src/near_core/asl/store.c src/near_core/asl/store.c
@ -123,6 +124,11 @@ set(AMDUAT_ASL_STORE_FS_SRCS
src/adapters/asl_store_fs/asl_store_fs_meta.c src/adapters/asl_store_fs/asl_store_fs_meta.c
) )
set(AMDUAT_ASL_STORE_INDEX_FS_SRCS
src/adapters/asl_store_index_fs/asl_store_index_fs.c
src/adapters/asl_store_index_fs/asl_store_index_fs_layout.c
)
set(AMDUAT_TGK_STORE_MEM_SRCS set(AMDUAT_TGK_STORE_MEM_SRCS
src/adapters/tgk_store_mem/tgk_store_mem.c src/adapters/tgk_store_mem/tgk_store_mem.c
) )
@ -155,6 +161,10 @@ amduat_add_lib(asl_store_fs SRCS ${AMDUAT_ASL_STORE_FS_SRCS})
amduat_link(asl_store_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util) amduat_link(asl_store_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util)
target_compile_definitions(amduat_asl_store_fs_obj PRIVATE _POSIX_C_SOURCE=200809L) target_compile_definitions(amduat_asl_store_fs_obj PRIVATE _POSIX_C_SOURCE=200809L)
amduat_add_lib(asl_store_index_fs SRCS ${AMDUAT_ASL_STORE_INDEX_FS_SRCS})
amduat_link(asl_store_index_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util)
target_compile_definitions(amduat_asl_store_index_fs_obj PRIVATE _POSIX_C_SOURCE=200809L)
amduat_add_lib(tgk_store_mem SRCS ${AMDUAT_TGK_STORE_MEM_SRCS}) amduat_add_lib(tgk_store_mem SRCS ${AMDUAT_TGK_STORE_MEM_SRCS})
amduat_link(tgk_store_mem amduat_tgk amduat_asl amduat_enc amduat_hash_asl1 amduat_util) amduat_link(tgk_store_mem amduat_tgk amduat_asl amduat_enc amduat_hash_asl1 amduat_util)
@ -343,6 +353,30 @@ target_link_libraries(amduat_test_asl_store_indexed_ops
) )
add_test(NAME asl_store_indexed_ops COMMAND amduat_test_asl_store_indexed_ops) add_test(NAME asl_store_indexed_ops COMMAND amduat_test_asl_store_indexed_ops)
add_executable(amduat_test_asl_replay tests/asl/test_asl_replay.c)
target_include_directories(amduat_test_asl_replay
PRIVATE ${AMDUAT_INTERNAL_DIR}
PRIVATE ${AMDUAT_INCLUDE_DIR}
)
target_link_libraries(amduat_test_asl_replay
PRIVATE amduat_asl
)
add_test(NAME asl_replay COMMAND amduat_test_asl_replay)
add_executable(amduat_test_asl_store_index_fs
tests/asl/test_asl_store_index_fs.c)
target_include_directories(amduat_test_asl_store_index_fs
PRIVATE ${AMDUAT_INTERNAL_DIR}
PRIVATE ${AMDUAT_INCLUDE_DIR}
)
target_compile_definitions(amduat_test_asl_store_index_fs
PRIVATE _POSIX_C_SOURCE=200809L
)
target_link_libraries(amduat_test_asl_store_index_fs
PRIVATE amduat_asl_store_index_fs
)
add_test(NAME asl_store_index_fs COMMAND amduat_test_asl_store_index_fs)
add_executable(amduat_test_pel_program_dag_exec add_executable(amduat_test_pel_program_dag_exec
tests/pel/test_pel_program_dag_exec.c) tests/pel/test_pel_program_dag_exec.c)
target_include_directories(amduat_test_pel_program_dag_exec target_include_directories(amduat_test_pel_program_dag_exec

View file

@ -0,0 +1,30 @@
#ifndef AMDUAT_ASL_STORE_INDEX_FS_H
#define AMDUAT_ASL_STORE_INDEX_FS_H
#include "amduat/asl/store.h"
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
enum { AMDUAT_ASL_STORE_INDEX_FS_ROOT_MAX = 1024 };
typedef struct {
amduat_asl_store_config_t config;
char root_path[AMDUAT_ASL_STORE_INDEX_FS_ROOT_MAX];
} amduat_asl_store_index_fs_t;
bool amduat_asl_store_index_fs_init(amduat_asl_store_index_fs_t *fs,
amduat_asl_store_config_t config,
const char *root_path);
amduat_asl_store_ops_t amduat_asl_store_index_fs_ops(void);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* AMDUAT_ASL_STORE_INDEX_FS_H */

View file

@ -0,0 +1,47 @@
#ifndef AMDUAT_ASL_INDEX_REPLAY_H
#define AMDUAT_ASL_INDEX_REPLAY_H
#include "amduat/asl/core.h"
#include "amduat/asl/store.h"
#include "amduat/enc/asl_log.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint64_t segment_id;
uint8_t segment_hash[32];
} amduat_asl_segment_seal_t;
typedef struct {
amduat_reference_t ref;
uint64_t tombstone_logseq;
} amduat_asl_tombstone_entry_t;
typedef struct {
amduat_asl_segment_seal_t *segments;
size_t segments_len;
amduat_asl_tombstone_entry_t *tombstones;
size_t tombstones_len;
amduat_asl_index_state_t state;
} amduat_asl_replay_state_t;
bool amduat_asl_replay_init(amduat_asl_replay_state_t *out);
void amduat_asl_replay_free(amduat_asl_replay_state_t *state);
bool amduat_asl_replay_apply_log(
const amduat_asl_log_record_t *records,
size_t record_count,
amduat_asl_log_position_t log_position,
amduat_asl_replay_state_t *state);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* AMDUAT_ASL_INDEX_REPLAY_H */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,191 @@
#include "asl_store_index_fs_layout.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static bool amduat_asl_store_index_fs_layout_join(const char *base,
const char *suffix,
char **out_path) {
size_t base_len;
size_t suffix_len;
bool needs_sep;
size_t total_len;
char *buffer;
size_t offset;
if (base == NULL || suffix == NULL || out_path == NULL) {
return false;
}
if (base[0] == '\0' || suffix[0] == '\0') {
return false;
}
base_len = strlen(base);
suffix_len = strlen(suffix);
needs_sep = base[base_len - 1u] != '/';
total_len = base_len + (needs_sep ? 1u : 0u) + suffix_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, suffix, suffix_len);
offset += suffix_len;
buffer[offset] = '\0';
*out_path = buffer;
return true;
}
static bool amduat_asl_store_index_fs_layout_format_id(const char *prefix,
uint64_t id,
const char *suffix,
char **out_name) {
int needed;
char *buffer;
if (prefix == NULL || suffix == NULL || out_name == NULL) {
return false;
}
needed = snprintf(NULL, 0, "%s%016" PRIx64 "%s", prefix, id, suffix);
if (needed <= 0) {
return false;
}
buffer = (char *)malloc((size_t)needed + 1u);
if (buffer == NULL) {
return false;
}
snprintf(buffer, (size_t)needed + 1u, "%s%016" PRIx64 "%s", prefix, id,
suffix);
*out_name = buffer;
return true;
}
bool amduat_asl_store_index_fs_layout_build_index_path(const char *root_path,
char **out_path) {
return amduat_asl_store_index_fs_layout_join(root_path, "index", out_path);
}
bool amduat_asl_store_index_fs_layout_build_segments_path(
const char *root_path,
char **out_path) {
char *index_path;
bool ok;
if (!amduat_asl_store_index_fs_layout_build_index_path(root_path,
&index_path)) {
return false;
}
ok = amduat_asl_store_index_fs_layout_join(index_path, "segments", out_path);
free(index_path);
return ok;
}
bool amduat_asl_store_index_fs_layout_build_blocks_path(const char *root_path,
char **out_path) {
char *index_path;
bool ok;
if (!amduat_asl_store_index_fs_layout_build_index_path(root_path,
&index_path)) {
return false;
}
ok = amduat_asl_store_index_fs_layout_join(index_path, "blocks", out_path);
free(index_path);
return ok;
}
bool amduat_asl_store_index_fs_layout_build_log_path(const char *root_path,
char **out_path) {
char *index_path;
bool ok;
if (!amduat_asl_store_index_fs_layout_build_index_path(root_path,
&index_path)) {
return false;
}
ok = amduat_asl_store_index_fs_layout_join(index_path, "log.asl", out_path);
free(index_path);
return ok;
}
bool amduat_asl_store_index_fs_layout_build_segment_meta_path(
const char *root_path,
char **out_path) {
char *segments_path;
bool ok;
if (!amduat_asl_store_index_fs_layout_build_segments_path(root_path,
&segments_path)) {
return false;
}
ok = amduat_asl_store_index_fs_layout_join(segments_path,
"next_id",
out_path);
free(segments_path);
return ok;
}
bool amduat_asl_store_index_fs_layout_build_segment_path(
const char *root_path,
uint64_t segment_id,
char **out_path) {
char *segments_path;
char *name;
bool ok;
if (!amduat_asl_store_index_fs_layout_build_segments_path(root_path,
&segments_path)) {
return false;
}
if (!amduat_asl_store_index_fs_layout_format_id("segment-",
segment_id,
".asl",
&name)) {
free(segments_path);
return false;
}
ok = amduat_asl_store_index_fs_layout_join(segments_path, name, out_path);
free(name);
free(segments_path);
return ok;
}
bool amduat_asl_store_index_fs_layout_build_block_path(
const char *root_path,
uint64_t block_id,
char **out_path) {
char *blocks_path;
char *name;
bool ok;
if (!amduat_asl_store_index_fs_layout_build_blocks_path(root_path,
&blocks_path)) {
return false;
}
if (!amduat_asl_store_index_fs_layout_format_id("block-",
block_id,
".asl",
&name)) {
free(blocks_path);
return false;
}
ok = amduat_asl_store_index_fs_layout_join(blocks_path, name, out_path);
free(name);
free(blocks_path);
return ok;
}

View file

@ -0,0 +1,42 @@
#ifndef AMDUAT_ASL_STORE_INDEX_FS_LAYOUT_H
#define AMDUAT_ASL_STORE_INDEX_FS_LAYOUT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
bool amduat_asl_store_index_fs_layout_build_index_path(const char *root_path,
char **out_path);
bool amduat_asl_store_index_fs_layout_build_segments_path(const char *root_path,
char **out_path);
bool amduat_asl_store_index_fs_layout_build_blocks_path(const char *root_path,
char **out_path);
bool amduat_asl_store_index_fs_layout_build_log_path(const char *root_path,
char **out_path);
bool amduat_asl_store_index_fs_layout_build_segment_meta_path(
const char *root_path,
char **out_path);
bool amduat_asl_store_index_fs_layout_build_segment_path(
const char *root_path,
uint64_t segment_id,
char **out_path);
bool amduat_asl_store_index_fs_layout_build_block_path(
const char *root_path,
uint64_t block_id,
char **out_path);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* AMDUAT_ASL_STORE_INDEX_FS_LAYOUT_H */

View file

@ -0,0 +1,313 @@
#include "amduat/asl/index_replay.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
const uint8_t *data;
size_t len;
size_t offset;
} amduat_asl_replay_cursor_t;
static bool amduat_asl_replay_read_u16_le(amduat_asl_replay_cursor_t *cur,
uint16_t *out) {
const uint8_t *data;
if (cur->len - cur->offset < 2) {
return false;
}
data = cur->data + cur->offset;
*out = (uint16_t)data[0] | ((uint16_t)data[1] << 8);
cur->offset += 2;
return true;
}
static bool amduat_asl_replay_read_u32_le(amduat_asl_replay_cursor_t *cur,
uint32_t *out) {
const uint8_t *data;
if (cur->len - cur->offset < 4) {
return false;
}
data = cur->data + cur->offset;
*out = (uint32_t)data[0] | ((uint32_t)data[1] << 8) |
((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24);
cur->offset += 4;
return true;
}
static bool amduat_asl_replay_read_u64_le(amduat_asl_replay_cursor_t *cur,
uint64_t *out) {
const uint8_t *data;
if (cur->len - cur->offset < 8) {
return false;
}
data = cur->data + cur->offset;
*out = (uint64_t)data[0] | ((uint64_t)data[1] << 8) |
((uint64_t)data[2] << 16) | ((uint64_t)data[3] << 24) |
((uint64_t)data[4] << 32) | ((uint64_t)data[5] << 40) |
((uint64_t)data[6] << 48) | ((uint64_t)data[7] << 56);
cur->offset += 8;
return true;
}
static bool amduat_asl_replay_parse_artifact_ref(
amduat_asl_replay_cursor_t *cur,
amduat_reference_t *out_ref) {
uint32_t hash_id_raw;
uint16_t digest_len;
uint16_t reserved0;
const uint8_t *digest;
if (!amduat_asl_replay_read_u32_le(cur, &hash_id_raw) ||
!amduat_asl_replay_read_u16_le(cur, &digest_len) ||
!amduat_asl_replay_read_u16_le(cur, &reserved0)) {
return false;
}
if (hash_id_raw > UINT16_MAX || digest_len == 0 || reserved0 != 0) {
return false;
}
if (cur->len - cur->offset < digest_len) {
return false;
}
digest = cur->data + cur->offset;
cur->offset += digest_len;
*out_ref = amduat_reference((amduat_hash_id_t)hash_id_raw,
amduat_octets(digest, digest_len));
return true;
}
static bool amduat_asl_replay_parse_segment_seal(
amduat_octets_t payload,
amduat_asl_segment_seal_t *out) {
amduat_asl_replay_cursor_t cur;
uint64_t segment_id;
if (payload.len < 8 + 32 || payload.data == NULL || out == NULL) {
return false;
}
cur.data = payload.data;
cur.len = payload.len;
cur.offset = 0;
if (!amduat_asl_replay_read_u64_le(&cur, &segment_id)) {
return false;
}
if (cur.len - cur.offset < 32) {
return false;
}
out->segment_id = segment_id;
memcpy(out->segment_hash, cur.data + cur.offset, 32);
return true;
}
static bool amduat_asl_replay_parse_tombstone(
amduat_octets_t payload,
amduat_reference_t *out_ref) {
amduat_asl_replay_cursor_t cur;
uint32_t scope;
uint32_t reason;
if (payload.len == 0 || payload.data == NULL || out_ref == NULL) {
return false;
}
cur.data = payload.data;
cur.len = payload.len;
cur.offset = 0;
if (!amduat_asl_replay_parse_artifact_ref(&cur, out_ref)) {
return false;
}
if (!amduat_asl_replay_read_u32_le(&cur, &scope) ||
!amduat_asl_replay_read_u32_le(&cur, &reason)) {
return false;
}
(void)scope;
(void)reason;
return true;
}
static bool amduat_asl_replay_parse_tombstone_lift(
amduat_octets_t payload,
amduat_reference_t *out_ref,
uint64_t *out_logseq) {
amduat_asl_replay_cursor_t cur;
uint64_t tombstone_logseq;
if (payload.len == 0 || payload.data == NULL || out_ref == NULL ||
out_logseq == NULL) {
return false;
}
cur.data = payload.data;
cur.len = payload.len;
cur.offset = 0;
if (!amduat_asl_replay_parse_artifact_ref(&cur, out_ref) ||
!amduat_asl_replay_read_u64_le(&cur, &tombstone_logseq)) {
return false;
}
*out_logseq = tombstone_logseq;
return true;
}
static bool amduat_asl_replay_update_segment(
amduat_asl_replay_state_t *state,
const amduat_asl_segment_seal_t *seal) {
size_t i;
amduat_asl_segment_seal_t *next;
for (i = 0; i < state->segments_len; ++i) {
if (state->segments[i].segment_id == seal->segment_id) {
memcpy(state->segments[i].segment_hash, seal->segment_hash, 32);
return true;
}
}
next = (amduat_asl_segment_seal_t *)realloc(
state->segments,
(state->segments_len + 1u) * sizeof(*state->segments));
if (next == NULL) {
return false;
}
state->segments = next;
state->segments[state->segments_len] = *seal;
state->segments_len += 1u;
return true;
}
static bool amduat_asl_replay_add_tombstone(
amduat_asl_replay_state_t *state,
amduat_reference_t ref,
uint64_t logseq) {
amduat_asl_tombstone_entry_t *next;
amduat_reference_t stored;
if (!amduat_reference_clone(ref, &stored)) {
return false;
}
next = (amduat_asl_tombstone_entry_t *)realloc(
state->tombstones,
(state->tombstones_len + 1u) * sizeof(*state->tombstones));
if (next == NULL) {
amduat_reference_free(&stored);
return false;
}
state->tombstones = next;
state->tombstones[state->tombstones_len].ref = stored;
state->tombstones[state->tombstones_len].tombstone_logseq = logseq;
state->tombstones_len += 1u;
return true;
}
static void amduat_asl_replay_remove_tombstone(
amduat_asl_replay_state_t *state,
amduat_reference_t ref,
uint64_t tombstone_logseq) {
size_t i;
for (i = 0; i < state->tombstones_len; ++i) {
if (state->tombstones[i].tombstone_logseq != tombstone_logseq) {
continue;
}
if (!amduat_reference_eq(state->tombstones[i].ref, ref)) {
continue;
}
amduat_reference_free(&state->tombstones[i].ref);
if (i + 1u < state->tombstones_len) {
state->tombstones[i] = state->tombstones[state->tombstones_len - 1u];
}
state->tombstones_len -= 1u;
return;
}
}
bool amduat_asl_replay_init(amduat_asl_replay_state_t *out) {
if (out == NULL) {
return false;
}
out->segments = NULL;
out->segments_len = 0;
out->tombstones = NULL;
out->tombstones_len = 0;
out->state.snapshot_id = 0;
out->state.log_position = 0;
return true;
}
void amduat_asl_replay_free(amduat_asl_replay_state_t *state) {
size_t i;
if (state == NULL) {
return;
}
free(state->segments);
state->segments = NULL;
state->segments_len = 0;
if (state->tombstones != NULL) {
for (i = 0; i < state->tombstones_len; ++i) {
amduat_reference_free(&state->tombstones[i].ref);
}
}
free(state->tombstones);
state->tombstones = NULL;
state->tombstones_len = 0;
state->state.snapshot_id = 0;
state->state.log_position = 0;
}
bool amduat_asl_replay_apply_log(
const amduat_asl_log_record_t *records,
size_t record_count,
amduat_asl_log_position_t log_position,
amduat_asl_replay_state_t *state) {
size_t i;
if (state == NULL) {
return false;
}
if (record_count != 0 && records == NULL) {
return false;
}
for (i = 0; i < record_count; ++i) {
const amduat_asl_log_record_t *record = &records[i];
amduat_asl_segment_seal_t seal;
amduat_reference_t ref;
uint64_t tombstone_logseq;
if (record->logseq > log_position) {
break;
}
switch (record->record_type) {
case AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL:
if (!amduat_asl_replay_parse_segment_seal(record->payload, &seal)) {
return false;
}
if (!amduat_asl_replay_update_segment(state, &seal)) {
return false;
}
break;
case AMDUAT_ASL_LOG_RECORD_TOMBSTONE:
if (!amduat_asl_replay_parse_tombstone(record->payload, &ref)) {
return false;
}
if (!amduat_asl_replay_add_tombstone(state, ref, record->logseq)) {
return false;
}
break;
case AMDUAT_ASL_LOG_RECORD_TOMBSTONE_LIFT:
if (!amduat_asl_replay_parse_tombstone_lift(record->payload,
&ref,
&tombstone_logseq)) {
return false;
}
amduat_asl_replay_remove_tombstone(state, ref, tombstone_logseq);
break;
default:
break;
}
}
state->state.log_position = log_position;
return true;
}

291
tests/asl/test_asl_replay.c Normal file
View file

@ -0,0 +1,291 @@
#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;
}

View file

@ -0,0 +1,203 @@
#include "amduat/asl/asl_store_index_fs.h"
#include "amduat/asl/store.h"
#include "amduat/enc/asl1_core.h"
#include "amduat/hash/asl1.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 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_store_index_fs_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 int test_round_trip(void) {
amduat_asl_store_config_t config;
amduat_asl_store_index_fs_t fs;
amduat_asl_store_t store;
amduat_asl_store_error_t err;
amduat_asl_index_state_t state;
amduat_asl_index_state_t current_state;
amduat_artifact_t artifact;
amduat_artifact_t loaded;
amduat_reference_t ref;
uint8_t payload[6];
char *root;
int exit_code = 1;
root = make_temp_root();
if (root == NULL) {
fprintf(stderr, "temp root failed\n");
return 1;
}
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);
memset(payload, 0x5a, sizeof(payload));
artifact = amduat_artifact(amduat_octets(payload, sizeof(payload)));
ref = amduat_reference(0u, amduat_octets(NULL, 0u));
err = amduat_asl_store_put_indexed(&store, artifact, &ref, &state);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "put_indexed failed: %d\n", err);
goto cleanup;
}
if (!amduat_asl_index_current_state(&store, &current_state)) {
fprintf(stderr, "current_state failed\n");
goto cleanup;
}
if (current_state.log_position != state.log_position) {
fprintf(stderr, "log position mismatch\n");
goto cleanup;
}
loaded = amduat_artifact(amduat_octets(NULL, 0u));
err = amduat_asl_store_get_indexed(&store, ref, state, &loaded);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "get_indexed failed: %d\n", err);
goto cleanup;
}
if (!amduat_artifact_eq(artifact, loaded)) {
fprintf(stderr, "artifact mismatch\n");
amduat_artifact_free(&loaded);
goto cleanup;
}
amduat_artifact_free(&loaded);
if (state.log_position > 0u) {
amduat_asl_index_state_t cutoff = state;
cutoff.log_position = state.log_position - 1u;
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, "cutoff lookup expected not found: %d\n", err);
amduat_artifact_free(&loaded);
goto cleanup;
}
}
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;
}
int main(void) {
return test_round_trip();
}