From d7ac8264070297bae013baad6db39bd3c4800307 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sun, 21 Dec 2025 08:05:59 +0100 Subject: [PATCH] Add PEL queue with head tracking and tests --- CMakeLists.txt | 11 + include/amduat/pel/queue.h | 31 ++ src/pel_stack/queue/queue.c | 675 ++++++++++++++++++++++++++++++++++++ tests/pel/test_pel_queue.c | 346 ++++++++++++++++++ 4 files changed, 1063 insertions(+) create mode 100644 include/amduat/pel/queue.h create mode 100644 src/pel_stack/queue/queue.c create mode 100644 tests/pel/test_pel_queue.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b153bef..be4f9d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ set(AMDUAT_PEL_SRCS src/pel_stack/program_dag/program_dag_desc.c src/pel_stack/run.c src/pel_stack/trace_dag/trace_dag.c + src/pel_stack/queue/queue.c src/pel_stack/opreg/kernel.c src/pel_stack/opreg/kernel_params.c ) @@ -260,3 +261,13 @@ target_link_libraries(amduat_test_pel_surf_run PRIVATE amduat_pel ) add_test(NAME pel_surf_run COMMAND amduat_test_pel_surf_run) + +add_executable(amduat_test_pel_queue tests/pel/test_pel_queue.c) +target_include_directories(amduat_test_pel_queue + PRIVATE ${AMDUAT_INTERNAL_DIR} + PRIVATE ${AMDUAT_INCLUDE_DIR} +) +target_link_libraries(amduat_test_pel_queue + PRIVATE amduat_pel +) +add_test(NAME pel_queue COMMAND amduat_test_pel_queue) diff --git a/include/amduat/pel/queue.h b/include/amduat/pel/queue.h new file mode 100644 index 0000000..1fe7253 --- /dev/null +++ b/include/amduat/pel/queue.h @@ -0,0 +1,31 @@ +#ifndef AMDUAT_PEL_QUEUE_H +#define AMDUAT_PEL_QUEUE_H + +#include "amduat/asl/core.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool amduat_pel_queue_append_trace_ref(const char *root, + amduat_reference_t ref); + +bool amduat_pel_queue_read_next(const char *root, + uint64_t offset, + amduat_reference_t *out_ref, + uint64_t *out_next_offset); + +bool amduat_pel_queue_load_pel_head(const char *root, uint64_t *out_offset); +bool amduat_pel_queue_store_pel_head(const char *root, uint64_t offset); + +bool amduat_pel_queue_load_tgk_head(const char *root, uint64_t *out_offset); +bool amduat_pel_queue_store_tgk_head(const char *root, uint64_t offset); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_PEL_QUEUE_H */ diff --git a/src/pel_stack/queue/queue.c b/src/pel_stack/queue/queue.c new file mode 100644 index 0000000..16ef833 --- /dev/null +++ b/src/pel_stack/queue/queue.c @@ -0,0 +1,675 @@ +#include "amduat/pel/queue.h" + +#include "amduat/enc/asl1_core_codec.h" +#include "amduat/hash/asl1.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + const uint8_t *data; + size_t len; + size_t offset; +} amduat_cursor_t; + +typedef enum { + AMDUAT_PEL_QUEUE_READ_OK = 0, + AMDUAT_PEL_QUEUE_READ_NOT_FOUND = 1, + AMDUAT_PEL_QUEUE_READ_ERR = 2 +} amduat_pel_queue_read_status_t; + +enum { + AMDUAT_PEL_QUEUE_RECORD_VERSION = 1, + AMDUAT_PEL_QUEUE_MIN_RECORD_LEN = 1 + 4 + 2 +}; + +static const char k_pel_queue_path[] = "PEL_QUEUE"; +static const char k_pel_head_path[] = "PEL_HEAD"; +static const char k_tgk_head_path[] = "TGK_HEAD"; +static const char k_pel_head_magic[] = "amduat-pel-head-v1"; +static const char k_tgk_head_magic[] = "amduat-tgk-head-v1"; + +static void amduat_store_u16_be(uint8_t *out, uint16_t value) { + out[0] = (uint8_t)((value >> 8) & 0xffu); + out[1] = (uint8_t)(value & 0xffu); +} + +static void amduat_store_u32_be(uint8_t *out, uint32_t value) { + out[0] = (uint8_t)((value >> 24) & 0xffu); + out[1] = (uint8_t)((value >> 16) & 0xffu); + out[2] = (uint8_t)((value >> 8) & 0xffu); + out[3] = (uint8_t)(value & 0xffu); +} + +static uint32_t amduat_load_u32_be(const uint8_t *data) { + return ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | + ((uint32_t)data[2] << 8) | (uint32_t)data[3]; +} + +static bool amduat_read_u8(amduat_cursor_t *cur, uint8_t *out) { + if (cur->len - cur->offset < 1) { + return false; + } + *out = cur->data[cur->offset]; + cur->offset += 1; + return true; +} + +static bool amduat_read_u32(amduat_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] << 24) | ((uint32_t)data[1] << 16) | + ((uint32_t)data[2] << 8) | (uint32_t)data[3]; + cur->offset += 4; + return true; +} + +static bool amduat_add_size(size_t *acc, size_t add) { + if (*acc > SIZE_MAX - add) { + return false; + } + *acc += add; + return true; +} + +static bool amduat_reference_bytes_len(amduat_reference_t ref, + size_t *out_len) { + const amduat_hash_asl1_desc_t *desc; + + if (ref.digest.len != 0 && ref.digest.data == NULL) { + return false; + } + + desc = amduat_hash_asl1_desc_lookup(ref.hash_id); + if (desc == NULL || desc->digest_len == 0) { + return false; + } + if (ref.digest.len != desc->digest_len) { + return false; + } + + *out_len = 2 + desc->digest_len; + return true; +} + +static bool amduat_encoded_ref_len(amduat_reference_t ref, size_t *out_len) { + size_t ref_len; + + if (!amduat_reference_bytes_len(ref, &ref_len)) { + return false; + } + if (ref_len > UINT32_MAX) { + return false; + } + *out_len = 4 + ref_len; + return true; +} + +static bool amduat_write_encoded_ref(uint8_t *buffer, + size_t buffer_len, + size_t *offset, + amduat_reference_t ref) { + size_t ref_len; + + if (!amduat_reference_bytes_len(ref, &ref_len)) { + return false; + } + if (ref_len > UINT32_MAX) { + return false; + } + if (buffer_len - *offset < 4 + ref_len) { + return false; + } + + amduat_store_u32_be(buffer + *offset, (uint32_t)ref_len); + *offset += 4; + amduat_store_u16_be(buffer + *offset, ref.hash_id); + *offset += 2; + if (ref.digest.len != 0) { + memcpy(buffer + *offset, ref.digest.data, ref.digest.len); + *offset += ref.digest.len; + } + + return true; +} + +static bool amduat_read_encoded_ref(amduat_cursor_t *cur, + amduat_reference_t *out_ref) { + uint32_t ref_len_u32; + amduat_octets_t ref_bytes; + + if (!amduat_read_u32(cur, &ref_len_u32)) { + return false; + } + if (ref_len_u32 < 2) { + return false; + } + if (cur->len - cur->offset < ref_len_u32) { + return false; + } + ref_bytes = amduat_octets(cur->data + cur->offset, ref_len_u32); + if (!amduat_enc_asl1_core_decode_reference_v1(ref_bytes, out_ref)) { + return false; + } + cur->offset += ref_len_u32; + return true; +} + +static void amduat_reference_free(amduat_reference_t *ref) { + if (ref == NULL) { + return; + } + free((void *)ref->digest.data); + ref->digest.data = NULL; + ref->digest.len = 0; +} + +static bool amduat_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 amduat_write_all(int fd, const uint8_t *data, size_t len) { + size_t total = 0u; + + while (total < len) { + ssize_t rc = write(fd, data + total, len - total); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + return false; + } + total += (size_t)rc; + } + + return true; +} + +static bool amduat_read_full(int fd, + uint8_t *data, + size_t len, + size_t *out_read) { + size_t total = 0u; + + if (out_read == NULL) { + return false; + } + + while (total < len) { + ssize_t rc = read(fd, data + total, len - total); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + return false; + } + if (rc == 0) { + break; + } + total += (size_t)rc; + } + + *out_read = total; + return true; +} + +static amduat_pel_queue_read_status_t amduat_read_file(const char *path, + char **out_bytes, + size_t *out_size) { + struct stat st; + size_t file_size; + char *buffer; + size_t total_read; + int fd; + + if (path == NULL || out_bytes == NULL || out_size == NULL) { + return AMDUAT_PEL_QUEUE_READ_ERR; + } + *out_bytes = NULL; + *out_size = 0; + + if (stat(path, &st) != 0) { + if (errno == ENOENT || errno == ENOTDIR) { + return AMDUAT_PEL_QUEUE_READ_NOT_FOUND; + } + return AMDUAT_PEL_QUEUE_READ_ERR; + } + if (!S_ISREG(st.st_mode)) { + return AMDUAT_PEL_QUEUE_READ_ERR; + } + if (st.st_size <= 0) { + return AMDUAT_PEL_QUEUE_READ_ERR; + } + if ((uintmax_t)st.st_size > SIZE_MAX) { + return AMDUAT_PEL_QUEUE_READ_ERR; + } + + file_size = (size_t)st.st_size; + buffer = (char *)malloc(file_size + 1u); + if (buffer == NULL) { + return AMDUAT_PEL_QUEUE_READ_ERR; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + free(buffer); + if (errno == ENOENT || errno == ENOTDIR) { + return AMDUAT_PEL_QUEUE_READ_NOT_FOUND; + } + return AMDUAT_PEL_QUEUE_READ_ERR; + } + + 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_PEL_QUEUE_READ_ERR; + } + if (rc == 0) { + close(fd); + free(buffer); + return AMDUAT_PEL_QUEUE_READ_ERR; + } + total_read += (size_t)rc; + } + + if (close(fd) != 0) { + free(buffer); + return AMDUAT_PEL_QUEUE_READ_ERR; + } + + buffer[file_size] = '\0'; + *out_bytes = buffer; + *out_size = file_size; + return AMDUAT_PEL_QUEUE_READ_OK; +} + +static bool amduat_parse_head_text(const char *text, + const char *magic, + uint64_t *out_offset) { + size_t magic_len; + const char *cursor; + char *end; + unsigned long long value; + + if (text == NULL || magic == NULL || out_offset == NULL) { + return false; + } + + magic_len = strlen(magic); + if (strncmp(text, magic, magic_len) != 0) { + return false; + } + if (text[magic_len] != '\n') { + return false; + } + + cursor = text + magic_len + 1; + if (strncmp(cursor, "offset=", 7) != 0) { + return false; + } + cursor += 7; + if (*cursor == '\0') { + return false; + } + errno = 0; + value = strtoull(cursor, &end, 10); + if (errno != 0 || end == cursor) { + return false; + } + if (*end == '\n') { + end++; + } + if (*end != '\0') { + return false; + } + if (value > (unsigned long long)UINT64_MAX) { + return false; + } + + *out_offset = (uint64_t)value; + return true; +} + +static bool amduat_load_head(const char *root, + const char *path_segment, + const char *magic, + uint64_t *out_offset) { + char *path = NULL; + amduat_pel_queue_read_status_t read_status; + char *head_text = NULL; + size_t head_size = 0u; + bool ok; + + if (root == NULL || root[0] == '\0' || out_offset == NULL) { + return false; + } + + if (!amduat_join_path(root, path_segment, &path)) { + return false; + } + + read_status = amduat_read_file(path, &head_text, &head_size); + free(path); + (void)head_size; + if (read_status == AMDUAT_PEL_QUEUE_READ_NOT_FOUND) { + *out_offset = 0; + return true; + } + if (read_status != AMDUAT_PEL_QUEUE_READ_OK) { + return false; + } + + ok = amduat_parse_head_text(head_text, magic, out_offset); + free(head_text); + return ok; +} + +static bool amduat_store_head(const char *root, + const char *path_segment, + const char *magic, + uint64_t offset) { + char *path = NULL; + char buffer[128]; + int buffer_len; + int fd; + bool ok; + + if (root == NULL || root[0] == '\0') { + return false; + } + + if (!amduat_join_path(root, path_segment, &path)) { + return false; + } + + buffer_len = snprintf(buffer, + sizeof(buffer), + "%s\noffset=%" PRIu64 "\n", + magic, + offset); + if (buffer_len <= 0 || (size_t)buffer_len >= sizeof(buffer)) { + free(path); + return false; + } + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + free(path); + if (fd < 0) { + return false; + } + + ok = amduat_write_all(fd, (const uint8_t *)buffer, (size_t)buffer_len); + if (!ok) { + close(fd); + return false; + } + if (close(fd) != 0) { + return false; + } + + return true; +} + +bool amduat_pel_queue_append_trace_ref(const char *root, + amduat_reference_t ref) { + char *path = NULL; + size_t encoded_len; + size_t record_len; + size_t total_len; + uint8_t *buffer; + size_t offset; + int fd; + bool ok; + + if (root == NULL || root[0] == '\0') { + return false; + } + + if (!amduat_encoded_ref_len(ref, &encoded_len)) { + return false; + } + record_len = 1u; + if (!amduat_add_size(&record_len, encoded_len)) { + return false; + } + if (record_len > UINT32_MAX) { + return false; + } + total_len = 4u; + if (!amduat_add_size(&total_len, record_len)) { + return false; + } + + buffer = (uint8_t *)malloc(total_len); + if (buffer == NULL) { + return false; + } + + amduat_store_u32_be(buffer, (uint32_t)record_len); + offset = 4u; + buffer[offset++] = AMDUAT_PEL_QUEUE_RECORD_VERSION; + if (!amduat_write_encoded_ref(buffer, total_len, &offset, ref)) { + free(buffer); + return false; + } + if (offset != total_len) { + free(buffer); + return false; + } + + if (!amduat_join_path(root, k_pel_queue_path, &path)) { + free(buffer); + return false; + } + + fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0644); + free(path); + if (fd < 0) { + free(buffer); + return false; + } + + ok = amduat_write_all(fd, buffer, total_len); + free(buffer); + if (!ok) { + close(fd); + return false; + } + if (close(fd) != 0) { + return false; + } + + return true; +} + +bool amduat_pel_queue_read_next(const char *root, + uint64_t offset, + amduat_reference_t *out_ref, + uint64_t *out_next_offset) { + char *path = NULL; + int fd; + uint8_t len_bytes[4]; + size_t read_len; + uint32_t record_len_u32; + size_t record_len; + uint8_t *record_bytes; + amduat_cursor_t cur; + uint8_t version; + amduat_reference_t ref; + uint64_t next_offset; + + if (out_next_offset != NULL) { + *out_next_offset = offset; + } + + if (root == NULL || root[0] == '\0' || out_ref == NULL || + out_next_offset == NULL) { + return false; + } + + if (!amduat_join_path(root, k_pel_queue_path, &path)) { + return false; + } + + fd = open(path, O_RDONLY); + free(path); + if (fd < 0) { + if (errno == ENOENT || errno == ENOTDIR) { + return false; + } + return false; + } + + if (lseek(fd, (off_t)offset, SEEK_SET) == (off_t)-1) { + close(fd); + return false; + } + + if (!amduat_read_full(fd, len_bytes, sizeof(len_bytes), &read_len)) { + close(fd); + return false; + } + if (read_len < sizeof(len_bytes)) { + close(fd); + return false; + } + + record_len_u32 = amduat_load_u32_be(len_bytes); + if (record_len_u32 < AMDUAT_PEL_QUEUE_MIN_RECORD_LEN) { + close(fd); + return false; + } + if (record_len_u32 > SIZE_MAX) { + close(fd); + return false; + } + record_len = (size_t)record_len_u32; + record_bytes = (uint8_t *)malloc(record_len); + if (record_bytes == NULL) { + close(fd); + return false; + } + + if (!amduat_read_full(fd, record_bytes, record_len, &read_len)) { + free(record_bytes); + close(fd); + return false; + } + if (read_len < record_len) { + free(record_bytes); + close(fd); + return false; + } + + if (close(fd) != 0) { + free(record_bytes); + return false; + } + + cur.data = record_bytes; + cur.len = record_len; + cur.offset = 0u; + if (!amduat_read_u8(&cur, &version)) { + free(record_bytes); + return false; + } + if (version != AMDUAT_PEL_QUEUE_RECORD_VERSION) { + free(record_bytes); + return false; + } + + ref.hash_id = 0; + ref.digest = amduat_octets(NULL, 0); + if (!amduat_read_encoded_ref(&cur, &ref)) { + free(record_bytes); + return false; + } + if (cur.offset != cur.len) { + amduat_reference_free(&ref); + free(record_bytes); + return false; + } + + if (offset > UINT64_MAX - 4u - record_len_u32) { + amduat_reference_free(&ref); + free(record_bytes); + return false; + } + next_offset = offset + 4u + record_len_u32; + + *out_ref = ref; + *out_next_offset = next_offset; + free(record_bytes); + return true; +} + +bool amduat_pel_queue_load_pel_head(const char *root, uint64_t *out_offset) { + return amduat_load_head(root, k_pel_head_path, k_pel_head_magic, out_offset); +} + +bool amduat_pel_queue_store_pel_head(const char *root, uint64_t offset) { + return amduat_store_head(root, k_pel_head_path, k_pel_head_magic, offset); +} + +bool amduat_pel_queue_load_tgk_head(const char *root, uint64_t *out_offset) { + return amduat_load_head(root, k_tgk_head_path, k_tgk_head_magic, out_offset); +} + +bool amduat_pel_queue_store_tgk_head(const char *root, uint64_t offset) { + return amduat_store_head(root, k_tgk_head_path, k_tgk_head_magic, offset); +} diff --git a/tests/pel/test_pel_queue.c b/tests/pel/test_pel_queue.c new file mode 100644 index 0000000..ed5e1d8 --- /dev/null +++ b/tests/pel/test_pel_queue.c @@ -0,0 +1,346 @@ +#include "amduat/pel/queue.h" + +#include "amduat/hash/asl1.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void store_u32_be(uint8_t *out, uint32_t value) { + out[0] = (uint8_t)((value >> 24) & 0xffu); + out[1] = (uint8_t)((value >> 16) & 0xffu); + out[2] = (uint8_t)((value >> 8) & 0xffu); + out[3] = (uint8_t)(value & 0xffu); +} + +static void free_ref(amduat_reference_t *ref) { + if (ref == NULL) { + return; + } + free((void *)ref->digest.data); + ref->digest.data = NULL; + ref->digest.len = 0; +} + +static bool make_ref(amduat_hash_id_t hash_id, + uint8_t fill, + amduat_reference_t *out_ref) { + const amduat_hash_asl1_desc_t *desc; + uint8_t *digest; + + if (out_ref == NULL) { + return false; + } + desc = amduat_hash_asl1_desc_lookup(hash_id); + if (desc == NULL || desc->digest_len == 0) { + return false; + } + digest = (uint8_t *)malloc(desc->digest_len); + if (digest == NULL) { + return false; + } + memset(digest, fill, desc->digest_len); + *out_ref = amduat_reference(hash_id, amduat_octets(digest, desc->digest_len)); + return true; +} + +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 write_all(int fd, const uint8_t *data, size_t len) { + size_t total = 0u; + + while (total < len) { + ssize_t rc = write(fd, data + total, len - total); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + return false; + } + total += (size_t)rc; + } + return true; +} + +static char *make_temp_root(void) { + char *templ; + const char template_prefix[] = "amduat_test_pel_queue_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 void cleanup_root(const char *root) { + char *path = NULL; + + if (root == NULL) { + return; + } + + if (join_path(root, "PEL_QUEUE", &path)) { + unlink(path); + free(path); + } + if (join_path(root, "PEL_HEAD", &path)) { + unlink(path); + free(path); + } + if (join_path(root, "TGK_HEAD", &path)) { + unlink(path); + free(path); + } + rmdir(root); +} + +static int test_append_and_read(void) { + char *root = NULL; + amduat_reference_t ref1; + amduat_reference_t ref2; + amduat_reference_t out_ref; + uint64_t offset = 0; + uint64_t next_offset = 0; + int exit_code = 1; + + root = make_temp_root(); + if (root == NULL) { + fprintf(stderr, "temp root failed\n"); + return exit_code; + } + + if (!make_ref(AMDUAT_HASH_ASL1_ID_SHA256, 0x11u, &ref1) || + !make_ref(AMDUAT_HASH_ASL1_ID_SHA256, 0x22u, &ref2)) { + fprintf(stderr, "ref alloc failed\n"); + goto cleanup; + } + + if (!amduat_pel_queue_append_trace_ref(root, ref1) || + !amduat_pel_queue_append_trace_ref(root, ref2)) { + fprintf(stderr, "append failed\n"); + goto cleanup; + } + + if (!amduat_pel_queue_read_next(root, offset, &out_ref, &next_offset)) { + fprintf(stderr, "read first failed\n"); + goto cleanup; + } + if (!amduat_reference_eq(out_ref, ref1)) { + fprintf(stderr, "first ref mismatch\n"); + free_ref(&out_ref); + goto cleanup; + } + free_ref(&out_ref); + offset = next_offset; + + if (!amduat_pel_queue_read_next(root, offset, &out_ref, &next_offset)) { + fprintf(stderr, "read second failed\n"); + goto cleanup; + } + if (!amduat_reference_eq(out_ref, ref2)) { + fprintf(stderr, "second ref mismatch\n"); + free_ref(&out_ref); + goto cleanup; + } + free_ref(&out_ref); + offset = next_offset; + + if (amduat_pel_queue_read_next(root, offset, &out_ref, &next_offset)) { + fprintf(stderr, "expected end of queue\n"); + free_ref(&out_ref); + goto cleanup; + } + if (next_offset != offset) { + fprintf(stderr, "unexpected offset on empty read\n"); + goto cleanup; + } + + exit_code = 0; + +cleanup: + free_ref(&ref1); + free_ref(&ref2); + cleanup_root(root); + free(root); + return exit_code; +} + +static int test_truncated_tail(void) { + char *root = NULL; + char *queue_path = NULL; + amduat_reference_t ref; + amduat_reference_t out_ref; + uint64_t offset = 0; + uint64_t next_offset = 0; + uint8_t len_bytes[4]; + int fd; + int exit_code = 1; + + root = make_temp_root(); + if (root == NULL) { + fprintf(stderr, "temp root failed\n"); + return exit_code; + } + + if (!make_ref(AMDUAT_HASH_ASL1_ID_SHA256, 0x33u, &ref)) { + fprintf(stderr, "ref alloc failed\n"); + goto cleanup; + } + + if (!amduat_pel_queue_append_trace_ref(root, ref)) { + fprintf(stderr, "append failed\n"); + goto cleanup; + } + + if (!join_path(root, "PEL_QUEUE", &queue_path)) { + fprintf(stderr, "queue path failed\n"); + goto cleanup; + } + fd = open(queue_path, O_WRONLY | O_APPEND); + free(queue_path); + queue_path = NULL; + if (fd < 0) { + fprintf(stderr, "open queue failed\n"); + goto cleanup; + } + + store_u32_be(len_bytes, 7u); + if (!write_all(fd, len_bytes, sizeof(len_bytes))) { + close(fd); + fprintf(stderr, "write truncated failed\n"); + goto cleanup; + } + if (close(fd) != 0) { + fprintf(stderr, "close queue failed\n"); + goto cleanup; + } + + if (!amduat_pel_queue_read_next(root, offset, &out_ref, &next_offset)) { + fprintf(stderr, "read first failed\n"); + goto cleanup; + } + free_ref(&out_ref); + offset = next_offset; + + if (amduat_pel_queue_read_next(root, offset, &out_ref, &next_offset)) { + fprintf(stderr, "expected truncated tail to stop\n"); + free_ref(&out_ref); + goto cleanup; + } + if (next_offset != offset) { + fprintf(stderr, "unexpected offset on truncated read\n"); + goto cleanup; + } + + exit_code = 0; + +cleanup: + free_ref(&ref); + cleanup_root(root); + free(root); + return exit_code; +} + +static int test_heads_round_trip(void) { + char *root = NULL; + uint64_t pel_offset = 12345u; + uint64_t tgk_offset = 67890u; + uint64_t loaded = 0u; + int exit_code = 1; + + root = make_temp_root(); + if (root == NULL) { + fprintf(stderr, "temp root failed\n"); + return exit_code; + } + + if (!amduat_pel_queue_store_pel_head(root, pel_offset) || + !amduat_pel_queue_load_pel_head(root, &loaded)) { + fprintf(stderr, "pel head round-trip failed\n"); + goto cleanup; + } + if (loaded != pel_offset) { + fprintf(stderr, "pel head mismatch\n"); + goto cleanup; + } + + if (!amduat_pel_queue_store_tgk_head(root, tgk_offset) || + !amduat_pel_queue_load_tgk_head(root, &loaded)) { + fprintf(stderr, "tgk head round-trip failed\n"); + goto cleanup; + } + if (loaded != tgk_offset) { + fprintf(stderr, "tgk head mismatch\n"); + goto cleanup; + } + + exit_code = 0; + +cleanup: + cleanup_root(root); + free(root); + return exit_code; +} + +int main(void) { + if (test_append_and_read() != 0) { + return 1; + } + if (test_truncated_tail() != 0) { + return 1; + } + if (test_heads_round_trip() != 0) { + return 1; + } + return 0; +}