#include "amduat/asl/index_snapshot.h" #include "amduat/asl/io.h" #include "amduat/hash/asl1.h" #include #include #include enum { AMDUAT_ASL_SNAPSHOT_MANIFEST_SEGMENT_SIZE = 40 }; static const uint8_t k_amduat_asl_snapshot_manifest_magic[ AMDUAT_ASL_SNAPSHOT_MANIFEST_MAGIC_LEN] = {'A', 'S', 'L', 'S', 'N', 'A', 'P', '1'}; static void amduat_asl_snapshot_store_u16_le(uint8_t *out, uint16_t value) { out[0] = (uint8_t)(value & 0xffu); out[1] = (uint8_t)((value >> 8) & 0xffu); } static void amduat_asl_snapshot_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 amduat_asl_snapshot_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 uint16_t amduat_asl_snapshot_load_u16_le(const uint8_t *data) { return (uint16_t)data[0] | ((uint16_t)data[1] << 8); } static uint32_t amduat_asl_snapshot_load_u32_le(const uint8_t *data) { return (uint32_t)data[0] | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); } static uint64_t amduat_asl_snapshot_load_u64_le(const uint8_t *data) { return (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); } static bool amduat_asl_snapshot_add_size(size_t *acc, size_t add) { if (*acc > SIZE_MAX - add) { return false; } *acc += add; return true; } static int amduat_asl_snapshot_compare_segments(const void *lhs, const void *rhs) { const amduat_asl_segment_seal_t *left = *(const amduat_asl_segment_seal_t *const *)lhs; const amduat_asl_segment_seal_t *right = *(const amduat_asl_segment_seal_t *const *)rhs; if (left->segment_id < right->segment_id) { return -1; } if (left->segment_id > right->segment_id) { return 1; } return memcmp(left->segment_hash, right->segment_hash, sizeof(left->segment_hash)); } static int amduat_asl_snapshot_compare_tombstones(const void *lhs, const void *rhs) { const amduat_asl_tombstone_entry_t *left = *(const amduat_asl_tombstone_entry_t *const *)lhs; const amduat_asl_tombstone_entry_t *right = *(const amduat_asl_tombstone_entry_t *const *)rhs; size_t min_len; int cmp; if (left->ref.hash_id < right->ref.hash_id) { return -1; } if (left->ref.hash_id > right->ref.hash_id) { return 1; } if (left->ref.digest.len < right->ref.digest.len) { return -1; } if (left->ref.digest.len > right->ref.digest.len) { return 1; } min_len = left->ref.digest.len; if (min_len != 0u) { cmp = memcmp(left->ref.digest.data, right->ref.digest.data, min_len); if (cmp != 0) { return cmp; } } if (left->tombstone_logseq < right->tombstone_logseq) { return -1; } if (left->tombstone_logseq > right->tombstone_logseq) { return 1; } return 0; } static bool amduat_asl_snapshot_hash_manifest(const uint8_t *bytes, size_t len, uint8_t out_hash[32]) { if (out_hash == NULL) { return true; } return amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256, amduat_octets(bytes, len), out_hash, 32); } void amduat_asl_snapshot_manifest_free( amduat_asl_snapshot_manifest_t *manifest) { size_t i; if (manifest == NULL) { return; } free(manifest->segments); manifest->segments = NULL; manifest->segments_len = 0; if (manifest->tombstones != NULL) { for (i = 0; i < manifest->tombstones_len; ++i) { amduat_reference_free(&manifest->tombstones[i].ref); } } free(manifest->tombstones); manifest->tombstones = NULL; manifest->tombstones_len = 0; } bool amduat_asl_snapshot_manifest_write( const char *path, const amduat_asl_snapshot_manifest_t *manifest, uint8_t out_hash[32]) { size_t i; size_t total_len; size_t offset; uint8_t *buffer; amduat_asl_segment_seal_t **segment_order; amduat_asl_tombstone_entry_t **tombstone_order; if (path == NULL || manifest == NULL) { return false; } if (manifest->segments_len != 0u && manifest->segments == NULL) { return false; } if (manifest->tombstones_len != 0u && manifest->tombstones == NULL) { return false; } if (manifest->segments_len > SIZE_MAX / AMDUAT_ASL_SNAPSHOT_MANIFEST_SEGMENT_SIZE) { return false; } total_len = 0; if (!amduat_asl_snapshot_add_size(&total_len, AMDUAT_ASL_SNAPSHOT_MANIFEST_HEADER_SIZE)) { return false; } if (!amduat_asl_snapshot_add_size( &total_len, manifest->segments_len * AMDUAT_ASL_SNAPSHOT_MANIFEST_SEGMENT_SIZE)) { return false; } for (i = 0; i < manifest->tombstones_len; ++i) { const amduat_asl_tombstone_entry_t *entry = &manifest->tombstones[i]; if (entry->ref.digest.len == 0 || (entry->ref.digest.len != 0u && entry->ref.digest.data == NULL)) { return false; } if (entry->ref.digest.len > UINT16_MAX) { return false; } if (!amduat_asl_snapshot_add_size(&total_len, 16u + entry->ref.digest.len)) { return false; } } buffer = (uint8_t *)malloc(total_len); if (buffer == NULL) { return false; } segment_order = NULL; tombstone_order = NULL; if (manifest->segments_len != 0u) { segment_order = (amduat_asl_segment_seal_t **)calloc( manifest->segments_len, sizeof(*segment_order)); if (segment_order == NULL) { free(buffer); return false; } for (i = 0; i < manifest->segments_len; ++i) { segment_order[i] = &manifest->segments[i]; } qsort(segment_order, manifest->segments_len, sizeof(*segment_order), amduat_asl_snapshot_compare_segments); } if (manifest->tombstones_len != 0u) { tombstone_order = (amduat_asl_tombstone_entry_t **)calloc( manifest->tombstones_len, sizeof(*tombstone_order)); if (tombstone_order == NULL) { free(segment_order); free(buffer); return false; } for (i = 0; i < manifest->tombstones_len; ++i) { tombstone_order[i] = &manifest->tombstones[i]; } qsort(tombstone_order, manifest->tombstones_len, sizeof(*tombstone_order), amduat_asl_snapshot_compare_tombstones); } offset = 0; memcpy(buffer + offset, k_amduat_asl_snapshot_manifest_magic, AMDUAT_ASL_SNAPSHOT_MANIFEST_MAGIC_LEN); offset += AMDUAT_ASL_SNAPSHOT_MANIFEST_MAGIC_LEN; amduat_asl_snapshot_store_u16_le(buffer + offset, AMDUAT_ASL_SNAPSHOT_MANIFEST_VERSION); offset += 2; amduat_asl_snapshot_store_u16_le(buffer + offset, 0); offset += 2; amduat_asl_snapshot_store_u32_le(buffer + offset, AMDUAT_ASL_SNAPSHOT_MANIFEST_HEADER_SIZE); offset += 4; amduat_asl_snapshot_store_u64_le(buffer + offset, manifest->snapshot_id); offset += 8; amduat_asl_snapshot_store_u64_le(buffer + offset, manifest->anchor_logseq); offset += 8; amduat_asl_snapshot_store_u64_le(buffer + offset, manifest->segments_len); offset += 8; amduat_asl_snapshot_store_u64_le(buffer + offset, manifest->tombstones_len); offset += 8; amduat_asl_snapshot_store_u32_le(buffer + offset, manifest->config.encoding_profile_id); offset += 4; amduat_asl_snapshot_store_u32_le(buffer + offset, manifest->config.hash_id); offset += 4; memset(buffer + offset, 0, AMDUAT_ASL_SNAPSHOT_MANIFEST_HEADER_SIZE - offset); offset = AMDUAT_ASL_SNAPSHOT_MANIFEST_HEADER_SIZE; for (i = 0; i < manifest->segments_len; ++i) { const amduat_asl_segment_seal_t *entry = segment_order[i]; amduat_asl_snapshot_store_u64_le(buffer + offset, entry->segment_id); offset += 8; memcpy(buffer + offset, entry->segment_hash, sizeof(entry->segment_hash)); offset += sizeof(entry->segment_hash); } for (i = 0; i < manifest->tombstones_len; ++i) { const amduat_asl_tombstone_entry_t *entry = tombstone_order[i]; amduat_asl_snapshot_store_u32_le(buffer + offset, entry->ref.hash_id); offset += 4; amduat_asl_snapshot_store_u16_le(buffer + offset, (uint16_t)entry->ref.digest.len); offset += 2; amduat_asl_snapshot_store_u16_le(buffer + offset, 0); offset += 2; memcpy(buffer + offset, entry->ref.digest.data, entry->ref.digest.len); offset += entry->ref.digest.len; amduat_asl_snapshot_store_u64_le(buffer + offset, entry->tombstone_logseq); offset += 8; } free(segment_order); free(tombstone_order); if (offset != total_len) { free(buffer); return false; } if (!amduat_asl_snapshot_hash_manifest(buffer, total_len, out_hash)) { free(buffer); return false; } if (!amduat_asl_write_path(path, buffer, total_len)) { free(buffer); return false; } free(buffer); return true; } bool amduat_asl_snapshot_manifest_read( const char *path, amduat_asl_snapshot_manifest_t *out_manifest, uint8_t out_hash[32]) { uint8_t *buffer; size_t len; size_t offset; uint16_t version; uint16_t reserved0; uint32_t header_size; uint64_t segment_count; uint64_t tombstone_count; size_t i; if (path == NULL || out_manifest == NULL) { return false; } memset(out_manifest, 0, sizeof(*out_manifest)); buffer = NULL; len = 0u; if (!amduat_asl_read_path(path, &buffer, &len)) { return false; } if (len < AMDUAT_ASL_SNAPSHOT_MANIFEST_HEADER_SIZE) { free(buffer); return false; } if (!amduat_asl_snapshot_hash_manifest(buffer, len, out_hash)) { free(buffer); return false; } if (memcmp(buffer, k_amduat_asl_snapshot_manifest_magic, AMDUAT_ASL_SNAPSHOT_MANIFEST_MAGIC_LEN) != 0) { free(buffer); return false; } offset = AMDUAT_ASL_SNAPSHOT_MANIFEST_MAGIC_LEN; version = amduat_asl_snapshot_load_u16_le(buffer + offset); offset += 2; reserved0 = amduat_asl_snapshot_load_u16_le(buffer + offset); offset += 2; header_size = amduat_asl_snapshot_load_u32_le(buffer + offset); offset += 4; if (version != AMDUAT_ASL_SNAPSHOT_MANIFEST_VERSION || reserved0 != 0 || header_size != AMDUAT_ASL_SNAPSHOT_MANIFEST_HEADER_SIZE) { free(buffer); return false; } out_manifest->snapshot_id = amduat_asl_snapshot_load_u64_le(buffer + offset); offset += 8; out_manifest->anchor_logseq = amduat_asl_snapshot_load_u64_le(buffer + offset); offset += 8; segment_count = amduat_asl_snapshot_load_u64_le(buffer + offset); offset += 8; tombstone_count = amduat_asl_snapshot_load_u64_le(buffer + offset); offset += 8; out_manifest->config.encoding_profile_id = amduat_asl_snapshot_load_u32_le(buffer + offset); offset += 4; out_manifest->config.hash_id = amduat_asl_snapshot_load_u32_le(buffer + offset); offset += 4; offset = AMDUAT_ASL_SNAPSHOT_MANIFEST_HEADER_SIZE; if (segment_count > SIZE_MAX || tombstone_count > SIZE_MAX) { free(buffer); return false; } out_manifest->segments_len = (size_t)segment_count; if (out_manifest->segments_len != 0u) { if (out_manifest->segments_len > SIZE_MAX / AMDUAT_ASL_SNAPSHOT_MANIFEST_SEGMENT_SIZE) { free(buffer); return false; } out_manifest->segments = (amduat_asl_segment_seal_t *)calloc( out_manifest->segments_len, sizeof(*out_manifest->segments)); if (out_manifest->segments == NULL) { free(buffer); return false; } } for (i = 0; i < out_manifest->segments_len; ++i) { amduat_asl_segment_seal_t *entry = &out_manifest->segments[i]; if (len - offset < AMDUAT_ASL_SNAPSHOT_MANIFEST_SEGMENT_SIZE) { amduat_asl_snapshot_manifest_free(out_manifest); free(buffer); return false; } entry->segment_id = amduat_asl_snapshot_load_u64_le(buffer + offset); offset += 8; memcpy(entry->segment_hash, buffer + offset, sizeof(entry->segment_hash)); offset += sizeof(entry->segment_hash); } out_manifest->tombstones_len = (size_t)tombstone_count; if (out_manifest->tombstones_len != 0u) { out_manifest->tombstones = (amduat_asl_tombstone_entry_t *)calloc( out_manifest->tombstones_len, sizeof(*out_manifest->tombstones)); if (out_manifest->tombstones == NULL) { amduat_asl_snapshot_manifest_free(out_manifest); free(buffer); return false; } } for (i = 0; i < out_manifest->tombstones_len; ++i) { amduat_asl_tombstone_entry_t *entry = &out_manifest->tombstones[i]; uint32_t hash_id; uint16_t digest_len; uint16_t tombstone_reserved; amduat_reference_t ref; uint64_t tombstone_logseq; if (len - offset < 16) { amduat_asl_snapshot_manifest_free(out_manifest); free(buffer); return false; } hash_id = amduat_asl_snapshot_load_u32_le(buffer + offset); offset += 4; if (hash_id > UINT16_MAX) { amduat_asl_snapshot_manifest_free(out_manifest); free(buffer); return false; } digest_len = amduat_asl_snapshot_load_u16_le(buffer + offset); offset += 2; tombstone_reserved = amduat_asl_snapshot_load_u16_le(buffer + offset); offset += 2; if (tombstone_reserved != 0 || digest_len == 0) { amduat_asl_snapshot_manifest_free(out_manifest); free(buffer); return false; } if (len - offset < (size_t)digest_len + 8u) { amduat_asl_snapshot_manifest_free(out_manifest); free(buffer); return false; } ref = amduat_reference((amduat_hash_id_t)hash_id, amduat_octets(buffer + offset, digest_len)); offset += digest_len; tombstone_logseq = amduat_asl_snapshot_load_u64_le(buffer + offset); offset += 8; if (!amduat_reference_clone(ref, &entry->ref)) { amduat_asl_snapshot_manifest_free(out_manifest); free(buffer); return false; } entry->tombstone_logseq = tombstone_logseq; } if (offset != len) { amduat_asl_snapshot_manifest_free(out_manifest); free(buffer); return false; } free(buffer); return true; }