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 index 7a969df..a1ac010 100644 --- a/src/adapters/asl_store_index_fs/asl_store_index_fs.c +++ b/src/adapters/asl_store_index_fs/asl_store_index_fs.c @@ -4089,6 +4089,29 @@ static amduat_asl_store_error_t amduat_asl_store_index_fs_get_indexed_impl( return AMDUAT_ASL_STORE_ERR_INTEGRITY; } + if (state.snapshot_id != 0u) { + /* + * Fast path: when the requested state is exactly a snapshot anchor, + * snapshot metadata is already a complete replay state. + */ + if (amduat_asl_store_index_fs_load_snapshot_replay(fs->root_path, + state.snapshot_id, + NULL, + &replay_state, + &anchor_logseq)) { + if (replay_state.state.log_position == anchor_logseq && + state.log_position == anchor_logseq) { + err = amduat_asl_store_index_fs_scan_segments(fs, + &replay_state, + ref, + out_artifact); + amduat_asl_replay_free(&replay_state); + return err; + } + amduat_asl_replay_free(&replay_state); + } + } + if (!amduat_asl_store_index_fs_layout_build_log_path(fs->root_path, &log_path)) { return AMDUAT_ASL_STORE_ERR_IO; diff --git a/tests/asl/test_asl_store_index_fs.c b/tests/asl/test_asl_store_index_fs.c index 3367822..71c4c69 100644 --- a/tests/asl/test_asl_store_index_fs.c +++ b/tests/asl/test_asl_store_index_fs.c @@ -156,6 +156,24 @@ static bool read_file(const char *path, uint8_t **out_bytes, size_t *out_len) { return true; } +static bool write_file(const char *path, const uint8_t *bytes, size_t len) { + FILE *fp; + size_t written; + + if (path == NULL || (len != 0u && bytes == NULL)) { + return false; + } + fp = fopen(path, "wb"); + if (fp == NULL) { + return false; + } + written = fwrite(bytes, 1u, len, fp); + if (fclose(fp) != 0) { + return false; + } + return written == len; +} + static bool build_log_path(const char *root, char **out_path) { char *index_path = NULL; bool ok; @@ -725,6 +743,349 @@ cleanup: return exit_code; } +static int test_snapshot_fast_path_with_corrupt_log(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_artifact_t artifact; + amduat_artifact_t loaded; + amduat_reference_t ref; + uint8_t payload[10]; + char *root; + char *log_path = NULL; + static const uint8_t corrupt_log[] = {'B', 'A', 'D'}; + int exit_code = 1; + + root = make_temp_root(); + if (root == NULL) { + fprintf(stderr, "temp root failed\n"); + return 1; + } + + memset(&config, 0, sizeof(config)); + config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1; + config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256; + if (!amduat_asl_store_index_fs_init(&fs, config, root)) { + fprintf(stderr, "index fs init failed\n"); + goto cleanup; + } + amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs); + + memset(payload, 0x4c, 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; + } + err = amduat_asl_store_index_fs_snapshot_create(&fs, 1u, NULL, NULL); + if (err != AMDUAT_ASL_STORE_OK) { + fprintf(stderr, "snapshot create failed: %d\n", err); + goto cleanup; + } + if (!amduat_asl_index_current_state(&store, &state)) { + fprintf(stderr, "current_state failed\n"); + goto cleanup; + } + if (state.snapshot_id != 1u) { + fprintf(stderr, "expected snapshot_id=1\n"); + goto cleanup; + } + + if (!build_log_path(root, &log_path) || + !write_file(log_path, corrupt_log, sizeof(corrupt_log))) { + fprintf(stderr, "corrupt log write failed\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 with snapshot fast path failed: %d\n", err); + goto cleanup; + } + if (!amduat_artifact_eq(artifact, loaded)) { + fprintf(stderr, "artifact mismatch from snapshot fast path\n"); + amduat_artifact_free(&loaded); + goto cleanup; + } + amduat_artifact_free(&loaded); + + exit_code = 0; + +cleanup: + free(log_path); + amduat_reference_free(&ref); + if (!remove_tree(root)) { + fprintf(stderr, "cleanup failed\n"); + exit_code = 1; + } + free(root); + return exit_code; +} + +static int test_snapshot_fallback_replays_delta(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 snapshot_state; + amduat_asl_index_state_t fallback_state; + amduat_artifact_t artifact_a; + amduat_artifact_t loaded; + amduat_reference_t ref_a; + uint8_t payload_a[7]; + char *root; + int exit_code = 1; + + root = make_temp_root(); + if (root == NULL) { + fprintf(stderr, "temp root failed\n"); + return 1; + } + + memset(&config, 0, sizeof(config)); + config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1; + config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256; + if (!amduat_asl_store_index_fs_init(&fs, config, root)) { + fprintf(stderr, "index fs init failed\n"); + goto cleanup; + } + amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs); + + memset(payload_a, 0x11, sizeof(payload_a)); + artifact_a = amduat_artifact(amduat_octets(payload_a, sizeof(payload_a))); + ref_a = amduat_reference(0u, amduat_octets(NULL, 0u)); + + err = amduat_asl_store_put_indexed(&store, artifact_a, &ref_a, &snapshot_state); + if (err != AMDUAT_ASL_STORE_OK) { + fprintf(stderr, "put_indexed a failed: %d\n", err); + goto cleanup; + } + err = amduat_asl_store_index_fs_snapshot_create(&fs, 1u, NULL, NULL); + if (err != AMDUAT_ASL_STORE_OK) { + fprintf(stderr, "snapshot create failed: %d\n", err); + goto cleanup; + } + if (!amduat_asl_index_current_state(&store, &snapshot_state)) { + fprintf(stderr, "snapshot current_state failed\n"); + goto cleanup; + } + if (snapshot_state.log_position == 0u) { + fprintf(stderr, "snapshot log position was zero\n"); + goto cleanup; + } + fallback_state = snapshot_state; + fallback_state.log_position -= 1u; + + loaded = amduat_artifact(amduat_octets(NULL, 0u)); + err = amduat_asl_store_get_indexed(&store, ref_a, fallback_state, &loaded); + if (err != AMDUAT_ASL_STORE_ERR_INTEGRITY) { + fprintf(stderr, "fallback expected integrity: %d\n", err); + amduat_artifact_free(&loaded); + goto cleanup; + } + amduat_artifact_free(&loaded); + + exit_code = 0; + +cleanup: + amduat_reference_free(&ref_a); + if (!remove_tree(root)) { + fprintf(stderr, "cleanup failed\n"); + exit_code = 1; + } + free(root); + return exit_code; +} + +static int test_snapshot_fast_path_not_found_and_tombstoned(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 tombstone_state; + amduat_reference_t ref; + amduat_reference_t missing_ref; + amduat_artifact_t artifact; + amduat_artifact_t loaded; + uint8_t payload[8]; + uint8_t *missing_digest = NULL; + int exit_code = 1; + char *root; + + root = make_temp_root(); + if (root == NULL) { + fprintf(stderr, "temp root failed\n"); + return 1; + } + + memset(&config, 0, sizeof(config)); + config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1; + config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256; + if (!amduat_asl_store_index_fs_init(&fs, config, root)) { + fprintf(stderr, "index fs init failed\n"); + goto cleanup; + } + amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs); + + memset(payload, 0x66, sizeof(payload)); + artifact = amduat_artifact(amduat_octets(payload, sizeof(payload))); + ref = amduat_reference(0u, amduat_octets(NULL, 0u)); + missing_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; + } + err = amduat_asl_store_index_fs_snapshot_create(&fs, 1u, NULL, NULL); + if (err != AMDUAT_ASL_STORE_OK) { + fprintf(stderr, "snapshot create failed: %d\n", err); + goto cleanup; + } + if (!amduat_asl_index_current_state(&store, &state)) { + fprintf(stderr, "current_state failed\n"); + goto cleanup; + } + + missing_digest = (uint8_t *)malloc(ref.digest.len); + if (missing_digest == NULL || ref.digest.len == 0u) { + fprintf(stderr, "missing ref build failed\n"); + goto cleanup; + } + memcpy(missing_digest, ref.digest.data, ref.digest.len); + missing_digest[0] ^= 0xffu; + missing_ref.hash_id = ref.hash_id; + missing_ref.digest = amduat_octets(missing_digest, ref.digest.len); + loaded = amduat_artifact(amduat_octets(NULL, 0u)); + err = amduat_asl_store_get_indexed(&store, missing_ref, state, &loaded); + if (err != AMDUAT_ASL_STORE_ERR_NOT_FOUND) { + fprintf(stderr, "snapshot not found expected: %d\n", err); + amduat_artifact_free(&loaded); + goto cleanup; + } + amduat_artifact_free(&loaded); + + err = amduat_asl_store_tombstone(&store, ref, 1u, 2u, &tombstone_state); + if (err != AMDUAT_ASL_STORE_OK) { + fprintf(stderr, "tombstone failed: %d\n", err); + goto cleanup; + } + err = amduat_asl_store_index_fs_snapshot_create(&fs, 2u, NULL, NULL); + if (err != AMDUAT_ASL_STORE_OK) { + fprintf(stderr, "snapshot create after tombstone failed: %d\n", err); + goto cleanup; + } + if (!amduat_asl_index_current_state(&store, &state)) { + fprintf(stderr, "current_state after tombstone failed\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_ERR_NOT_FOUND) { + fprintf(stderr, "snapshot tombstoned expected not found: %d\n", err); + amduat_artifact_free(&loaded); + goto cleanup; + } + amduat_artifact_free(&loaded); + + exit_code = 0; + +cleanup: + amduat_reference_free(&ref); + amduat_reference_free(&missing_ref); + if (!remove_tree(root)) { + fprintf(stderr, "cleanup failed\n"); + exit_code = 1; + } + free(root); + return exit_code; +} + +static int test_snapshot_fast_path_integrity_error_unchanged(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_artifact_t artifact; + amduat_artifact_t loaded; + amduat_reference_t ref; + uint8_t payload[6]; + char *root; + char *segment_path = NULL; + static const uint8_t corrupt_segment[] = {'X', 'X', 'X'}; + int exit_code = 1; + + root = make_temp_root(); + if (root == NULL) { + fprintf(stderr, "temp root failed\n"); + return 1; + } + + memset(&config, 0, sizeof(config)); + config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1; + config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256; + if (!amduat_asl_store_index_fs_init(&fs, config, root)) { + fprintf(stderr, "index fs init failed\n"); + goto cleanup; + } + amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs); + + memset(payload, 0x31, 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; + } + err = amduat_asl_store_index_fs_snapshot_create(&fs, 1u, NULL, NULL); + if (err != AMDUAT_ASL_STORE_OK) { + fprintf(stderr, "snapshot create failed: %d\n", err); + goto cleanup; + } + if (!amduat_asl_index_current_state(&store, &state)) { + fprintf(stderr, "current_state failed\n"); + goto cleanup; + } + + if (!build_segment_path(root, 1u, &segment_path) || + !write_file(segment_path, corrupt_segment, sizeof(corrupt_segment))) { + fprintf(stderr, "segment corruption failed\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_ERR_INTEGRITY) { + fprintf(stderr, "expected integrity error, got: %d\n", err); + amduat_artifact_free(&loaded); + goto cleanup; + } + amduat_artifact_free(&loaded); + + exit_code = 0; + +cleanup: + free(segment_path); + amduat_reference_free(&ref); + if (!remove_tree(root)) { + fprintf(stderr, "cleanup failed\n"); + exit_code = 1; + } + free(root); + return exit_code; +} + static int test_segment_batch_packing(void) { amduat_asl_store_config_t config; amduat_asl_store_index_fs_t fs; @@ -1598,6 +1959,18 @@ int main(void) { if (test_round_trip() != 0) { return 1; } + if (test_snapshot_fast_path_with_corrupt_log() != 0) { + return 1; + } + if (test_snapshot_fallback_replays_delta() != 0) { + return 1; + } + if (test_snapshot_fast_path_not_found_and_tombstoned() != 0) { + return 1; + } + if (test_snapshot_fast_path_integrity_error_unchanged() != 0) { + return 1; + } if (test_snapshot_truncation() != 0) { return 1; }