diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a1aaec..ff2cf2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ set(AMDUAT_ASL_SRCS src/kernel/asl/core.c src/near_core/asl/artifact_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/ref_io.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 ) +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 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) 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_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_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 tests/pel/test_pel_program_dag_exec.c) target_include_directories(amduat_test_pel_program_dag_exec diff --git a/include/amduat/asl/asl_store_index_fs.h b/include/amduat/asl/asl_store_index_fs.h new file mode 100644 index 0000000..b1e275d --- /dev/null +++ b/include/amduat/asl/asl_store_index_fs.h @@ -0,0 +1,30 @@ +#ifndef AMDUAT_ASL_STORE_INDEX_FS_H +#define AMDUAT_ASL_STORE_INDEX_FS_H + +#include "amduat/asl/store.h" + +#include +#include + +#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 */ diff --git a/include/amduat/asl/index_replay.h b/include/amduat/asl/index_replay.h new file mode 100644 index 0000000..2fa920d --- /dev/null +++ b/include/amduat/asl/index_replay.h @@ -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 +#include +#include + +#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 */ 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 new file mode 100644 index 0000000..434ed96 --- /dev/null +++ b/src/adapters/asl_store_index_fs/asl_store_index_fs.c @@ -0,0 +1,1446 @@ +#include "amduat/asl/asl_store_index_fs.h" + +#include "asl_store_index_fs_layout.h" +#include "amduat/asl/index_replay.h" +#include "amduat/asl/ref_derive.h" +#include "amduat/enc/asl1_core.h" +#include "amduat/enc/asl1_core_codec.h" +#include "amduat/enc/asl_core_index.h" +#include "amduat/enc/asl_log.h" +#include "amduat/hash/asl1.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef O_DIRECTORY +#define O_DIRECTORY 0 +#endif + +enum { + AMDUAT_ASL_STORE_INDEX_FS_MIN_DIGEST_BYTES = 2, + AMDUAT_ASL_STORE_INDEX_FS_SEGMENT_HASH_LEN = 32 +}; + +typedef enum { + AMDUAT_ASL_STORE_INDEX_FS_WRITE_OK = 0, + AMDUAT_ASL_STORE_INDEX_FS_WRITE_EXIST = 1, + AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR = 2 +} amduat_asl_store_index_fs_write_status_t; + +typedef enum { + AMDUAT_ASL_STORE_INDEX_FS_READ_OK = 0, + AMDUAT_ASL_STORE_INDEX_FS_READ_NOT_FOUND = 1, + AMDUAT_ASL_STORE_INDEX_FS_READ_IO = 2, + AMDUAT_ASL_STORE_INDEX_FS_READ_CORRUPT = 3 +} amduat_asl_store_index_fs_read_status_t; + +static bool amduat_asl_store_index_fs_ensure_directory(const char *path) { + struct stat st; + + if (path == NULL || path[0] == '\0') { + return false; + } + if (stat(path, &st) == 0) { + return S_ISDIR(st.st_mode); + } + if (errno != ENOENT) { + return false; + } + if (mkdir(path, 0755) != 0) { + if (errno == EEXIST) { + return stat(path, &st) == 0 && S_ISDIR(st.st_mode); + } + return false; + } + return true; +} + +static bool amduat_asl_store_index_fs_fsync_directory(const char *path) { + int fd; + int fsync_errno; + + if (path == NULL || path[0] == '\0') { + return false; + } + + fd = open(path, O_RDONLY | O_DIRECTORY); + if (fd < 0) { + return false; + } + while (fsync(fd) != 0) { + if (errno == EINTR) { + continue; + } + fsync_errno = errno; + close(fd); + errno = fsync_errno; + return false; + } + if (close(fd) != 0) { + return false; + } + return true; +} + +static amduat_asl_store_index_fs_read_status_t +amduat_asl_store_index_fs_read_file(const char *path, + uint8_t **out_bytes, + size_t *out_size) { + struct stat st; + size_t file_size; + uint8_t *buffer; + size_t total_read; + int fd; + + if (path == NULL || out_bytes == NULL || out_size == NULL) { + return AMDUAT_ASL_STORE_INDEX_FS_READ_IO; + } + *out_bytes = NULL; + *out_size = 0; + + if (stat(path, &st) != 0) { + if (errno == ENOENT || errno == ENOTDIR) { + return AMDUAT_ASL_STORE_INDEX_FS_READ_NOT_FOUND; + } + return AMDUAT_ASL_STORE_INDEX_FS_READ_IO; + } + if (!S_ISREG(st.st_mode)) { + return AMDUAT_ASL_STORE_INDEX_FS_READ_CORRUPT; + } + if (st.st_size <= 0) { + return AMDUAT_ASL_STORE_INDEX_FS_READ_CORRUPT; + } + if ((uintmax_t)st.st_size > SIZE_MAX) { + return AMDUAT_ASL_STORE_INDEX_FS_READ_CORRUPT; + } + + file_size = (size_t)st.st_size; + buffer = (uint8_t *)malloc(file_size); + if (buffer == NULL) { + return AMDUAT_ASL_STORE_INDEX_FS_READ_IO; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + free(buffer); + if (errno == ENOENT || errno == ENOTDIR) { + return AMDUAT_ASL_STORE_INDEX_FS_READ_NOT_FOUND; + } + return AMDUAT_ASL_STORE_INDEX_FS_READ_IO; + } + + total_read = 0u; + while (total_read < file_size) { + ssize_t rc = read(fd, buffer + total_read, file_size - total_read); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + close(fd); + free(buffer); + return AMDUAT_ASL_STORE_INDEX_FS_READ_IO; + } + if (rc == 0) { + close(fd); + free(buffer); + return AMDUAT_ASL_STORE_INDEX_FS_READ_CORRUPT; + } + total_read += (size_t)rc; + } + + if (close(fd) != 0) { + free(buffer); + return AMDUAT_ASL_STORE_INDEX_FS_READ_IO; + } + + *out_bytes = buffer; + *out_size = file_size; + return AMDUAT_ASL_STORE_INDEX_FS_READ_OK; +} + +static amduat_asl_store_index_fs_write_status_t +amduat_asl_store_index_fs_write_atomic(const char *temp_dir, + const char *final_path, + const uint8_t *bytes, + size_t size) { + static const char suffix[] = "tmp.XXXXXX"; + size_t temp_len; + bool need_sep; + size_t template_len; + char *template_path; + int temp_fd; + size_t written; + + if (temp_dir == NULL || final_path == NULL) { + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + if (size != 0u && bytes == NULL) { + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + temp_len = strlen(temp_dir); + if (temp_len == 0u) { + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + need_sep = temp_dir[temp_len - 1u] != '/'; + template_len = temp_len + (need_sep ? 1u : 0u) + sizeof(suffix); + + template_path = (char *)malloc(template_len); + if (template_path == NULL) { + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + if (need_sep) { + snprintf(template_path, template_len, "%s/%s", temp_dir, suffix); + } else { + snprintf(template_path, template_len, "%s%s", temp_dir, suffix); + } + + temp_fd = mkstemp(template_path); + if (temp_fd < 0) { + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + written = 0u; + while (written < size) { + ssize_t rc = write(temp_fd, bytes + written, size - written); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + close(temp_fd); + unlink(template_path); + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + written += (size_t)rc; + } + + if (fsync(temp_fd) != 0) { + close(temp_fd); + unlink(template_path); + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + if (close(temp_fd) != 0) { + unlink(template_path); + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + if (link(template_path, final_path) != 0) { + const int link_errno = errno; + unlink(template_path); + free(template_path); + if (link_errno == EEXIST) { + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_EXIST; + } + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + unlink(template_path); + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_OK; +} + +static amduat_asl_store_index_fs_write_status_t +amduat_asl_store_index_fs_write_replace(const char *temp_dir, + const char *final_path, + const uint8_t *bytes, + size_t size) { + static const char suffix[] = "tmp.XXXXXX"; + size_t temp_len; + bool need_sep; + size_t template_len; + char *template_path; + int temp_fd; + size_t written; + + if (temp_dir == NULL || final_path == NULL) { + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + if (size != 0u && bytes == NULL) { + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + temp_len = strlen(temp_dir); + if (temp_len == 0u) { + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + need_sep = temp_dir[temp_len - 1u] != '/'; + template_len = temp_len + (need_sep ? 1u : 0u) + sizeof(suffix); + + template_path = (char *)malloc(template_len); + if (template_path == NULL) { + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + if (need_sep) { + snprintf(template_path, template_len, "%s/%s", temp_dir, suffix); + } else { + snprintf(template_path, template_len, "%s%s", temp_dir, suffix); + } + + temp_fd = mkstemp(template_path); + if (temp_fd < 0) { + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + written = 0u; + while (written < size) { + ssize_t rc = write(temp_fd, bytes + written, size - written); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + close(temp_fd); + unlink(template_path); + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + written += (size_t)rc; + } + + if (fsync(temp_fd) != 0) { + close(temp_fd); + unlink(template_path); + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + if (close(temp_fd) != 0) { + unlink(template_path); + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + if (rename(template_path, final_path) != 0) { + unlink(template_path); + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_ERR; + } + + free(template_path); + return AMDUAT_ASL_STORE_INDEX_FS_WRITE_OK; +} + +static void amduat_asl_store_index_fs_fill_index_state( + amduat_asl_index_state_t *out_state, + uint64_t log_position) { + if (out_state == NULL) { + return; + } + out_state->snapshot_id = 0u; + out_state->log_position = log_position; +} + +static void amduat_asl_store_index_fs_log_free( + amduat_asl_log_record_t *records, + size_t record_count) { + if (records == NULL) { + return; + } + if (record_count == 0u) { + free(records); + return; + } + amduat_enc_asl_log_free(records, record_count); +} + +static bool amduat_asl_store_index_fs_prepare_dirs( + const char *root_path, + char **out_index_path, + char **out_segments_path, + char **out_blocks_path) { + if (!amduat_asl_store_index_fs_layout_build_index_path(root_path, + out_index_path)) { + return false; + } + if (!amduat_asl_store_index_fs_layout_build_segments_path(root_path, + out_segments_path)) { + free(*out_index_path); + *out_index_path = NULL; + return false; + } + if (!amduat_asl_store_index_fs_layout_build_blocks_path(root_path, + out_blocks_path)) { + free(*out_index_path); + *out_index_path = NULL; + free(*out_segments_path); + *out_segments_path = NULL; + return false; + } + if (!amduat_asl_store_index_fs_ensure_directory(*out_index_path) || + !amduat_asl_store_index_fs_ensure_directory(*out_segments_path) || + !amduat_asl_store_index_fs_ensure_directory(*out_blocks_path)) { + return false; + } + return true; +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_load_log( + const char *log_path, + amduat_asl_log_record_t **out_records, + size_t *out_count) { + uint8_t *log_bytes; + size_t log_len; + amduat_asl_store_index_fs_read_status_t status; + + if (out_records == NULL || out_count == NULL) { + return AMDUAT_ASL_STORE_ERR_IO; + } + *out_records = NULL; + *out_count = 0u; + + log_bytes = NULL; + log_len = 0u; + status = amduat_asl_store_index_fs_read_file(log_path, &log_bytes, &log_len); + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_NOT_FOUND) { + return AMDUAT_ASL_STORE_OK; + } + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_IO) { + return AMDUAT_ASL_STORE_ERR_IO; + } + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_CORRUPT) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (status != AMDUAT_ASL_STORE_INDEX_FS_READ_OK) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (!amduat_enc_asl_log_decode_v1(amduat_octets(log_bytes, log_len), + out_records, out_count)) { + free(log_bytes); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + free(log_bytes); + return AMDUAT_ASL_STORE_OK; +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_write_log( + const char *log_path, + const char *log_dir, + amduat_asl_log_record_t *records, + size_t record_count) { + amduat_octets_t log_bytes; + amduat_asl_store_index_fs_write_status_t status; + + if (!amduat_enc_asl_log_encode_v1(records, record_count, &log_bytes)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + status = amduat_asl_store_index_fs_write_replace(log_dir, log_path, + log_bytes.data, + log_bytes.len); + amduat_octets_free(&log_bytes); + if (status == AMDUAT_ASL_STORE_INDEX_FS_WRITE_OK) { + return AMDUAT_ASL_STORE_OK; + } + return AMDUAT_ASL_STORE_ERR_IO; +} + +static bool amduat_asl_store_index_fs_parse_u64(const char *value, + uint64_t *out) { + char *endptr; + unsigned long long parsed; + + if (value == NULL || out == NULL) { + return false; + } + + errno = 0; + parsed = strtoull(value, &endptr, 10); + if (errno != 0 || endptr == value || *endptr != '\0') { + return false; + } + if (parsed > UINT64_MAX) { + return false; + } + *out = (uint64_t)parsed; + return true; +} + +static amduat_asl_store_error_t +amduat_asl_store_index_fs_read_next_segment_id(const char *root_path, + uint64_t *out_segment_id) { + char *meta_path; + uint8_t *meta_bytes; + char *meta_str; + size_t meta_len; + amduat_asl_store_index_fs_read_status_t status; + uint64_t next_id; + size_t trimmed_len; + + if (out_segment_id == NULL) { + return AMDUAT_ASL_STORE_ERR_IO; + } + *out_segment_id = 0u; + + if (!amduat_asl_store_index_fs_layout_build_segment_meta_path(root_path, + &meta_path)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + meta_bytes = NULL; + meta_len = 0u; + status = amduat_asl_store_index_fs_read_file(meta_path, &meta_bytes, + &meta_len); + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_NOT_FOUND) { + free(meta_path); + *out_segment_id = 1u; + return AMDUAT_ASL_STORE_OK; + } + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_IO) { + free(meta_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_CORRUPT) { + free(meta_path); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (status != AMDUAT_ASL_STORE_INDEX_FS_READ_OK) { + free(meta_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (meta_len == 0u) { + free(meta_path); + free(meta_bytes); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + meta_str = (char *)malloc(meta_len + 1u); + if (meta_str == NULL) { + free(meta_path); + free(meta_bytes); + return AMDUAT_ASL_STORE_ERR_IO; + } + memcpy(meta_str, meta_bytes, meta_len); + meta_str[meta_len] = '\0'; + trimmed_len = meta_len; + while (trimmed_len > 0u) { + char c = meta_str[trimmed_len - 1u]; + if (c != '\n' && c != '\r' && c != ' ' && c != '\t') { + break; + } + meta_str[trimmed_len - 1u] = '\0'; + trimmed_len -= 1u; + } + if (trimmed_len == 0u || + !amduat_asl_store_index_fs_parse_u64(meta_str, &next_id)) { + free(meta_path); + free(meta_bytes); + free(meta_str); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (next_id == 0u) { + free(meta_path); + free(meta_bytes); + free(meta_str); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + *out_segment_id = next_id; + free(meta_path); + free(meta_bytes); + free(meta_str); + return AMDUAT_ASL_STORE_OK; +} + +static amduat_asl_store_error_t +amduat_asl_store_index_fs_write_next_segment_id(const char *root_path, + uint64_t next_id) { + char *meta_path; + char *segments_path; + char buffer[32]; + int len; + amduat_asl_store_index_fs_write_status_t status; + + if (next_id == 0u) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (!amduat_asl_store_index_fs_layout_build_segment_meta_path(root_path, + &meta_path)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + if (!amduat_asl_store_index_fs_layout_build_segments_path(root_path, + &segments_path)) { + free(meta_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + len = snprintf(buffer, sizeof(buffer), "%" PRIu64, next_id); + if (len <= 0 || (size_t)len >= sizeof(buffer)) { + free(meta_path); + free(segments_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + status = amduat_asl_store_index_fs_write_replace( + segments_path, meta_path, (const uint8_t *)buffer, (size_t)len); + free(meta_path); + if (status == AMDUAT_ASL_STORE_INDEX_FS_WRITE_OK) { + if (!amduat_asl_store_index_fs_fsync_directory(segments_path)) { + free(segments_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + free(segments_path); + return AMDUAT_ASL_STORE_OK; + } + free(segments_path); + return AMDUAT_ASL_STORE_ERR_IO; +} + +static bool amduat_asl_store_index_fs_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 amduat_asl_store_error_t amduat_asl_store_index_fs_load_segment( + const char *segment_path, + amduat_asl_core_index_segment_t *out_segment, + uint8_t out_hash[AMDUAT_ASL_STORE_INDEX_FS_SEGMENT_HASH_LEN]) { + uint8_t *segment_bytes; + size_t segment_len; + amduat_asl_store_index_fs_read_status_t status; + + if (out_segment == NULL || out_hash == NULL) { + return AMDUAT_ASL_STORE_ERR_IO; + } + memset(out_hash, 0, AMDUAT_ASL_STORE_INDEX_FS_SEGMENT_HASH_LEN); + + segment_bytes = NULL; + segment_len = 0u; + status = amduat_asl_store_index_fs_read_file(segment_path, + &segment_bytes, + &segment_len); + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_NOT_FOUND) { + return AMDUAT_ASL_STORE_ERR_NOT_FOUND; + } + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_IO) { + return AMDUAT_ASL_STORE_ERR_IO; + } + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_CORRUPT) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (status != AMDUAT_ASL_STORE_INDEX_FS_READ_OK) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (!amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256, + amduat_octets(segment_bytes, segment_len), + out_hash, + AMDUAT_ASL_STORE_INDEX_FS_SEGMENT_HASH_LEN)) { + free(segment_bytes); + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (!amduat_enc_asl_core_index_decode_v1( + amduat_octets(segment_bytes, segment_len), out_segment)) { + free(segment_bytes); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + free(segment_bytes); + return AMDUAT_ASL_STORE_OK; +} + +static bool amduat_asl_store_index_fs_record_matches( + const amduat_asl_core_index_segment_t *segment, + const amduat_asl_index_record_t *record, + amduat_reference_t ref) { + size_t offset; + const uint8_t *digest; + + if (segment == NULL || record == NULL) { + return false; + } + if (record->hash_id != (uint32_t)ref.hash_id) { + return false; + } + if (record->digest_len != ref.digest.len) { + 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; + return memcmp(digest, ref.digest.data, record->digest_len) == 0; +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_read_extent_bytes( + const char *root_path, + const amduat_asl_extent_record_t *extent, + uint8_t *out_cursor, + size_t remaining) { + char *block_path; + uint8_t *block_bytes; + size_t block_len; + amduat_asl_store_index_fs_read_status_t status; + + if (extent == NULL || out_cursor == NULL) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (!amduat_asl_store_index_fs_layout_build_block_path(root_path, + extent->block_id, + &block_path)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + block_bytes = NULL; + block_len = 0u; + status = amduat_asl_store_index_fs_read_file(block_path, &block_bytes, + &block_len); + free(block_path); + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_NOT_FOUND) { + return AMDUAT_ASL_STORE_ERR_NOT_FOUND; + } + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_IO) { + return AMDUAT_ASL_STORE_ERR_IO; + } + if (status == AMDUAT_ASL_STORE_INDEX_FS_READ_CORRUPT) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (status != AMDUAT_ASL_STORE_INDEX_FS_READ_OK) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (extent->offset > block_len || + extent->length > block_len - extent->offset) { + free(block_bytes); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if ((size_t)extent->length > remaining) { + free(block_bytes); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + memcpy(out_cursor, block_bytes + extent->offset, extent->length); + free(block_bytes); + return AMDUAT_ASL_STORE_OK; +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_materialize_artifact( + const char *root_path, + const amduat_asl_core_index_segment_t *segment, + const amduat_asl_index_record_t *record, + amduat_artifact_t *out_artifact) { + uint8_t *artifact_bytes; + size_t artifact_len; + size_t extents_base; + size_t i; + size_t cursor; + + if (segment == NULL || record == NULL || out_artifact == NULL) { + return AMDUAT_ASL_STORE_ERR_IO; + } + if ((record->flags & AMDUAT_ASL_INDEX_FLAG_TOMBSTONE) != 0) { + return AMDUAT_ASL_STORE_ERR_NOT_FOUND; + } + if (record->extent_count == 0) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (record->total_length == 0) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + if (record->extents_offset < segment->header.extents_offset) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if ((record->extents_offset - segment->header.extents_offset) % + AMDUAT_ASL_CORE_INDEX_EXTENT_SIZE != + 0u) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + 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 AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + artifact_len = record->total_length; + artifact_bytes = (uint8_t *)malloc(artifact_len); + if (artifact_bytes == NULL) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + cursor = 0u; + for (i = 0; i < record->extent_count; ++i) { + const amduat_asl_extent_record_t *extent = + &segment->extents[extents_base + i]; + amduat_asl_store_error_t err = amduat_asl_store_index_fs_read_extent_bytes( + root_path, extent, artifact_bytes + cursor, artifact_len - cursor); + if (err != AMDUAT_ASL_STORE_OK) { + free(artifact_bytes); + return err; + } + cursor += extent->length; + } + if (cursor != artifact_len) { + free(artifact_bytes); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + if (!amduat_enc_asl1_core_decode_artifact_v1( + amduat_octets(artifact_bytes, artifact_len), out_artifact)) { + free(artifact_bytes); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + free(artifact_bytes); + return AMDUAT_ASL_STORE_OK; +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_scan_segments( + const char *root_path, + const amduat_asl_replay_state_t *replay_state, + amduat_reference_t ref, + amduat_artifact_t *out_artifact) { + size_t i; + + if (replay_state == NULL || out_artifact == NULL) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (amduat_asl_store_index_fs_is_tombstoned(replay_state, ref)) { + return AMDUAT_ASL_STORE_ERR_NOT_FOUND; + } + + for (i = replay_state->segments_len; i > 0u; --i) { + const amduat_asl_segment_seal_t *seal = + &replay_state->segments[i - 1u]; + char *segment_path; + amduat_asl_core_index_segment_t segment; + uint8_t segment_hash[AMDUAT_ASL_STORE_INDEX_FS_SEGMENT_HASH_LEN]; + amduat_asl_store_error_t err; + size_t r; + + if (!amduat_asl_store_index_fs_layout_build_segment_path(root_path, + seal->segment_id, + &segment_path)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + err = amduat_asl_store_index_fs_load_segment(segment_path, + &segment, + segment_hash); + free(segment_path); + if (err != AMDUAT_ASL_STORE_OK) { + return err; + } + + if (memcmp(segment_hash, seal->segment_hash, + AMDUAT_ASL_STORE_INDEX_FS_SEGMENT_HASH_LEN) != 0) { + amduat_enc_asl_core_index_free(&segment); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + for (r = 0; r < segment.record_count; ++r) { + const amduat_asl_index_record_t *record = &segment.records[r]; + if (!amduat_asl_store_index_fs_record_matches(&segment, record, ref)) { + continue; + } + err = amduat_asl_store_index_fs_materialize_artifact(root_path, + &segment, + record, + out_artifact); + amduat_enc_asl_core_index_free(&segment); + return err; + } + + amduat_enc_asl_core_index_free(&segment); + } + + return AMDUAT_ASL_STORE_ERR_NOT_FOUND; +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_validate_config( + void *ctx, + amduat_asl_store_config_t config) { + const amduat_hash_asl1_desc_t *hash_desc; + + (void)ctx; + + if (config.encoding_profile_id != AMDUAT_ENC_ASL1_CORE_V1) { + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + hash_desc = amduat_hash_asl1_desc_lookup(config.hash_id); + if (hash_desc == NULL || hash_desc->digest_len == 0 || + hash_desc->impl.digest == NULL) { + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + if (hash_desc->digest_len < AMDUAT_ASL_STORE_INDEX_FS_MIN_DIGEST_BYTES) { + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + if ((hash_desc->digest_len % 8u) != 0u) { + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + if (hash_desc->digest_len > UINT16_MAX) { + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + + return AMDUAT_ASL_STORE_OK; +} + +static bool amduat_asl_store_index_fs_current_state_impl( + void *ctx, + amduat_asl_index_state_t *out_state) { + amduat_asl_store_index_fs_t *fs; + char *log_path; + amduat_asl_log_record_t *records; + size_t record_count; + uint64_t last_logseq; + amduat_asl_store_error_t err; + + if (ctx == NULL || out_state == NULL) { + return false; + } + fs = (amduat_asl_store_index_fs_t *)ctx; + + if (!amduat_asl_store_index_fs_layout_build_log_path(fs->root_path, + &log_path)) { + return false; + } + + records = NULL; + record_count = 0u; + err = amduat_asl_store_index_fs_load_log(log_path, &records, &record_count); + free(log_path); + if (err != AMDUAT_ASL_STORE_OK) { + amduat_enc_asl_log_free(records, record_count); + return false; + } + + last_logseq = 0u; + if (record_count != 0u) { + last_logseq = records[record_count - 1u].logseq; + } + + amduat_asl_store_index_fs_fill_index_state(out_state, last_logseq); + amduat_enc_asl_log_free(records, record_count); + return true; +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_get_indexed_impl( + void *ctx, + amduat_reference_t ref, + amduat_asl_index_state_t state, + amduat_artifact_t *out_artifact) { + amduat_asl_store_index_fs_t *fs; + const amduat_hash_asl1_desc_t *hash_desc; + char *log_path; + amduat_asl_log_record_t *records; + size_t record_count; + amduat_asl_replay_state_t replay_state; + amduat_asl_store_error_t err; + + if (ctx == NULL || out_artifact == NULL) { + return AMDUAT_ASL_STORE_ERR_IO; + } + fs = (amduat_asl_store_index_fs_t *)ctx; + + if (state.snapshot_id != 0u) { + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + if (fs->config.encoding_profile_id != AMDUAT_ENC_ASL1_CORE_V1) { + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + if (ref.hash_id != fs->config.hash_id) { + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + hash_desc = amduat_hash_asl1_desc_lookup(fs->config.hash_id); + if (hash_desc == NULL || hash_desc->digest_len == 0 || + hash_desc->impl.digest == NULL) { + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + if (ref.digest.len != hash_desc->digest_len || + (ref.digest.len != 0u && ref.digest.data == NULL)) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + if (!amduat_asl_store_index_fs_layout_build_log_path(fs->root_path, + &log_path)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + records = NULL; + record_count = 0u; + err = amduat_asl_store_index_fs_load_log(log_path, &records, &record_count); + free(log_path); + if (err != AMDUAT_ASL_STORE_OK) { + amduat_enc_asl_log_free(records, record_count); + return err; + } + + if (!amduat_asl_replay_init(&replay_state)) { + amduat_enc_asl_log_free(records, record_count); + return AMDUAT_ASL_STORE_ERR_IO; + } + if (!amduat_asl_replay_apply_log(records, record_count, state.log_position, + &replay_state)) { + amduat_enc_asl_log_free(records, record_count); + amduat_asl_replay_free(&replay_state); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + err = amduat_asl_store_index_fs_scan_segments(fs->root_path, + &replay_state, + ref, + out_artifact); + amduat_enc_asl_log_free(records, record_count); + amduat_asl_replay_free(&replay_state); + return err; +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_put_indexed_impl( + void *ctx, + amduat_artifact_t artifact, + amduat_reference_t *out_ref, + amduat_asl_index_state_t *out_state) { + amduat_asl_store_index_fs_t *fs; + const amduat_hash_asl1_desc_t *hash_desc; + amduat_reference_t derived_ref; + amduat_octets_t artifact_bytes; + amduat_asl_index_state_t current_state; + amduat_artifact_t existing_artifact; + amduat_asl_store_error_t err; + char *index_path; + char *segments_path; + char *blocks_path; + char *segment_path; + char *block_path; + char *log_path; + uint64_t segment_id; + amduat_asl_core_index_segment_t segment; + amduat_asl_index_record_t record; + amduat_asl_extent_record_t extent; + amduat_octets_t segment_bytes; + uint8_t segment_hash[AMDUAT_ASL_STORE_INDEX_FS_SEGMENT_HASH_LEN]; + amduat_asl_log_record_t *log_records; + size_t log_count; + uint8_t *seal_payload; + size_t seal_len; + uint64_t new_logseq; + amduat_asl_store_index_fs_write_status_t write_status; + + if (ctx == NULL || out_ref == NULL || out_state == NULL) { + return AMDUAT_ASL_STORE_ERR_IO; + } + fs = (amduat_asl_store_index_fs_t *)ctx; + + err = amduat_asl_store_index_fs_validate_config(ctx, fs->config); + if (err != AMDUAT_ASL_STORE_OK) { + return err; + } + + if (!amduat_asl_ref_derive(artifact, + fs->config.encoding_profile_id, + fs->config.hash_id, + &derived_ref, + &artifact_bytes)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (!amduat_asl_store_index_fs_current_state_impl(ctx, ¤t_state)) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + return AMDUAT_ASL_STORE_ERR_IO; + } + + existing_artifact = amduat_artifact(amduat_octets(NULL, 0u)); + err = amduat_asl_store_index_fs_get_indexed_impl(ctx, derived_ref, + current_state, + &existing_artifact); + if (err == AMDUAT_ASL_STORE_OK) { + amduat_artifact_free(&existing_artifact); + *out_ref = derived_ref; + amduat_asl_store_index_fs_fill_index_state(out_state, + current_state.log_position); + amduat_octets_free(&artifact_bytes); + return AMDUAT_ASL_STORE_OK; + } + if (err != AMDUAT_ASL_STORE_ERR_NOT_FOUND) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + return err; + } + + index_path = NULL; + segments_path = NULL; + blocks_path = NULL; + if (!amduat_asl_store_index_fs_prepare_dirs(fs->root_path, + &index_path, + &segments_path, + &blocks_path)) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + err = amduat_asl_store_index_fs_read_next_segment_id(fs->root_path, + &segment_id); + if (err != AMDUAT_ASL_STORE_OK) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return err; + } + if (segment_id == UINT64_MAX) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + err = amduat_asl_store_index_fs_write_next_segment_id(fs->root_path, + segment_id + 1u); + if (err != AMDUAT_ASL_STORE_OK) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return err; + } + + if (!amduat_asl_store_index_fs_layout_build_block_path(fs->root_path, + segment_id, + &block_path)) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + write_status = amduat_asl_store_index_fs_write_atomic( + blocks_path, block_path, artifact_bytes.data, artifact_bytes.len); + free(block_path); + if (write_status != AMDUAT_ASL_STORE_INDEX_FS_WRITE_OK) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + hash_desc = amduat_hash_asl1_desc_lookup(fs->config.hash_id); + if (hash_desc == NULL || hash_desc->digest_len == 0) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + if (artifact_bytes.len > UINT32_MAX) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; + } + + memset(&segment, 0, sizeof(segment)); + memset(&record, 0, sizeof(record)); + memset(&extent, 0, sizeof(extent)); + + segment.header.shard_id = 0u; + segment.header.snapshot_min = 0u; + segment.header.snapshot_max = 0u; + segment.header.segment_domain_id = 0u; + segment.header.segment_visibility = 0u; + segment.footer.seal_snapshot = 0u; + segment.footer.seal_time_ns = 0u; + + record.hash_id = fs->config.hash_id; + record.digest_len = (uint16_t)hash_desc->digest_len; + record.digest_offset = 0u; + record.extents_offset = 0u; + record.extent_count = 1u; + record.total_length = (uint32_t)artifact_bytes.len; + record.domain_id = 0u; + record.visibility = 0u; + record.has_cross_domain_source = 0u; + record.cross_domain_source = 0u; + record.flags = 0u; + + extent.block_id = segment_id; + extent.offset = 0u; + extent.length = (uint32_t)artifact_bytes.len; + + segment.records = &record; + segment.record_count = 1u; + segment.digests = amduat_octets(derived_ref.digest.data, + derived_ref.digest.len); + segment.extents = &extent; + segment.extent_count = 1u; + + segment_bytes = amduat_octets(NULL, 0u); + if (!amduat_enc_asl_core_index_encode_v1(&segment, &segment_bytes)) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (!amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256, + segment_bytes, + segment_hash, + sizeof(segment_hash))) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + amduat_octets_free(&segment_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (!amduat_asl_store_index_fs_layout_build_segment_path(fs->root_path, + segment_id, + &segment_path)) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + amduat_octets_free(&segment_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + write_status = amduat_asl_store_index_fs_write_atomic( + segments_path, segment_path, segment_bytes.data, segment_bytes.len); + free(segment_path); + amduat_octets_free(&segment_bytes); + if (write_status != AMDUAT_ASL_STORE_INDEX_FS_WRITE_OK) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (!amduat_asl_store_index_fs_layout_build_log_path(fs->root_path, + &log_path)) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + log_records = NULL; + log_count = 0u; + err = amduat_asl_store_index_fs_load_log(log_path, &log_records, &log_count); + if (err != AMDUAT_ASL_STORE_OK) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + amduat_asl_store_index_fs_log_free(log_records, log_count); + free(log_path); + free(index_path); + free(segments_path); + free(blocks_path); + return err; + } + + if (log_count != 0u && log_records == NULL) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(log_path); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (log_count == 0u) { + log_records = (amduat_asl_log_record_t *)calloc(1u, + sizeof(*log_records)); + if (log_records == NULL) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + free(log_path); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + } else { + amduat_asl_log_record_t *next = (amduat_asl_log_record_t *)realloc( + log_records, (log_count + 1u) * sizeof(*log_records)); + if (next == NULL) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + amduat_enc_asl_log_free(log_records, log_count); + free(log_path); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + log_records = next; + } + + seal_len = 8u + AMDUAT_ASL_STORE_INDEX_FS_SEGMENT_HASH_LEN; + seal_payload = (uint8_t *)malloc(seal_len); + if (seal_payload == NULL) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + amduat_asl_store_index_fs_log_free(log_records, log_count); + free(log_path); + free(index_path); + free(segments_path); + free(blocks_path); + return AMDUAT_ASL_STORE_ERR_IO; + } + + seal_payload[0] = (uint8_t)(segment_id & 0xffu); + seal_payload[1] = (uint8_t)((segment_id >> 8) & 0xffu); + seal_payload[2] = (uint8_t)((segment_id >> 16) & 0xffu); + seal_payload[3] = (uint8_t)((segment_id >> 24) & 0xffu); + seal_payload[4] = (uint8_t)((segment_id >> 32) & 0xffu); + seal_payload[5] = (uint8_t)((segment_id >> 40) & 0xffu); + seal_payload[6] = (uint8_t)((segment_id >> 48) & 0xffu); + seal_payload[7] = (uint8_t)((segment_id >> 56) & 0xffu); + memcpy(seal_payload + 8, segment_hash, sizeof(segment_hash)); + + if (log_count == 0u) { + new_logseq = 1u; + } else { + if (log_records[log_count - 1u].logseq == UINT64_MAX) { + amduat_reference_free(&derived_ref); + amduat_octets_free(&artifact_bytes); + amduat_asl_store_index_fs_log_free(log_records, log_count); + free(log_path); + free(index_path); + free(segments_path); + free(blocks_path); + free(seal_payload); + return AMDUAT_ASL_STORE_ERR_IO; + } + new_logseq = log_records[log_count - 1u].logseq + 1u; + } + log_records[log_count].logseq = new_logseq; + log_records[log_count].record_type = AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL; + log_records[log_count].payload = amduat_octets(seal_payload, seal_len); + memset(log_records[log_count].record_hash, 0, + sizeof(log_records[log_count].record_hash)); + log_count += 1u; + + err = amduat_asl_store_index_fs_write_log(log_path, index_path, + log_records, log_count); + amduat_asl_store_index_fs_log_free(log_records, log_count); + free(log_path); + free(index_path); + free(segments_path); + free(blocks_path); + amduat_octets_free(&artifact_bytes); + + if (err != AMDUAT_ASL_STORE_OK) { + amduat_reference_free(&derived_ref); + return err; + } + + *out_ref = derived_ref; + amduat_asl_store_index_fs_fill_index_state(out_state, new_logseq); + return AMDUAT_ASL_STORE_OK; +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_put_impl( + void *ctx, + amduat_artifact_t artifact, + amduat_reference_t *out_ref) { + amduat_asl_index_state_t state; + + return amduat_asl_store_index_fs_put_indexed_impl(ctx, artifact, out_ref, + &state); +} + +static amduat_asl_store_error_t amduat_asl_store_index_fs_get_impl( + void *ctx, + amduat_reference_t ref, + amduat_artifact_t *out_artifact) { + amduat_asl_index_state_t state; + + if (!amduat_asl_store_index_fs_current_state_impl(ctx, &state)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + return amduat_asl_store_index_fs_get_indexed_impl(ctx, ref, state, + out_artifact); +} + +bool amduat_asl_store_index_fs_init(amduat_asl_store_index_fs_t *fs, + amduat_asl_store_config_t config, + const char *root_path) { + size_t len; + + if (fs == NULL || root_path == NULL) { + return false; + } + len = strlen(root_path); + if (len == 0u || len >= AMDUAT_ASL_STORE_INDEX_FS_ROOT_MAX) { + return false; + } + memcpy(fs->root_path, root_path, len); + fs->root_path[len] = '\0'; + fs->config = config; + return true; +} + +amduat_asl_store_ops_t amduat_asl_store_index_fs_ops(void) { + amduat_asl_store_ops_t ops; + + amduat_asl_store_ops_init(&ops); + ops.put = amduat_asl_store_index_fs_put_impl; + ops.get = amduat_asl_store_index_fs_get_impl; + ops.put_indexed = amduat_asl_store_index_fs_put_indexed_impl; + ops.get_indexed = amduat_asl_store_index_fs_get_indexed_impl; + ops.current_state = amduat_asl_store_index_fs_current_state_impl; + ops.validate_config = amduat_asl_store_index_fs_validate_config; + return ops; +} diff --git a/src/adapters/asl_store_index_fs/asl_store_index_fs_layout.c b/src/adapters/asl_store_index_fs/asl_store_index_fs_layout.c new file mode 100644 index 0000000..f12cad4 --- /dev/null +++ b/src/adapters/asl_store_index_fs/asl_store_index_fs_layout.c @@ -0,0 +1,191 @@ +#include "asl_store_index_fs_layout.h" + +#include +#include +#include +#include +#include + +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; +} diff --git a/src/adapters/asl_store_index_fs/asl_store_index_fs_layout.h b/src/adapters/asl_store_index_fs/asl_store_index_fs_layout.h new file mode 100644 index 0000000..3587e93 --- /dev/null +++ b/src/adapters/asl_store_index_fs/asl_store_index_fs_layout.h @@ -0,0 +1,42 @@ +#ifndef AMDUAT_ASL_STORE_INDEX_FS_LAYOUT_H +#define AMDUAT_ASL_STORE_INDEX_FS_LAYOUT_H + +#include +#include +#include + +#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 */ diff --git a/src/near_core/asl/index_replay.c b/src/near_core/asl/index_replay.c new file mode 100644 index 0000000..6eedbbb --- /dev/null +++ b/src/near_core/asl/index_replay.c @@ -0,0 +1,313 @@ +#include "amduat/asl/index_replay.h" + +#include +#include +#include + +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; +} diff --git a/tests/asl/test_asl_replay.c b/tests/asl/test_asl_replay.c new file mode 100644 index 0000000..ad78991 --- /dev/null +++ b/tests/asl/test_asl_replay.c @@ -0,0 +1,291 @@ +#include "amduat/asl/index_replay.h" +#include "amduat/hash/asl1.h" + +#include +#include +#include +#include + +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; +} diff --git a/tests/asl/test_asl_store_index_fs.c b/tests/asl/test_asl_store_index_fs.c new file mode 100644 index 0000000..395a43d --- /dev/null +++ b/tests/asl/test_asl_store_index_fs.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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, ¤t_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(); +}