#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #define _POSIX_C_SOURCE 200809L #include "amduat/asl/artifact_io.h" #include "amduat/asl/collection.h" #include "amduat/asl/collection_view.h" #include "amduat/asl/none.h" #include "amduat/asl/record.h" #include "amduat/asl/asl_pointer_fs.h" #include "amduat/asl/asl_store_fs.h" #include "amduat/asl/asl_store_fs_meta.h" #include "amduat/asl/ref_text.h" #include "amduat/asl/store.h" #include "amduat/asl/ref_derive.h" #include "amduat/enc/asl1_core_codec.h" #include "amduat/enc/asl_log.h" #include "amduat/enc/fer1_receipt.h" #include "amduat/fed/replay.h" #include "amduat/enc/pel1_result.h" #include "amduat/enc/pel_program_dag.h" #include "amduat/enc/tgk1_edge.h" #include "amduat/fer/receipt.h" #include "amduat/format/pel.h" #include "amduat/tgk/core.h" #include "federation/coord.h" #include "federation/transport_stub.h" #include "amduat/pel/program_dag.h" #include "amduat/pel/program_dag_desc.h" #include "amduat/pel/opreg_kernel.h" #include "amduat/pel/run.h" #include "amduat/util/hex.h" #include "amduat/util/log.h" #include "amduat/hash/asl1.h" #include "amduatd_ui.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct amduatd_strbuf { char *data; size_t len; size_t cap; } amduatd_strbuf_t; static bool amduatd_http_send_json(int fd, int code, const char *reason, const char *json, bool head_only); static bool amduatd_send_json_error(int fd, int code, const char *reason, const char *msg); #if AMDUATD_ENABLE_UI bool amduatd_seed_ui_html(amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, amduat_reference_t *out_ref); #endif static uint64_t amduatd_now_ms(void) { struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { return 0; } return ((uint64_t)ts.tv_sec * 1000u) + ((uint64_t)ts.tv_nsec / 1000000u); } static void amduatd_strbuf_free(amduatd_strbuf_t *b); static bool amduatd_strbuf_append_cstr(amduatd_strbuf_t *b, const char *s); static bool amduatd_strbuf_append_char(amduatd_strbuf_t *b, char c); static const char *const AMDUATD_DEFAULT_ROOT = ".amduat-asl"; static const char *const AMDUATD_DEFAULT_SOCK = "amduatd.sock"; static const char *const AMDUATD_EDGES_FILE = ".amduatd.edges"; static const char *const AMDUATD_EDGE_COLLECTION = "daemon/edges"; static const uint64_t AMDUATD_FED_TICK_MS = 1000u; static const uint32_t AMDUATD_EDGE_VIEW_BATCH = 1024u; static const uint16_t AMDUATD_EDGE_COLLECTION_KIND = 1u; static const uint64_t AMDUATD_CAP_MAX_EXPIRY_SECONDS = 7u * 24u * 60u * 60u; typedef struct { const char *space_id; bool space_enabled; bool migrate_unscoped_edges; } amduatd_cfg_t; typedef struct { bool enabled; bool reads_enabled; uint8_t hmac_key[32]; size_t hmac_key_len; } amduatd_cap_state_t; typedef struct { amduat_reference_t record_ref; amduat_reference_t src_ref; amduat_reference_t dst_ref; char *rel; } amduatd_edge_entry_t; typedef struct { amduatd_edge_entry_t *items; size_t len; size_t cap; } amduatd_edge_list_t; static void amduatd_edge_entry_free(amduatd_edge_entry_t *entry) { if (entry == NULL) { return; } amduat_reference_free(&entry->record_ref); amduat_reference_free(&entry->src_ref); amduat_reference_free(&entry->dst_ref); free(entry->rel); entry->rel = NULL; } static void amduatd_edge_list_clear(amduatd_edge_list_t *list) { if (list == NULL) { return; } for (size_t i = 0; i < list->len; ++i) { amduatd_edge_entry_free(&list->items[i]); } free(list->items); list->items = NULL; list->len = 0; list->cap = 0; } static bool amduatd_edge_list_push(amduatd_edge_list_t *list, const amduatd_edge_entry_t *entry) { amduatd_edge_entry_t cloned; amduatd_edge_entry_t *next; if (list == NULL || entry == NULL) { return false; } memset(&cloned, 0, sizeof(cloned)); if (!amduat_reference_clone(entry->record_ref, &cloned.record_ref) || !amduat_reference_clone(entry->src_ref, &cloned.src_ref) || !amduat_reference_clone(entry->dst_ref, &cloned.dst_ref)) { amduatd_edge_entry_free(&cloned); return false; } if (entry->rel != NULL) { cloned.rel = strdup(entry->rel); if (cloned.rel == NULL) { amduatd_edge_entry_free(&cloned); return false; } } if (list->len == list->cap) { size_t next_cap = list->cap != 0 ? list->cap * 2u : 64u; next = (amduatd_edge_entry_t *)realloc(list->items, next_cap * sizeof(*list->items)); if (next == NULL) { amduatd_edge_entry_free(&cloned); return false; } list->items = next; list->cap = next_cap; } list->items[list->len++] = cloned; return true; } typedef struct { const char *root_path; char edges_path[1024]; char *edge_collection_name; amduat_reference_t rel_aliases_ref; amduat_reference_t rel_materializes_ref; amduat_reference_t rel_represents_ref; amduat_reference_t rel_requires_key_ref; amduat_reference_t rel_within_domain_ref; amduat_reference_t rel_computed_by_ref; amduat_reference_t rel_has_provenance_ref; amduat_asl_collection_store_t edge_collection; amduatd_edge_list_t edges; } amduatd_concepts_t; static bool amduatd_concepts_put_name_artifact(amduat_asl_store_t *store, const char *name, amduat_reference_t *out_ref); static bool amduatd_concepts_put_edge(amduat_asl_store_t *store, amduatd_concepts_t *c, amduat_reference_t from, amduat_reference_t to, amduat_reference_t relation_concept_ref, amduat_octets_t actor, amduat_reference_t *out_edge_ref); static bool amduatd_concepts_lookup_alias(amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *c, const char *name, amduat_reference_t *out_concept_ref); static void amduatd_concepts_free(amduatd_concepts_t *c) { if (c == NULL) { return; } amduat_reference_free(&c->rel_aliases_ref); amduat_reference_free(&c->rel_materializes_ref); amduat_reference_free(&c->rel_represents_ref); amduat_reference_free(&c->rel_requires_key_ref); amduat_reference_free(&c->rel_within_domain_ref); amduat_reference_free(&c->rel_computed_by_ref); amduat_reference_free(&c->rel_has_provenance_ref); amduatd_edge_list_clear(&c->edges); free(c->edge_collection_name); c->edge_collection_name = NULL; } enum { AMDUATD_EDGE_MAGIC_LEN = 8, AMDUATD_EDGE_VERSION = 1 }; static const uint8_t k_amduatd_edge_magic[AMDUATD_EDGE_MAGIC_LEN] = { 'A', 'S', 'L', 'E', 'D', 'G', '1', '\0' }; enum { AMDUATD_CAP_MAGIC_LEN = 8, AMDUATD_CAP_VERSION = 1 }; enum { AMDUATD_CAP_KIND_ARTIFACT_REF = 1, AMDUATD_CAP_KIND_POINTER_NAME = 2, AMDUATD_CAP_KIND_PEL_VIEW = 3 }; static const uint8_t k_amduatd_cap_magic[AMDUATD_CAP_MAGIC_LEN] = { 'A', 'S', 'L', 'C', 'A', 'P', '1', '\0' }; static const char *const AMDUATD_EDGE_SCHEMA = "tgk/edge"; static const char *const AMDUATD_REL_ALIAS = "alias"; static const char *const AMDUATD_REL_MATERIALIZES = "materializes"; static const char *const AMDUATD_REL_REPRESENTS = "represents"; static const char *const AMDUATD_REL_REQUIRES_KEY = "requires_key"; static const char *const AMDUATD_REL_WITHIN_DOMAIN = "within_domain"; static const char *const AMDUATD_REL_COMPUTED_BY = "computed_by"; static const char *const AMDUATD_REL_HAS_PROVENANCE = "has_provenance"; static void amduatd_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 bool amduatd_read_u32_le(const uint8_t *data, size_t len, size_t *offset, uint32_t *out) { if (len - *offset < 4u) { return false; } *out = (uint32_t)data[*offset] | ((uint32_t)data[*offset + 1u] << 8) | ((uint32_t)data[*offset + 2u] << 16) | ((uint32_t)data[*offset + 3u] << 24); *offset += 4u; return true; } static void amduatd_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 bool amduatd_read_u64_le(const uint8_t *data, size_t len, size_t *offset, uint64_t *out) { if (len - *offset < 8u) { return false; } *out = (uint64_t)data[*offset] | ((uint64_t)data[*offset + 1u] << 8) | ((uint64_t)data[*offset + 2u] << 16) | ((uint64_t)data[*offset + 3u] << 24) | ((uint64_t)data[*offset + 4u] << 32) | ((uint64_t)data[*offset + 5u] << 40) | ((uint64_t)data[*offset + 6u] << 48) | ((uint64_t)data[*offset + 7u] << 56); *offset += 8u; return true; } static bool amduatd_add_size(size_t *acc, size_t add) { if (*acc > SIZE_MAX - add) { return false; } *acc += add; return true; } static bool amduatd_is_ascii(amduat_octets_t bytes) { if (bytes.len == 0u || bytes.data == NULL) { return false; } for (size_t i = 0u; i < bytes.len; ++i) { uint8_t c = bytes.data[i]; if (c < 0x20u || c > 0x7eu) { return false; } } return true; } static bool amduatd_edge_payload_encode(amduat_reference_t src, amduat_reference_t dst, const char *rel, amduat_octets_t *out_payload) { amduat_octets_t src_bytes = amduat_octets(NULL, 0u); amduat_octets_t dst_bytes = amduat_octets(NULL, 0u); size_t total_len = 0u; size_t offset = 0u; uint8_t *buffer = NULL; uint32_t rel_len = 0u; if (out_payload == NULL || rel == NULL) { return false; } out_payload->data = NULL; out_payload->len = 0u; { size_t rel_len_full = strlen(rel); if (rel_len_full > UINT32_MAX) { return false; } rel_len = (uint32_t)rel_len_full; } if (rel_len == 0u) { return false; } if (!amduatd_is_ascii(amduat_octets((const uint8_t *)rel, rel_len))) { return false; } if (!amduat_enc_asl1_core_encode_reference_v1(src, &src_bytes) || !amduat_enc_asl1_core_encode_reference_v1(dst, &dst_bytes)) { free((void *)src_bytes.data); free((void *)dst_bytes.data); return false; } if (src_bytes.len > UINT32_MAX || dst_bytes.len > UINT32_MAX) { free((void *)src_bytes.data); free((void *)dst_bytes.data); return false; } if (!amduatd_add_size(&total_len, AMDUATD_EDGE_MAGIC_LEN + 4u + 4u + src_bytes.len) || !amduatd_add_size(&total_len, 4u + dst_bytes.len) || !amduatd_add_size(&total_len, 4u + rel_len) || !amduatd_add_size(&total_len, 1u)) { free((void *)src_bytes.data); free((void *)dst_bytes.data); return false; } buffer = (uint8_t *)malloc(total_len); if (buffer == NULL) { free((void *)src_bytes.data); free((void *)dst_bytes.data); return false; } memcpy(buffer + offset, k_amduatd_edge_magic, AMDUATD_EDGE_MAGIC_LEN); offset += AMDUATD_EDGE_MAGIC_LEN; amduatd_store_u32_le(buffer + offset, AMDUATD_EDGE_VERSION); offset += 4u; amduatd_store_u32_le(buffer + offset, (uint32_t)src_bytes.len); offset += 4u; memcpy(buffer + offset, src_bytes.data, src_bytes.len); offset += src_bytes.len; amduatd_store_u32_le(buffer + offset, (uint32_t)dst_bytes.len); offset += 4u; memcpy(buffer + offset, dst_bytes.data, dst_bytes.len); offset += dst_bytes.len; amduatd_store_u32_le(buffer + offset, rel_len); offset += 4u; memcpy(buffer + offset, rel, rel_len); offset += rel_len; buffer[offset++] = 0u; free((void *)src_bytes.data); free((void *)dst_bytes.data); if (offset != total_len) { free(buffer); return false; } out_payload->data = buffer; out_payload->len = total_len; return true; } static bool amduatd_edge_payload_decode(amduat_octets_t payload, amduat_reference_t *out_src, amduat_reference_t *out_dst, char **out_rel) { size_t offset = 0u; uint32_t version = 0u; uint32_t src_len = 0u; uint32_t dst_len = 0u; uint32_t rel_len = 0u; uint8_t flags = 0u; amduat_octets_t src_bytes; amduat_octets_t dst_bytes; char *rel = NULL; if (out_src == NULL || out_dst == NULL || out_rel == NULL) { return false; } *out_src = amduat_reference(0u, amduat_octets(NULL, 0u)); *out_dst = amduat_reference(0u, amduat_octets(NULL, 0u)); *out_rel = NULL; if (payload.len < AMDUATD_EDGE_MAGIC_LEN + 4u + 1u || payload.data == NULL) { return false; } if (memcmp(payload.data, k_amduatd_edge_magic, AMDUATD_EDGE_MAGIC_LEN) != 0) { return false; } offset += AMDUATD_EDGE_MAGIC_LEN; if (!amduatd_read_u32_le(payload.data, payload.len, &offset, &version) || version != AMDUATD_EDGE_VERSION) { return false; } if (!amduatd_read_u32_le(payload.data, payload.len, &offset, &src_len)) { return false; } if (payload.len - offset < src_len) { return false; } src_bytes = amduat_octets(payload.data + offset, src_len); offset += src_len; if (!amduatd_read_u32_le(payload.data, payload.len, &offset, &dst_len)) { return false; } if (payload.len - offset < dst_len) { return false; } dst_bytes = amduat_octets(payload.data + offset, dst_len); offset += dst_len; if (!amduatd_read_u32_le(payload.data, payload.len, &offset, &rel_len)) { return false; } if (payload.len - offset < rel_len + 1u) { return false; } if (!amduatd_is_ascii(amduat_octets(payload.data + offset, rel_len))) { return false; } rel = (char *)malloc(rel_len + 1u); if (rel == NULL) { return false; } memcpy(rel, payload.data + offset, rel_len); rel[rel_len] = '\0'; offset += rel_len; flags = payload.data[offset++]; (void)flags; if (offset != payload.len) { free(rel); return false; } if (!amduat_enc_asl1_core_decode_reference_v1(src_bytes, out_src) || !amduat_enc_asl1_core_decode_reference_v1(dst_bytes, out_dst)) { amduat_reference_free(out_src); amduat_reference_free(out_dst); free(rel); return false; } *out_rel = rel; return true; } static const char *amduatd_relation_name_for_ref( const amduatd_concepts_t *c, amduat_reference_t ref) { if (c == NULL) { return NULL; } if (amduat_reference_eq(ref, c->rel_aliases_ref)) { return AMDUATD_REL_ALIAS; } if (amduat_reference_eq(ref, c->rel_materializes_ref)) { return AMDUATD_REL_MATERIALIZES; } if (amduat_reference_eq(ref, c->rel_represents_ref)) { return AMDUATD_REL_REPRESENTS; } if (amduat_reference_eq(ref, c->rel_requires_key_ref)) { return AMDUATD_REL_REQUIRES_KEY; } if (amduat_reference_eq(ref, c->rel_within_domain_ref)) { return AMDUATD_REL_WITHIN_DOMAIN; } if (amduat_reference_eq(ref, c->rel_computed_by_ref)) { return AMDUATD_REL_COMPUTED_BY; } if (amduat_reference_eq(ref, c->rel_has_provenance_ref)) { return AMDUATD_REL_HAS_PROVENANCE; } return NULL; } static bool amduatd_build_collection_head_name(const char *name, char **out_name) { size_t name_len; size_t total_len; char *buffer; size_t offset = 0u; if (name == NULL || out_name == NULL) { return false; } if (!amduat_asl_pointer_name_is_valid(name)) { return false; } name_len = strlen(name); total_len = 11u + name_len + 5u + 1u; buffer = (char *)malloc(total_len); if (buffer == NULL) { return false; } memcpy(buffer + offset, "collection/", 11u); offset += 11u; memcpy(buffer + offset, name, name_len); offset += name_len; memcpy(buffer + offset, "/head", 5u); offset += 5u; buffer[offset] = '\0'; *out_name = buffer; return true; } static bool amduatd_build_collection_log_head_name(const char *name, char **out_name) { size_t name_len; size_t log_name_len; size_t total_len; char *buffer; size_t offset = 0u; if (name == NULL || out_name == NULL) { return false; } if (!amduat_asl_pointer_name_is_valid(name)) { return false; } name_len = strlen(name); log_name_len = 11u + name_len + 4u; total_len = 4u + log_name_len + 5u + 1u; buffer = (char *)malloc(total_len); if (buffer == NULL) { return false; } memcpy(buffer + offset, "log/", 4u); offset += 4u; memcpy(buffer + offset, "collection/", 11u); offset += 11u; memcpy(buffer + offset, name, name_len); offset += name_len; memcpy(buffer + offset, "/log", 4u); offset += 4u; memcpy(buffer + offset, "/head", 5u); offset += 5u; buffer[offset] = '\0'; *out_name = buffer; return true; } static bool amduatd_get_none_ref(amduat_asl_store_t *store, amduat_reference_t *out_ref) { amduat_artifact_t none_artifact; if (store == NULL || out_ref == NULL) { return false; } if (!amduat_asl_none_artifact(&none_artifact)) { return false; } if (amduat_asl_store_put(store, none_artifact, out_ref) != AMDUAT_ASL_STORE_OK) { amduat_artifact_free(&none_artifact); return false; } amduat_artifact_free(&none_artifact); return true; } static bool amduatd_put_view_params(amduat_asl_store_t *store, uint64_t from_offset, uint32_t limit, amduat_reference_t *out_ref) { uint8_t payload[12]; if (store == NULL || out_ref == NULL) { return false; } payload[0] = (uint8_t)(from_offset & 0xffu); payload[1] = (uint8_t)((from_offset >> 8) & 0xffu); payload[2] = (uint8_t)((from_offset >> 16) & 0xffu); payload[3] = (uint8_t)((from_offset >> 24) & 0xffu); payload[4] = (uint8_t)((from_offset >> 32) & 0xffu); payload[5] = (uint8_t)((from_offset >> 40) & 0xffu); payload[6] = (uint8_t)((from_offset >> 48) & 0xffu); payload[7] = (uint8_t)((from_offset >> 56) & 0xffu); payload[8] = (uint8_t)(limit & 0xffu); payload[9] = (uint8_t)((limit >> 8) & 0xffu); payload[10] = (uint8_t)((limit >> 16) & 0xffu); payload[11] = (uint8_t)((limit >> 24) & 0xffu); if (amduat_asl_record_store_put( store, amduat_octets("pel/params", strlen("pel/params")), amduat_octets(payload, sizeof(payload)), out_ref) != AMDUAT_ASL_STORE_OK) { return false; } return true; } static void amduatd_program_free(amduat_pel_program_t *program) { if (program == NULL || program->nodes == NULL) { return; } for (size_t i = 0u; i < program->nodes_len; ++i) { free(program->nodes[i].inputs); } free(program->nodes); free(program->roots); program->nodes = NULL; program->roots = NULL; program->nodes_len = 0u; program->roots_len = 0u; } static bool amduatd_build_collection_view_program( amduat_pel_program_t *out_program) { static const char k_op_snapshot[] = "collection.snapshot_decode_v1"; static const char k_op_read_range[] = "log.read_range_v1"; static const char k_op_merge[] = "collection.merge_refs_v1"; amduat_pel_node_t *nodes; amduat_pel_root_ref_t *roots; if (out_program == NULL) { return false; } memset(out_program, 0, sizeof(*out_program)); nodes = (amduat_pel_node_t *)calloc(3u, sizeof(*nodes)); roots = (amduat_pel_root_ref_t *)calloc(1u, sizeof(*roots)); if (nodes == NULL || roots == NULL) { free(nodes); free(roots); return false; } nodes[0].id = 1u; nodes[0].op.name = amduat_octets(k_op_snapshot, strlen(k_op_snapshot)); nodes[0].op.version = 1u; nodes[0].inputs_len = 1u; nodes[0].inputs = (amduat_pel_dag_input_t *)calloc(1u, sizeof(*nodes[0].inputs)); if (nodes[0].inputs == NULL) { free(nodes); free(roots); return false; } nodes[0].inputs[0].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; nodes[0].inputs[0].value.external.input_index = 0u; nodes[0].params = amduat_octets(NULL, 0u); nodes[1].id = 2u; nodes[1].op.name = amduat_octets(k_op_read_range, strlen(k_op_read_range)); nodes[1].op.version = 1u; nodes[1].inputs_len = 3u; nodes[1].inputs = (amduat_pel_dag_input_t *)calloc(3u, sizeof(*nodes[1].inputs)); if (nodes[1].inputs == NULL) { free(nodes[0].inputs); free(nodes); free(roots); return false; } nodes[1].inputs[0].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; nodes[1].inputs[0].value.external.input_index = 1u; nodes[1].inputs[1].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; nodes[1].inputs[1].value.external.input_index = 2u; nodes[1].inputs[2].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; nodes[1].inputs[2].value.external.input_index = 3u; nodes[1].params = amduat_octets(NULL, 0u); nodes[2].id = 3u; nodes[2].op.name = amduat_octets(k_op_merge, strlen(k_op_merge)); nodes[2].op.version = 1u; nodes[2].inputs_len = 4u; nodes[2].inputs = (amduat_pel_dag_input_t *)calloc(4u, sizeof(*nodes[2].inputs)); if (nodes[2].inputs == NULL) { free(nodes[1].inputs); free(nodes[0].inputs); free(nodes); free(roots); return false; } nodes[2].inputs[0].kind = AMDUAT_PEL_DAG_INPUT_NODE; nodes[2].inputs[0].value.node.node_id = 1u; nodes[2].inputs[0].value.node.output_index = 0u; nodes[2].inputs[1].kind = AMDUAT_PEL_DAG_INPUT_NODE; nodes[2].inputs[1].value.node.node_id = 2u; nodes[2].inputs[1].value.node.output_index = 0u; nodes[2].inputs[2].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; nodes[2].inputs[2].value.external.input_index = 2u; nodes[2].inputs[3].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; nodes[2].inputs[3].value.external.input_index = 3u; nodes[2].params = amduat_octets(NULL, 0u); roots[0].node_id = 3u; roots[0].output_index = 0u; out_program->nodes = nodes; out_program->nodes_len = 3u; out_program->roots = roots; out_program->roots_len = 1u; return true; } static bool amduatd_collection_view(amduat_asl_store_t *store, const char *root_path, const char *name, uint64_t from, uint32_t limit, amduat_asl_collection_view_t *out_view) { amduat_asl_pointer_store_t pointer_store; amduat_reference_t snapshot_ref; amduat_reference_t log_head_ref; amduat_reference_t none_ref; amduat_reference_t params_ref; amduat_reference_t program_ref; amduat_reference_t input_refs[4]; amduat_pel_program_t program; amduat_octets_t program_bytes = amduat_octets(NULL, 0u); amduat_pel_run_result_t run_result; amduat_artifact_t view_artifact; amduat_asl_collection_view_t view; amduat_type_tag_t expected_type_tag; amduat_asl_encoding_profile_id_t expected_profile; char *snapshot_name = NULL; char *log_head_name = NULL; bool snapshot_exists = false; bool log_head_exists = false; bool ok = false; if (out_view == NULL) { return false; } memset(out_view, 0, sizeof(*out_view)); memset(&snapshot_ref, 0, sizeof(snapshot_ref)); memset(&log_head_ref, 0, sizeof(log_head_ref)); memset(&none_ref, 0, sizeof(none_ref)); memset(¶ms_ref, 0, sizeof(params_ref)); memset(&program_ref, 0, sizeof(program_ref)); memset(&run_result, 0, sizeof(run_result)); memset(&view, 0, sizeof(view)); if (store == NULL || root_path == NULL || name == NULL) { return false; } if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) { return false; } if (!amduatd_get_none_ref(store, &none_ref)) { return false; } if (!amduatd_build_collection_head_name(name, &snapshot_name) || !amduatd_build_collection_log_head_name(name, &log_head_name)) { goto view_cleanup; } if (amduat_asl_pointer_get(&pointer_store, snapshot_name, &snapshot_exists, &snapshot_ref) != AMDUAT_ASL_POINTER_OK) { goto view_cleanup; } if (!snapshot_exists && !amduat_reference_clone(none_ref, &snapshot_ref)) { goto view_cleanup; } if (amduat_asl_pointer_get(&pointer_store, log_head_name, &log_head_exists, &log_head_ref) != AMDUAT_ASL_POINTER_OK) { goto view_cleanup; } if (!log_head_exists && !amduat_reference_clone(none_ref, &log_head_ref)) { goto view_cleanup; } if (!amduatd_put_view_params(store, from, limit, ¶ms_ref)) { goto view_cleanup; } if (!amduatd_build_collection_view_program(&program)) { goto view_cleanup; } if (!amduat_enc_pel_program_dag_encode_v1(&program, &program_bytes)) { amduatd_program_free(&program); goto view_cleanup; } amduatd_program_free(&program); if (!amduat_pel_program_dag_desc_get_program_binding(&expected_type_tag, &expected_profile)) { goto view_cleanup; } if (expected_profile != AMDUAT_PEL_ENC_PROGRAM_DAG_V1) { goto view_cleanup; } { amduat_artifact_t program_artifact = amduat_artifact_with_type(program_bytes, expected_type_tag); if (amduat_asl_store_put(store, program_artifact, &program_ref) != AMDUAT_ASL_STORE_OK) { goto view_cleanup; } } input_refs[0] = snapshot_ref; input_refs[1] = log_head_ref; input_refs[2] = params_ref; input_refs[3] = params_ref; if (!amduat_pel_surf_run_with_result(store, amduat_pel_program_dag_scheme_ref(), program_ref, input_refs, 4u, true, params_ref, &run_result)) { goto view_cleanup; } if (!run_result.has_result_value || run_result.result_value.core_result.status != AMDUAT_PEL_EXEC_STATUS_OK || run_result.output_refs_len != 1u) { goto view_cleanup; } memset(&view_artifact, 0, sizeof(view_artifact)); if (amduat_asl_store_get(store, run_result.output_refs[0], &view_artifact) != AMDUAT_ASL_STORE_OK) { goto view_cleanup; } if (!view_artifact.has_type_tag || view_artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_ASL_COLLECTION_VIEW_1 || !amduat_asl_collection_view_decode_v1(view_artifact.bytes, &view)) { amduat_artifact_free(&view_artifact); goto view_cleanup; } amduat_artifact_free(&view_artifact); *out_view = view; memset(&view, 0, sizeof(view)); ok = true; view_cleanup: amduat_asl_collection_view_free(&view); if (run_result.has_result_value) { amduat_enc_pel1_result_free(&run_result.result_value); } if (run_result.output_refs != NULL) { amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len); } amduat_pel_surf_free_ref(&run_result.result_ref); amduat_reference_free(&snapshot_ref); amduat_reference_free(&log_head_ref); amduat_reference_free(&none_ref); amduat_reference_free(¶ms_ref); amduat_reference_free(&program_ref); free((void *)program_bytes.data); free(snapshot_name); free(log_head_name); return ok; } static const char k_amduatd_contract_v1_json[] = "{" "\"contract\":\"AMDUATD/API/1\"," "\"base_path\":\"/v1\"," "\"endpoints\":[" "{\"method\":\"GET\",\"path\":\"/v1/ui\"}," "{\"method\":\"GET\",\"path\":\"/v1/meta\"}," "{\"method\":\"HEAD\",\"path\":\"/v1/meta\"}," "{\"method\":\"GET\",\"path\":\"/v1/contract\"}," "{\"method\":\"POST\",\"path\":\"/v1/capabilities\"}," "{\"method\":\"GET\",\"path\":\"/v1/cap/resolve\"}," "{\"method\":\"GET\",\"path\":\"/v1/fed/records\"}," "{\"method\":\"GET\",\"path\":\"/v1/fed/artifacts/{ref}\"}," "{\"method\":\"GET\",\"path\":\"/v1/fed/status\"}," "{\"method\":\"POST\",\"path\":\"/v1/concepts\"}," "{\"method\":\"GET\",\"path\":\"/v1/concepts\"}," "{\"method\":\"GET\",\"path\":\"/v1/concepts/{name}\"}," "{\"method\":\"POST\",\"path\":\"/v1/concepts/{name}/publish\"}," "{\"method\":\"GET\",\"path\":\"/v1/resolve/{name}\"}," "{\"method\":\"POST\",\"path\":\"/v1/artifacts\"}," "{\"method\":\"GET\",\"path\":\"/v1/relations\"}," "{\"method\":\"GET\",\"path\":\"/v1/artifacts/{ref}\"}," "{\"method\":\"HEAD\",\"path\":\"/v1/artifacts/{ref}\"}," "{\"method\":\"GET\",\"path\":\"/v1/artifacts/{ref}?format=info\"}," "{\"method\":\"POST\",\"path\":\"/v1/pel/run\"}," "{\"method\":\"POST\",\"path\":\"/v1/pel/programs\"}," "{\"method\":\"POST\",\"path\":\"/v1/context_frames\"}" "]," "\"schemas\":{" "\"capability_mint_request\":{" "\"type\":\"object\"," "\"required\":[\"kind\",\"target\",\"expiry_seconds\"]," "\"properties\":{" "\"kind\":{\"type\":\"string\"}," "\"target\":{\"type\":\"object\"}," "\"expiry_seconds\":{\"type\":\"integer\"}" "}" "}," "\"capability_mint_response\":{" "\"type\":\"object\"," "\"required\":[\"token\"]," "\"properties\":{" "\"token\":{\"type\":\"string\"}" "}" "}," "\"pel_run_request\":{" "\"type\":\"object\"," "\"required\":[\"program_ref\",\"input_refs\"]," "\"properties\":{" "\"program_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"input_refs\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}}," "\"params_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"scheme_ref\":{\"type\":\"string\",\"description\":\"hex ref or 'dag'\"}," "\"receipt\":{" "\"type\":\"object\"," "\"required\":[\"input_manifest_ref\",\"environment_ref\",\"evaluator_id\",\"executor_ref\",\"started_at\",\"completed_at\"]," "\"properties\":{" "\"input_manifest_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"environment_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"evaluator_id\":{\"type\":\"string\",\"description\":\"opaque evaluator bytes (utf-8)\"}," "\"executor_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"sbom_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"parity_digest_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"}," "\"executor_fingerprint_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"run_id_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"}," "\"limits\":{\"type\":\"object\",\"required\":[\"cpu_ms\",\"wall_ms\",\"max_rss_kib\",\"io_reads\",\"io_writes\"],\"properties\":{" "\"cpu_ms\":{\"type\":\"integer\"}," "\"wall_ms\":{\"type\":\"integer\"}," "\"max_rss_kib\":{\"type\":\"integer\"}," "\"io_reads\":{\"type\":\"integer\"}," "\"io_writes\":{\"type\":\"integer\"}" "}}," "\"logs\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"kind\",\"log_ref\",\"sha256_hex\"],\"properties\":{" "\"kind\":{\"type\":\"integer\"}," "\"log_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"sha256_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"}" "}}}," "\"determinism_level\":{\"type\":\"integer\",\"description\":\"0-255\"}," "\"rng_seed_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"}," "\"signature_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"}," "\"started_at\":{\"type\":\"integer\"}," "\"completed_at\":{\"type\":\"integer\"}" "}" "}" "}" "}," "\"pel_run_response\":{" "\"type\":\"object\"," "\"required\":[\"result_ref\",\"output_refs\",\"status\"]," "\"properties\":{" "\"result_ref\":{\"type\":\"string\",\"description\":\"hex ref\"}," "\"trace_ref\":{\"type\":\"string\",\"description\":\"hex ref\"}," "\"receipt_ref\":{\"type\":\"string\",\"description\":\"hex ref\"}," "\"output_refs\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"description\":\"hex ref\"}}," "\"status\":{\"type\":\"string\"}" "}" "}," "\"fed_records_response\":{" "\"type\":\"object\"," "\"required\":[\"domain_id\",\"snapshot_id\",\"log_prefix\",\"next_logseq\",\"records\"]," "\"properties\":{" "\"domain_id\":{\"type\":\"integer\"}," "\"snapshot_id\":{\"type\":\"integer\"}," "\"log_prefix\":{\"type\":\"integer\"}," "\"next_logseq\":{\"type\":\"integer\",\"description\":\"Paging cursor; last emitted logseq + 1, or from_logseq if no records emitted.\"}," "\"records\":{\"type\":\"array\",\"items\":{" "\"type\":\"object\"," "\"required\":[\"domain_id\",\"type\",\"ref\",\"logseq\",\"snapshot_id\",\"log_prefix\"]," "\"properties\":{" "\"domain_id\":{\"type\":\"integer\"}," "\"type\":{\"type\":\"integer\"}," "\"ref\":{\"type\":\"string\"}," "\"logseq\":{\"type\":\"integer\"}," "\"snapshot_id\":{\"type\":\"integer\"}," "\"log_prefix\":{\"type\":\"integer\"}," "\"visibility\":{\"type\":\"integer\"}," "\"has_source\":{\"type\":\"boolean\"}," "\"source_domain\":{\"type\":\"integer\"}," "\"notes\":{\"type\":\"string\",\"description\":\"Type mapping: ARTIFACT_PUBLISH -> ARTIFACT, PER when type_tag=FER1_RECEIPT_1, TGK_EDGE when type_tag=TGK1_EDGE_V1; ARTIFACT_UNPUBLISH -> TOMBSTONE.\"}" "}" "}}" "}" "}," "\"fed_status_response\":{" "\"type\":\"object\"," "\"required\":[\"status\",\"domain_id\",\"registry_ref\",\"last_tick_ms\"]," "\"properties\":{" "\"status\":{\"type\":\"string\"}," "\"domain_id\":{\"type\":\"integer\"}," "\"registry_ref\":{\"type\":[\"string\",\"null\"]}," "\"last_tick_ms\":{\"type\":\"integer\"}" "}" "}," "\"context_frame_request\":{" "\"type\":\"object\"," "\"required\":[\"bindings\"]," "\"properties\":{" "\"bindings\":{" "\"type\":\"array\"," "\"items\":{" "\"type\":\"object\"," "\"required\":[\"key\"]," "\"properties\":{" "\"key\":{\"type\":\"string\",\"description\":\"concept name or hex ref\"}," "\"value\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"value_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"value_scalar\":{\"type\":\"object\",\"properties\":{" "\"int\":{\"type\":\"integer\"}," "\"enum\":{\"type\":\"string\",\"description\":\"concept name or hex ref\"}" "}}" "}" "}" "}" "}" "}," "\"pel_program_author_request\":{" "\"type\":\"object\"," "\"required\":[\"nodes\",\"roots\"]," "\"properties\":{" "\"nodes\":{\"type\":\"array\"}," "\"roots\":{\"type\":\"array\"}" "}" "}" "}" "}\n"; static volatile sig_atomic_t amduatd_should_exit = 0; static void amduatd_on_signal(int signo) { (void)signo; amduatd_should_exit = 1; } static bool amduatd_write_all(int fd, const uint8_t *buf, size_t len) { size_t off = 0; while (off < len) { ssize_t n = write(fd, buf + off, len - off); if (n < 0) { if (errno == EINTR) { continue; } return false; } if (n == 0) { return false; } off += (size_t)n; } return true; } static bool amduatd_read_exact(int fd, uint8_t *buf, size_t len); static bool amduatd_read_file(const char *path, uint8_t **out, size_t *out_len) { uint8_t *buf = NULL; size_t cap = 0; size_t len = 0; FILE *f; if (out != NULL) { *out = NULL; } if (out_len != NULL) { *out_len = 0; } if (path == NULL || out == NULL || out_len == NULL) { return false; } f = fopen(path, "rb"); if (f == NULL) { return false; } for (;;) { size_t n; if (len == cap) { size_t next_cap = cap != 0 ? cap * 2u : 4096u; uint8_t *next = (uint8_t *)realloc(buf, next_cap); if (next == NULL) { free(buf); fclose(f); return false; } buf = next; cap = next_cap; } n = fread(buf + len, 1, cap - len, f); len += n; if (n == 0) { if (ferror(f)) { free(buf); fclose(f); return false; } break; } } fclose(f); *out = buf; *out_len = len; return true; } static bool amduatd_read_urandom(uint8_t *out, size_t len) { int fd; if (out == NULL || len == 0) { return false; } fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { return false; } if (!amduatd_read_exact(fd, out, len)) { close(fd); return false; } close(fd); return true; } static bool amduatd_ct_memcmp(const uint8_t *a, const uint8_t *b, size_t len) { uint8_t diff = 0u; if (a == NULL || b == NULL) { return false; } for (size_t i = 0u; i < len; ++i) { diff |= (uint8_t)(a[i] ^ b[i]); } return diff == 0u; } static bool amduatd_sha256_digest(amduat_octets_t input, uint8_t out[32]) { return amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256, input, out, 32u); } static bool amduatd_hmac_sha256(const uint8_t *key, size_t key_len, const uint8_t *data, size_t data_len, uint8_t out[32]) { uint8_t key_block[64]; uint8_t ipad[64]; uint8_t opad[64]; uint8_t inner[32]; size_t i; amduat_hash_asl1_stream_t stream; if (key == NULL || data == NULL || out == NULL) { return false; } memset(key_block, 0, sizeof(key_block)); if (key_len > sizeof(key_block)) { if (!amduatd_sha256_digest(amduat_octets(key, key_len), key_block)) { return false; } } else if (key_len != 0u) { memcpy(key_block, key, key_len); } for (i = 0u; i < sizeof(key_block); ++i) { ipad[i] = (uint8_t)(key_block[i] ^ 0x36u); opad[i] = (uint8_t)(key_block[i] ^ 0x5cu); } if (!amduat_hash_asl1_stream_init(AMDUAT_HASH_ASL1_ID_SHA256, &stream)) { return false; } if (!amduat_hash_asl1_stream_update(&stream, amduat_octets(ipad, sizeof(ipad))) || !amduat_hash_asl1_stream_update(&stream, amduat_octets(data, data_len)) || !amduat_hash_asl1_stream_final(&stream, inner, sizeof(inner))) { amduat_hash_asl1_stream_destroy(&stream); return false; } amduat_hash_asl1_stream_destroy(&stream); if (!amduat_hash_asl1_stream_init(AMDUAT_HASH_ASL1_ID_SHA256, &stream)) { return false; } if (!amduat_hash_asl1_stream_update(&stream, amduat_octets(opad, sizeof(opad))) || !amduat_hash_asl1_stream_update(&stream, amduat_octets(inner, sizeof(inner))) || !amduat_hash_asl1_stream_final(&stream, out, 32u)) { amduat_hash_asl1_stream_destroy(&stream); return false; } amduat_hash_asl1_stream_destroy(&stream); return true; } static int amduatd_b64url_value(char c) { if (c >= 'A' && c <= 'Z') { return c - 'A'; } if (c >= 'a' && c <= 'z') { return c - 'a' + 26; } if (c >= '0' && c <= '9') { return c - '0' + 52; } if (c == '-') { return 62; } if (c == '_') { return 63; } return -1; } static bool amduatd_b64url_encode(const uint8_t *data, size_t len, char **out) { static const char k_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; size_t out_len; size_t i; size_t o = 0; char *buf; if (out != NULL) { *out = NULL; } if (data == NULL || out == NULL) { return false; } out_len = (len / 3u) * 4u; if (len % 3u == 1u) { out_len += 2u; } else if (len % 3u == 2u) { out_len += 3u; } if (out_len > SIZE_MAX - 1u) { return false; } buf = (char *)malloc(out_len + 1u); if (buf == NULL) { return false; } for (i = 0u; i + 2u < len; i += 3u) { uint32_t v = ((uint32_t)data[i] << 16) | ((uint32_t)data[i + 1u] << 8) | (uint32_t)data[i + 2u]; buf[o++] = k_table[(v >> 18) & 0x3fu]; buf[o++] = k_table[(v >> 12) & 0x3fu]; buf[o++] = k_table[(v >> 6) & 0x3fu]; buf[o++] = k_table[v & 0x3fu]; } if (len % 3u == 1u) { uint32_t v = (uint32_t)data[i] << 16; buf[o++] = k_table[(v >> 18) & 0x3fu]; buf[o++] = k_table[(v >> 12) & 0x3fu]; } else if (len % 3u == 2u) { uint32_t v = ((uint32_t)data[i] << 16) | ((uint32_t)data[i + 1u] << 8); buf[o++] = k_table[(v >> 18) & 0x3fu]; buf[o++] = k_table[(v >> 12) & 0x3fu]; buf[o++] = k_table[(v >> 6) & 0x3fu]; } buf[o] = '\0'; *out = buf; return true; } static bool amduatd_b64url_decode(const char *text, uint8_t **out, size_t *out_len) { size_t len; size_t clean_len; size_t i = 0u; size_t o = 0u; uint8_t *buf; if (out != NULL) { *out = NULL; } if (out_len != NULL) { *out_len = 0; } if (text == NULL || out == NULL || out_len == NULL) { return false; } len = strlen(text); clean_len = len; while (clean_len > 0u && text[clean_len - 1u] == '=') { clean_len--; } if (clean_len % 4u == 1u) { return false; } *out_len = (clean_len / 4u) * 3u; if (clean_len % 4u == 2u) { *out_len += 1u; } else if (clean_len % 4u == 3u) { *out_len += 2u; } if (*out_len > SIZE_MAX) { return false; } buf = (uint8_t *)malloc(*out_len); if (buf == NULL) { return false; } while (i + 4u <= clean_len) { int v0 = amduatd_b64url_value(text[i]); int v1 = amduatd_b64url_value(text[i + 1u]); int v2 = amduatd_b64url_value(text[i + 2u]); int v3 = amduatd_b64url_value(text[i + 3u]); uint32_t v; if (v0 < 0 || v1 < 0 || v2 < 0 || v3 < 0) { free(buf); return false; } v = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12) | ((uint32_t)v2 << 6) | (uint32_t)v3; buf[o++] = (uint8_t)((v >> 16) & 0xffu); buf[o++] = (uint8_t)((v >> 8) & 0xffu); buf[o++] = (uint8_t)(v & 0xffu); i += 4u; } if (clean_len % 4u == 2u) { int v0 = amduatd_b64url_value(text[i]); int v1 = amduatd_b64url_value(text[i + 1u]); uint32_t v; if (v0 < 0 || v1 < 0) { free(buf); return false; } v = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12); buf[o++] = (uint8_t)((v >> 16) & 0xffu); } else if (clean_len % 4u == 3u) { int v0 = amduatd_b64url_value(text[i]); int v1 = amduatd_b64url_value(text[i + 1u]); int v2 = amduatd_b64url_value(text[i + 2u]); uint32_t v; if (v0 < 0 || v1 < 0 || v2 < 0) { free(buf); return false; } v = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12) | ((uint32_t)v2 << 6); buf[o++] = (uint8_t)((v >> 16) & 0xffu); buf[o++] = (uint8_t)((v >> 8) & 0xffu); } if (o != *out_len) { free(buf); return false; } *out = buf; return true; } static bool amduatd_name_valid(const char *name) { size_t i; size_t len; if (name == NULL) { return false; } len = strlen(name); if (len == 0 || len > 64) { return false; } for (i = 0; i < len; ++i) { unsigned char c = (unsigned char)name[i]; if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.' || c == '/') { continue; } return false; } return true; } static bool amduatd_strdup_cstr(const char *s, char **out) { size_t len; char *buf; if (out == NULL) { return false; } *out = NULL; if (s == NULL) { return false; } len = strlen(s); if (len > SIZE_MAX - 1u) { return false; } buf = (char *)malloc(len + 1u); if (buf == NULL) { return false; } if (len != 0) { memcpy(buf, s, len); } buf[len] = '\0'; *out = buf; return true; } static bool amduatd_space_id_valid(const char *space_id) { if (space_id == NULL || space_id[0] == '\0') { return false; } if (strchr(space_id, '/') != NULL) { return false; } return amduat_asl_pointer_name_is_valid(space_id); } static void amduatd_log_scoped_name(const amduatd_cfg_t *cfg, const char *user_name, const char *scoped_name) { if (cfg == NULL || !cfg->space_enabled) { return; } if (user_name == NULL || scoped_name == NULL) { return; } amduat_log(AMDUAT_LOG_DEBUG, "%s -> %s", user_name, scoped_name); } static bool amduatd_scope_name(const amduatd_cfg_t *cfg, const char *user_name, amduat_octets_t *out_scoped_name) { char *buf = NULL; size_t user_len = 0; size_t space_len = 0; size_t total_len = 0; size_t offset = 0; if (out_scoped_name != NULL) { *out_scoped_name = amduat_octets(NULL, 0u); } if (user_name == NULL || out_scoped_name == NULL) { return false; } if (cfg == NULL || !cfg->space_enabled || cfg->space_id == NULL) { if (!amduatd_name_valid(user_name)) { return false; } if (!amduatd_strdup_cstr(user_name, &buf)) { return false; } *out_scoped_name = amduat_octets((const uint8_t *)buf, strlen(buf)); return true; } if (strncmp(user_name, "space/", 6) == 0) { return false; } if (!amduat_asl_pointer_name_is_valid(user_name)) { return false; } if (!amduatd_space_id_valid(cfg->space_id)) { return false; } user_len = strlen(user_name); space_len = strlen(cfg->space_id); if (space_len > SIZE_MAX - 7u || user_len > SIZE_MAX - (7u + space_len)) { return false; } total_len = 6u + space_len + 1u + user_len; buf = (char *)malloc(total_len + 1u); if (buf == NULL) { return false; } memcpy(buf + offset, "space/", 6u); offset += 6u; memcpy(buf + offset, cfg->space_id, space_len); offset += space_len; buf[offset++] = '/'; if (user_len != 0) { memcpy(buf + offset, user_name, user_len); offset += user_len; } buf[offset] = '\0'; if (!amduat_asl_pointer_name_is_valid(buf)) { free(buf); return false; } *out_scoped_name = amduat_octets((const uint8_t *)buf, total_len); return true; } static bool amduatd_scope_name_cstr(const amduatd_cfg_t *cfg, const char *user_name, char **out_scoped_name) { amduat_octets_t scoped; if (out_scoped_name != NULL) { *out_scoped_name = NULL; } if (out_scoped_name == NULL) { return false; } if (!amduatd_scope_name(cfg, user_name, &scoped)) { return false; } *out_scoped_name = (char *)scoped.data; return *out_scoped_name != NULL; } static bool amduatd_unscoped_name(const amduatd_cfg_t *cfg, const char *scoped_name, char *out, size_t cap) { const char *suffix = NULL; size_t suffix_len = 0; if (out != NULL && cap != 0) { out[0] = '\0'; } if (out == NULL || cap == 0 || scoped_name == NULL) { return false; } if (cfg == NULL || !cfg->space_enabled || cfg->space_id == NULL) { size_t len = strlen(scoped_name); if (!amduatd_name_valid(scoped_name)) { return false; } if (len >= cap) { return false; } memcpy(out, scoped_name, len); out[len] = '\0'; return true; } if (!amduatd_space_id_valid(cfg->space_id)) { return false; } { size_t space_len = strlen(cfg->space_id); size_t prefix_len = 6u + space_len + 1u; if (strncmp(scoped_name, "space/", 6u) != 0) { return false; } if (strncmp(scoped_name + 6u, cfg->space_id, space_len) != 0) { return false; } if (scoped_name[6u + space_len] != '/') { return false; } suffix = scoped_name + prefix_len; } if (suffix == NULL || suffix[0] == '\0') { return false; } if (!amduat_asl_pointer_name_is_valid(suffix)) { return false; } suffix_len = strlen(suffix); if (suffix_len >= cap) { return false; } memcpy(out, suffix, suffix_len); out[suffix_len] = '\0'; return true; } typedef struct { uint8_t kind; char *space; size_t space_len; uint64_t expiry_unix; amduat_reference_t artifact_ref; char *pointer_name; amduat_reference_t program_ref; amduat_reference_t *input_refs; size_t input_refs_len; bool has_params_ref; amduat_reference_t params_ref; } amduatd_cap_token_t; static void amduatd_cap_token_free(amduatd_cap_token_t *token) { if (token == NULL) { return; } free(token->space); token->space = NULL; token->space_len = 0u; amduat_reference_free(&token->artifact_ref); free(token->pointer_name); token->pointer_name = NULL; amduat_reference_free(&token->program_ref); if (token->input_refs != NULL) { for (size_t i = 0u; i < token->input_refs_len; ++i) { amduat_reference_free(&token->input_refs[i]); } free(token->input_refs); } token->input_refs = NULL; token->input_refs_len = 0u; if (token->has_params_ref) { amduat_reference_free(&token->params_ref); } token->has_params_ref = false; memset(token, 0, sizeof(*token)); } static bool amduatd_pointer_name_scoped_for_space(const amduatd_cfg_t *dcfg, const char *name) { size_t space_len; if (dcfg == NULL || name == NULL) { return false; } if (!amduat_asl_pointer_name_is_valid(name)) { return false; } if (!dcfg->space_enabled || dcfg->space_id == NULL) { return true; } space_len = strlen(dcfg->space_id); if (strncmp(name, "space/", 6u) != 0) { return false; } if (strncmp(name + 6u, dcfg->space_id, space_len) != 0) { return false; } if (name[6u + space_len] != '/') { return false; } return true; } static const char *amduatd_cap_kind_name(uint8_t kind) { switch (kind) { case AMDUATD_CAP_KIND_ARTIFACT_REF: return "artifact_ref"; case AMDUATD_CAP_KIND_POINTER_NAME: return "pointer_name"; case AMDUATD_CAP_KIND_PEL_VIEW: return "pel_view"; default: return "unknown"; } } static void amduatd_cap_token_hash_prefix(const uint8_t *bytes, size_t len, char out[9]) { uint8_t digest[32]; if (out == NULL) { return; } if (bytes == NULL || len == 0u || !amduatd_sha256_digest(amduat_octets(bytes, len), digest)) { strncpy(out, "unknown", 8u); out[8] = '\0'; return; } if (!amduat_hex_encode_lower(digest, 4u, out, 9u)) { strncpy(out, "unknown", 8u); out[8] = '\0'; return; } out[8] = '\0'; } static bool amduatd_cap_init(const char *root_path, amduatd_cap_state_t *cap) { char secrets_path[1024]; char key_path[1060]; struct stat st; int fd = -1; uint8_t key_bytes[32]; if (cap == NULL) { return false; } memset(cap, 0, sizeof(*cap)); if (root_path == NULL) { return false; } { int n = snprintf(secrets_path, sizeof(secrets_path), "%s/secrets", root_path); if (n <= 0 || (size_t)n >= sizeof(secrets_path)) { return false; } } if (stat(secrets_path, &st) != 0) { if (errno != ENOENT) { return false; } if (mkdir(secrets_path, 0700) != 0) { return false; } } else if (!S_ISDIR(st.st_mode)) { return false; } if (stat(secrets_path, &st) != 0) { return false; } if ((st.st_mode & 0077u) != 0u) { amduat_log(AMDUAT_LOG_WARN, "capabilities disabled: secrets dir permissions too open"); return false; } { int n = snprintf(key_path, sizeof(key_path), "%s/secrets/cap_hmac_key", root_path); if (n <= 0 || (size_t)n >= sizeof(key_path)) { return false; } } if (stat(key_path, &st) != 0) { if (errno != ENOENT) { return false; } fd = open(key_path, O_WRONLY | O_CREAT | O_EXCL, 0600); if (fd < 0) { return false; } if (!amduatd_read_urandom(key_bytes, sizeof(key_bytes))) { close(fd); return false; } if (!amduatd_write_all(fd, key_bytes, sizeof(key_bytes))) { close(fd); return false; } close(fd); } if (stat(key_path, &st) != 0) { return false; } if (!S_ISREG(st.st_mode) || (st.st_mode & 0077u) != 0u || st.st_size != (off_t)sizeof(key_bytes)) { amduat_log(AMDUAT_LOG_WARN, "capabilities disabled: cap_hmac_key permissions invalid"); return false; } fd = open(key_path, O_RDONLY); if (fd < 0) { return false; } if (!amduatd_read_exact(fd, key_bytes, sizeof(key_bytes))) { close(fd); return false; } close(fd); memcpy(cap->hmac_key, key_bytes, sizeof(key_bytes)); cap->hmac_key_len = sizeof(key_bytes); cap->enabled = true; return true; } static bool amduatd_cap_build_token(const amduatd_cap_token_t *token, const uint8_t *key, size_t key_len, uint8_t **out, size_t *out_len) { size_t total = 0u; size_t payload_len = 0u; size_t offset = 0u; size_t hmac_offset = 0u; uint8_t *buf = NULL; uint8_t hmac[32]; amduat_octets_t ref_bytes = amduat_octets(NULL, 0u); amduat_octets_t program_bytes = amduat_octets(NULL, 0u); amduat_octets_t *input_bytes = NULL; amduat_octets_t params_bytes = amduat_octets(NULL, 0u); if (out != NULL) { *out = NULL; } if (out_len != NULL) { *out_len = 0u; } if (token == NULL || out == NULL || out_len == NULL || key == NULL || key_len == 0u) { return false; } if (token->kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { if (!amduat_enc_asl1_core_encode_reference_v1(token->artifact_ref, &ref_bytes)) { return false; } payload_len = 4u + ref_bytes.len; } else if (token->kind == AMDUATD_CAP_KIND_POINTER_NAME) { if (token->pointer_name == NULL) { return false; } payload_len = 4u + strlen(token->pointer_name); } else if (token->kind == AMDUATD_CAP_KIND_PEL_VIEW) { size_t inputs_total = 0u; if (!amduat_enc_asl1_core_encode_reference_v1(token->program_ref, &program_bytes)) { return false; } if (token->input_refs_len != 0u) { input_bytes = (amduat_octets_t *)calloc(token->input_refs_len, sizeof(*input_bytes)); if (input_bytes == NULL) { free((void *)program_bytes.data); return false; } for (size_t i = 0u; i < token->input_refs_len; ++i) { if (!amduat_enc_asl1_core_encode_reference_v1(token->input_refs[i], &input_bytes[i])) { free((void *)program_bytes.data); for (size_t j = 0u; j < i; ++j) { free((void *)input_bytes[j].data); } free(input_bytes); return false; } if (!amduatd_add_size(&inputs_total, 4u + input_bytes[i].len)) { free((void *)program_bytes.data); for (size_t j = 0u; j <= i; ++j) { free((void *)input_bytes[j].data); } free(input_bytes); return false; } } } if (token->has_params_ref) { if (!amduat_enc_asl1_core_encode_reference_v1(token->params_ref, ¶ms_bytes)) { free((void *)program_bytes.data); if (input_bytes != NULL) { for (size_t i = 0u; i < token->input_refs_len; ++i) { free((void *)input_bytes[i].data); } free(input_bytes); } return false; } } payload_len = 4u + program_bytes.len + 4u + inputs_total + 1u; if (token->has_params_ref) { payload_len += 4u + params_bytes.len; } } else { return false; } if (!amduatd_add_size(&total, AMDUATD_CAP_MAGIC_LEN) || !amduatd_add_size(&total, 4u) || !amduatd_add_size(&total, 4u + token->space_len) || !amduatd_add_size(&total, 8u) || !amduatd_add_size(&total, 1u) || !amduatd_add_size(&total, payload_len) || !amduatd_add_size(&total, 4u) || !amduatd_add_size(&total, sizeof(hmac))) { free((void *)ref_bytes.data); free((void *)program_bytes.data); if (input_bytes != NULL) { for (size_t i = 0u; i < token->input_refs_len; ++i) { free((void *)input_bytes[i].data); } free(input_bytes); } free((void *)params_bytes.data); return false; } buf = (uint8_t *)malloc(total); if (buf == NULL) { free((void *)ref_bytes.data); free((void *)program_bytes.data); if (input_bytes != NULL) { for (size_t i = 0u; i < token->input_refs_len; ++i) { free((void *)input_bytes[i].data); } free(input_bytes); } free((void *)params_bytes.data); return false; } memcpy(buf + offset, k_amduatd_cap_magic, AMDUATD_CAP_MAGIC_LEN); offset += AMDUATD_CAP_MAGIC_LEN; amduatd_store_u32_le(buf + offset, AMDUATD_CAP_VERSION); offset += 4u; amduatd_store_u32_le(buf + offset, (uint32_t)token->space_len); offset += 4u; if (token->space_len != 0u && token->space != NULL) { memcpy(buf + offset, token->space, token->space_len); offset += token->space_len; } amduatd_store_u64_le(buf + offset, token->expiry_unix); offset += 8u; buf[offset++] = token->kind; if (token->kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { amduatd_store_u32_le(buf + offset, (uint32_t)ref_bytes.len); offset += 4u; if (ref_bytes.len != 0u) { memcpy(buf + offset, ref_bytes.data, ref_bytes.len); offset += ref_bytes.len; } } else if (token->kind == AMDUATD_CAP_KIND_POINTER_NAME) { uint32_t name_len = (uint32_t)strlen(token->pointer_name); amduatd_store_u32_le(buf + offset, name_len); offset += 4u; if (name_len != 0u) { memcpy(buf + offset, token->pointer_name, name_len); offset += name_len; } } else if (token->kind == AMDUATD_CAP_KIND_PEL_VIEW) { amduatd_store_u32_le(buf + offset, (uint32_t)program_bytes.len); offset += 4u; if (program_bytes.len != 0u) { memcpy(buf + offset, program_bytes.data, program_bytes.len); offset += program_bytes.len; } amduatd_store_u32_le(buf + offset, (uint32_t)token->input_refs_len); offset += 4u; if (input_bytes != NULL) { for (size_t i = 0u; i < token->input_refs_len; ++i) { amduatd_store_u32_le(buf + offset, (uint32_t)input_bytes[i].len); offset += 4u; if (input_bytes[i].len != 0u) { memcpy(buf + offset, input_bytes[i].data, input_bytes[i].len); offset += input_bytes[i].len; } } } buf[offset++] = token->has_params_ref ? 1u : 0u; if (token->has_params_ref) { amduatd_store_u32_le(buf + offset, (uint32_t)params_bytes.len); offset += 4u; if (params_bytes.len != 0u) { memcpy(buf + offset, params_bytes.data, params_bytes.len); offset += params_bytes.len; } } } hmac_offset = offset; amduatd_store_u32_le(buf + offset, (uint32_t)sizeof(hmac)); offset += 4u; if (!amduatd_hmac_sha256(key, key_len, buf, hmac_offset, hmac)) { free(buf); free((void *)ref_bytes.data); free((void *)program_bytes.data); if (input_bytes != NULL) { for (size_t i = 0u; i < token->input_refs_len; ++i) { free((void *)input_bytes[i].data); } free(input_bytes); } free((void *)params_bytes.data); return false; } memcpy(buf + offset, hmac, sizeof(hmac)); offset += sizeof(hmac); free((void *)ref_bytes.data); free((void *)program_bytes.data); if (input_bytes != NULL) { for (size_t i = 0u; i < token->input_refs_len; ++i) { free((void *)input_bytes[i].data); } free(input_bytes); } free((void *)params_bytes.data); if (offset != total) { free(buf); return false; } *out = buf; *out_len = total; return true; } static bool amduatd_cap_decode_token(const uint8_t *bytes, size_t len, amduatd_cap_token_t *out_token, size_t *out_hmac_offset, uint32_t *out_hmac_len, const uint8_t **out_hmac) { size_t offset = 0u; uint32_t version = 0u; uint32_t space_len = 0u; uint32_t ref_len = 0u; uint32_t input_count = 0u; uint32_t hmac_len = 0u; uint64_t expiry = 0u; uint8_t kind = 0u; amduatd_cap_token_t token; const uint8_t *hmac_ptr = NULL; if (out_token == NULL || out_hmac_offset == NULL || out_hmac_len == NULL || out_hmac == NULL) { return false; } memset(&token, 0, sizeof(token)); if (len < AMDUATD_CAP_MAGIC_LEN + 4u + 4u + 8u + 1u + 4u + 32u) { return false; } if (memcmp(bytes, k_amduatd_cap_magic, AMDUATD_CAP_MAGIC_LEN) != 0) { return false; } offset += AMDUATD_CAP_MAGIC_LEN; if (!amduatd_read_u32_le(bytes, len, &offset, &version) || version != AMDUATD_CAP_VERSION) { return false; } if (!amduatd_read_u32_le(bytes, len, &offset, &space_len)) { return false; } if (len - offset < space_len) { return false; } if (space_len != 0u) { token.space = (char *)malloc(space_len + 1u); if (token.space == NULL) { return false; } memcpy(token.space, bytes + offset, space_len); token.space[space_len] = '\0'; token.space_len = space_len; } offset += space_len; if (!amduatd_read_u64_le(bytes, len, &offset, &expiry)) { amduatd_cap_token_free(&token); return false; } token.expiry_unix = expiry; if (offset >= len) { amduatd_cap_token_free(&token); return false; } kind = bytes[offset++]; token.kind = kind; if (kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len)) { amduatd_cap_token_free(&token); return false; } if (len - offset < ref_len) { amduatd_cap_token_free(&token); return false; } if (!amduat_enc_asl1_core_decode_reference_v1( amduat_octets(bytes + offset, ref_len), &token.artifact_ref)) { amduatd_cap_token_free(&token); return false; } offset += ref_len; } else if (kind == AMDUATD_CAP_KIND_POINTER_NAME) { uint32_t name_len = 0u; if (!amduatd_read_u32_le(bytes, len, &offset, &name_len)) { amduatd_cap_token_free(&token); return false; } if (len - offset < name_len) { amduatd_cap_token_free(&token); return false; } token.pointer_name = (char *)malloc(name_len + 1u); if (token.pointer_name == NULL) { amduatd_cap_token_free(&token); return false; } memcpy(token.pointer_name, bytes + offset, name_len); token.pointer_name[name_len] = '\0'; offset += name_len; if (!amduat_asl_pointer_name_is_valid(token.pointer_name)) { amduatd_cap_token_free(&token); return false; } } else if (kind == AMDUATD_CAP_KIND_PEL_VIEW) { if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len)) { amduatd_cap_token_free(&token); return false; } if (len - offset < ref_len) { amduatd_cap_token_free(&token); return false; } if (!amduat_enc_asl1_core_decode_reference_v1( amduat_octets(bytes + offset, ref_len), &token.program_ref)) { amduatd_cap_token_free(&token); return false; } offset += ref_len; if (!amduatd_read_u32_le(bytes, len, &offset, &input_count)) { amduatd_cap_token_free(&token); return false; } if (input_count != 0u) { token.input_refs = (amduat_reference_t *)calloc( input_count, sizeof(*token.input_refs)); if (token.input_refs == NULL) { amduatd_cap_token_free(&token); return false; } token.input_refs_len = input_count; for (uint32_t i = 0u; i < input_count; ++i) { if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len) || len - offset < ref_len || !amduat_enc_asl1_core_decode_reference_v1( amduat_octets(bytes + offset, ref_len), &token.input_refs[i])) { amduatd_cap_token_free(&token); return false; } offset += ref_len; } } if (offset >= len) { amduatd_cap_token_free(&token); return false; } token.has_params_ref = bytes[offset++] != 0u; if (token.has_params_ref) { if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len) || len - offset < ref_len || !amduat_enc_asl1_core_decode_reference_v1( amduat_octets(bytes + offset, ref_len), &token.params_ref)) { amduatd_cap_token_free(&token); return false; } offset += ref_len; } } else { amduatd_cap_token_free(&token); return false; } *out_hmac_offset = offset; if (!amduatd_read_u32_le(bytes, len, &offset, &hmac_len)) { amduatd_cap_token_free(&token); return false; } if (len - offset < hmac_len) { amduatd_cap_token_free(&token); return false; } hmac_ptr = bytes + offset; offset += hmac_len; if (offset != len) { amduatd_cap_token_free(&token); return false; } *out_hmac_len = hmac_len; *out_hmac = hmac_ptr; *out_token = token; return true; } static bool amduatd_cap_verify_token(const amduatd_cap_state_t *cap, const amduatd_cfg_t *dcfg, const uint8_t *bytes, size_t len, amduatd_cap_token_t *out_token, const char **out_reason) { amduatd_cap_token_t token; size_t hmac_offset = 0u; uint32_t hmac_len = 0u; const uint8_t *hmac = NULL; uint8_t expected[32]; time_t now; if (out_reason != NULL) { *out_reason = "invalid"; } if (cap == NULL || dcfg == NULL || bytes == NULL || out_token == NULL) { return false; } if (!amduatd_cap_decode_token(bytes, len, &token, &hmac_offset, &hmac_len, &hmac)) { return false; } if (hmac_len != sizeof(expected)) { *out_token = token; return false; } if (!amduatd_hmac_sha256(cap->hmac_key, cap->hmac_key_len, bytes, hmac_offset, expected) || !amduatd_ct_memcmp(expected, hmac, sizeof(expected))) { if (out_reason != NULL) { *out_reason = "bad-mac"; } *out_token = token; return false; } now = time(NULL); if (now < 0 || token.expiry_unix < (uint64_t)now) { if (out_reason != NULL) { *out_reason = "expired"; } *out_token = token; return false; } if (dcfg->space_enabled) { if (token.space_len == 0u || token.space == NULL || strcmp(token.space, dcfg->space_id) != 0) { if (out_reason != NULL) { *out_reason = "wrong-space"; } *out_token = token; return false; } } else { if (token.space_len != 0u) { if (out_reason != NULL) { *out_reason = "wrong-space"; } *out_token = token; return false; } } if (token.kind == AMDUATD_CAP_KIND_POINTER_NAME && !amduatd_pointer_name_scoped_for_space(dcfg, token.pointer_name)) { if (out_reason != NULL) { *out_reason = "wrong-space"; } *out_token = token; return false; } *out_token = token; if (out_reason != NULL) { *out_reason = "ok"; } return true; } static bool amduatd_build_prefixed_bytes(const char *prefix, const char *text, uint8_t **out, size_t *out_len) { size_t p_len; size_t t_len; size_t len; uint8_t *buf; if (out != NULL) { *out = NULL; } if (out_len != NULL) { *out_len = 0; } if (prefix == NULL || text == NULL || out == NULL || out_len == NULL) { return false; } p_len = strlen(prefix); t_len = strlen(text); if (p_len > SIZE_MAX - 1u || t_len > SIZE_MAX - (p_len + 1u)) { return false; } len = p_len + 1u + t_len; buf = (uint8_t *)malloc(len); if (buf == NULL) { return false; } memcpy(buf, prefix, p_len); buf[p_len] = 0; memcpy(buf + p_len + 1u, text, t_len); *out = buf; *out_len = len; return true; } static bool amduatd_concepts_seed_relation(amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_cfg_t *dcfg, const char *relation_name, amduat_reference_t *out_ref) { uint8_t *bytes = NULL; size_t bytes_len = 0; amduat_artifact_t artifact; char *scoped_name = NULL; bool ok = false; if (out_ref == NULL) { return false; } *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); if (store == NULL || cfg == NULL || relation_name == NULL) { return false; } if (dcfg != NULL && dcfg->space_enabled) { if (!amduatd_scope_name_cstr(dcfg, relation_name, &scoped_name)) { return false; } } else if (!amduatd_strdup_cstr(relation_name, &scoped_name)) { return false; } if (!amduatd_build_prefixed_bytes("AMDUATD/RELATION/1", scoped_name, &bytes, &bytes_len)) { goto seed_cleanup; } artifact = amduat_artifact(amduat_octets(bytes, bytes_len)); if (!amduat_asl_ref_derive(artifact, cfg->config.encoding_profile_id, cfg->config.hash_id, out_ref, NULL)) { goto seed_cleanup; } (void)amduat_asl_store_put(store, artifact, out_ref); ok = true; seed_cleanup: free(bytes); free(scoped_name); return ok; } static bool amduatd_concepts_ensure_alias(amduatd_concepts_t *c, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_cfg_t *dcfg, const char *alias_name, amduat_reference_t concept_ref) { amduat_reference_t existing; amduat_reference_t name_ref; amduat_reference_t edge_ref; char *scoped_name = NULL; bool ok = false; memset(&existing, 0, sizeof(existing)); memset(&name_ref, 0, sizeof(name_ref)); memset(&edge_ref, 0, sizeof(edge_ref)); if (c == NULL || store == NULL || cfg == NULL || alias_name == NULL) { return false; } if (!amduatd_scope_name_cstr(dcfg, alias_name, &scoped_name)) { amduat_log(AMDUAT_LOG_ERROR, "ensure alias: scope failed for %s", alias_name); return false; } if (amduatd_concepts_lookup_alias(store, cfg, c, scoped_name, &existing)) { amduat_reference_free(&existing); ok = true; goto alias_cleanup; } if (!amduatd_concepts_put_name_artifact(store, scoped_name, &name_ref)) { amduat_log(AMDUAT_LOG_ERROR, "ensure alias: name artifact failed for %s", scoped_name); goto alias_cleanup; } if (!amduatd_concepts_put_edge(store, c, name_ref, concept_ref, c->rel_aliases_ref, amduat_octets(NULL, 0u), &edge_ref)) { amduat_log(AMDUAT_LOG_ERROR, "ensure alias: put edge failed for %s", scoped_name); goto alias_cleanup; } ok = true; alias_cleanup: amduat_reference_free(&name_ref); amduat_reference_free(&edge_ref); free(scoped_name); return ok; } static bool amduatd_concepts_load_edges(amduatd_concepts_t *c, amduat_asl_store_t *store) { amduat_asl_collection_view_t view; uint64_t from = 0u; bool ok = false; if (c == NULL || store == NULL) { return false; } amduatd_edge_list_clear(&c->edges); while (true) { memset(&view, 0, sizeof(view)); if (!amduatd_collection_view(store, c->root_path, c->edge_collection_name, from, AMDUATD_EDGE_VIEW_BATCH, &view)) { return false; } for (size_t i = 0u; i < view.refs_len; ++i) { amduat_reference_t record_ref = view.refs[i]; amduat_asl_record_t record; amduat_reference_t src_ref; amduat_reference_t dst_ref; char *rel = NULL; amduatd_edge_entry_t entry; amduat_asl_store_error_t err; bool schema_ok = false; memset(&record, 0, sizeof(record)); err = amduat_asl_record_store_get(store, record_ref, &record); if (err != AMDUAT_ASL_STORE_OK) { continue; } if (record.schema.len == strlen(AMDUATD_EDGE_SCHEMA) && memcmp(record.schema.data, AMDUATD_EDGE_SCHEMA, record.schema.len) == 0) { schema_ok = true; } if (!schema_ok || !amduatd_edge_payload_decode(record.payload, &src_ref, &dst_ref, &rel)) { amduat_asl_record_free(&record); continue; } amduat_asl_record_free(&record); memset(&entry, 0, sizeof(entry)); if (!amduat_reference_clone(record_ref, &entry.record_ref)) { amduat_reference_free(&src_ref); amduat_reference_free(&dst_ref); free(rel); goto load_cleanup; } entry.src_ref = src_ref; entry.dst_ref = dst_ref; entry.rel = rel; if (!amduatd_edge_list_push(&c->edges, &entry)) { amduatd_edge_entry_free(&entry); goto load_cleanup; } amduatd_edge_entry_free(&entry); } if (view.refs_len == 0u || view.computed_up_to_offset <= from) { break; } from = view.computed_up_to_offset; amduat_asl_collection_view_free(&view); } ok = true; load_cleanup: amduat_asl_collection_view_free(&view); return ok; } static bool amduatd_concepts_append_edge_record(amduatd_concepts_t *c, amduat_asl_store_t *store, amduat_reference_t src, amduat_reference_t dst, const char *rel, amduat_octets_t actor, amduat_reference_t *out_ref) { amduat_octets_t payload = amduat_octets(NULL, 0u); amduat_reference_t record_ref; amduatd_edge_entry_t entry; uint64_t offset = 0u; if (out_ref != NULL) { *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); } if (c == NULL || store == NULL || rel == NULL) { return false; } if (!amduatd_edge_payload_encode(src, dst, rel, &payload)) { return false; } if (amduat_asl_record_store_put(store, amduat_octets(AMDUATD_EDGE_SCHEMA, strlen(AMDUATD_EDGE_SCHEMA)), payload, &record_ref) != AMDUAT_ASL_STORE_OK) { free((void *)payload.data); return false; } free((void *)payload.data); { amduat_asl_collection_error_t append_err = amduat_asl_collection_append(&c->edge_collection, c->edge_collection_name, record_ref, AMDUATD_EDGE_COLLECTION_KIND, actor, &offset); if (append_err != AMDUAT_ASL_COLLECTION_OK) { if (c->edge_collection_name != NULL && !amduat_asl_pointer_name_is_valid(c->edge_collection_name)) { amduat_log(AMDUAT_LOG_ERROR, "edge collection name invalid: %s", c->edge_collection_name); } if (c->edge_collection_name != NULL) { size_t name_len = strlen(c->edge_collection_name); size_t total_len = 11u + name_len + 4u + 1u; char *log_name = (char *)malloc(total_len); if (log_name != NULL) { size_t pos = 0u; memcpy(log_name + pos, "collection/", 11u); pos += 11u; memcpy(log_name + pos, c->edge_collection_name, name_len); pos += name_len; memcpy(log_name + pos, "/log", 4u); pos += 4u; log_name[pos] = '\0'; if (!amduat_asl_pointer_name_is_valid(log_name)) { amduat_log(AMDUAT_LOG_ERROR, "edge log name invalid: %s", log_name); } { size_t head_len = 4u + pos + 5u + 1u; char *head_name = (char *)malloc(head_len); if (head_name != NULL) { size_t hpos = 0u; memcpy(head_name + hpos, "log/", 4u); hpos += 4u; memcpy(head_name + hpos, log_name, pos); hpos += pos; memcpy(head_name + hpos, "/head", 5u); hpos += 5u; head_name[hpos] = '\0'; if (!amduat_asl_pointer_name_is_valid(head_name)) { amduat_log(AMDUAT_LOG_ERROR, "edge log head name invalid: %s", head_name); } else { bool head_exists = false; amduat_reference_t head_ref; memset(&head_ref, 0, sizeof(head_ref)); amduat_asl_pointer_error_t ptr_err = amduat_asl_pointer_get(&c->edge_collection.pointer_store, head_name, &head_exists, &head_ref); if (ptr_err != AMDUAT_ASL_POINTER_OK) { amduat_log(AMDUAT_LOG_ERROR, "edge log head pointer get failed: %d", ptr_err); } amduat_reference_free(&head_ref); } free(head_name); } } free(log_name); } } amduat_log(AMDUAT_LOG_ERROR, "edge append failed for %s (err=%d)", c->edge_collection_name != NULL ? c->edge_collection_name : "?", (int)append_err); amduat_reference_free(&record_ref); return false; } } memset(&entry, 0, sizeof(entry)); entry.record_ref = record_ref; entry.src_ref = src; entry.dst_ref = dst; entry.rel = (char *)rel; if (!amduatd_edge_list_push(&c->edges, &entry)) { amduat_reference_free(&record_ref); return false; } if (offset != 0u && (offset % 256u) == 0u) { amduat_reference_t snapshot_ref; bool swapped = false; memset(&snapshot_ref, 0, sizeof(snapshot_ref)); (void)amduat_asl_collection_snapshot(&c->edge_collection, c->edge_collection_name, offset, &snapshot_ref, &swapped); amduat_reference_free(&snapshot_ref); } if (out_ref != NULL) { *out_ref = record_ref; } else { amduat_reference_free(&record_ref); } return true; } static bool amduatd_concepts_migrate_edges(amduatd_concepts_t *c, amduat_asl_store_t *store) { uint8_t *file_bytes = NULL; size_t file_len = 0; bool ok = true; if (c == NULL || store == NULL) { return false; } if (!amduatd_read_file(c->edges_path, &file_bytes, &file_len)) { return true; } { char *text = (char *)file_bytes; char *line = text; char *end = text + file_len; while (line < end) { char *nl = memchr(line, '\n', (size_t)(end - line)); size_t n = nl != NULL ? (size_t)(nl - line) : (size_t)(end - line); char *tmp; amduat_reference_t ref; bool ok_ref; while (n != 0 && (line[n - 1] == '\r' || line[n - 1] == ' ' || line[n - 1] == '\t')) { n--; } while (n != 0 && (*line == ' ' || *line == '\t')) { line++; n--; } if (n != 0) { tmp = (char *)malloc(n + 1u); if (tmp == NULL) { ok = false; break; } memcpy(tmp, line, n); tmp[n] = '\0'; memset(&ref, 0, sizeof(ref)); ok_ref = amduat_asl_ref_decode_hex(tmp, &ref); free(tmp); if (ok_ref) { amduat_artifact_t artifact; amduat_tgk_edge_body_t edge; const char *rel_name = NULL; memset(&artifact, 0, sizeof(artifact)); if (amduat_asl_store_get(store, ref, &artifact) == AMDUAT_ASL_STORE_OK && artifact.has_type_tag && artifact.type_tag.tag_id == AMDUAT_TYPE_TAG_TGK1_EDGE_V1) { memset(&edge, 0, sizeof(edge)); if (amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) { if (edge.from_len == 1 && edge.to_len == 1) { rel_name = amduatd_relation_name_for_ref(c, edge.payload); } if (rel_name != NULL) { if (!amduatd_concepts_append_edge_record(c, store, edge.from[0], edge.to[0], rel_name, amduat_octets(NULL, 0u), NULL)) { ok = false; } } amduat_enc_tgk1_edge_free(&edge); } amduat_asl_artifact_free(&artifact); } else if (artifact.bytes.data != NULL) { amduat_asl_artifact_free(&artifact); } amduat_reference_free(&ref); if (!ok) { break; } } } if (nl == NULL) { break; } line = nl + 1; } } free(file_bytes); if (ok) { char migrated_path[1100]; (void)snprintf(migrated_path, sizeof(migrated_path), "%s.migrated", c->edges_path); (void)rename(c->edges_path, migrated_path); } return ok; } static bool amduatd_concepts_migrate_unscoped_edges( amduatd_concepts_t *c, amduat_asl_store_t *store, const char *unscoped_collection) { amduat_asl_collection_view_t view; uint64_t from = 0u; bool ok = false; if (c == NULL || store == NULL || unscoped_collection == NULL) { return false; } while (true) { memset(&view, 0, sizeof(view)); if (!amduatd_collection_view(store, c->root_path, unscoped_collection, from, AMDUATD_EDGE_VIEW_BATCH, &view)) { return false; } for (size_t i = 0u; i < view.refs_len; ++i) { uint64_t offset = 0u; if (amduat_asl_collection_append(&c->edge_collection, c->edge_collection_name, view.refs[i], AMDUATD_EDGE_COLLECTION_KIND, amduat_octets(NULL, 0u), &offset) != AMDUAT_ASL_COLLECTION_OK) { goto migrate_cleanup; } if (offset != 0u && (offset % 256u) == 0u) { amduat_reference_t snapshot_ref; bool swapped = false; memset(&snapshot_ref, 0, sizeof(snapshot_ref)); (void)amduat_asl_collection_snapshot(&c->edge_collection, c->edge_collection_name, offset, &snapshot_ref, &swapped); amduat_reference_free(&snapshot_ref); } } if (view.refs_len == 0u || view.computed_up_to_offset <= from) { break; } from = view.computed_up_to_offset; amduat_asl_collection_view_free(&view); } ok = true; migrate_cleanup: amduat_asl_collection_view_free(&view); return ok; } static bool amduatd_concepts_init(amduatd_concepts_t *c, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_cfg_t *dcfg, const char *root_path) { amduat_octets_t scoped_collection = amduat_octets(NULL, 0u); if (c == NULL || store == NULL || cfg == NULL || root_path == NULL) { return false; } memset(c, 0, sizeof(*c)); c->root_path = root_path; (void)snprintf(c->edges_path, sizeof(c->edges_path), "%s/%s", root_path, AMDUATD_EDGES_FILE); if (!amduatd_scope_name(dcfg, AMDUATD_EDGE_COLLECTION, &scoped_collection)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: scope edges failed"); return false; } c->edge_collection_name = (char *)scoped_collection.data; if (c->edge_collection_name == NULL) { return false; } if (!amduat_asl_collection_store_init(&c->edge_collection, root_path, store)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: collection store init failed"); return false; } if (!amduatd_concepts_seed_relation(store, cfg, dcfg, "aliases", &c->rel_aliases_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: seed aliases failed"); return false; } if (!amduatd_concepts_ensure_alias(c, store, cfg, dcfg, "ms.aliases", c->rel_aliases_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: ensure ms.aliases failed"); return false; } if (!amduatd_concepts_seed_relation(store, cfg, dcfg, "materializesAs", &c->rel_materializes_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: seed materializesAs failed"); return false; } if (!amduatd_concepts_ensure_alias(c, store, cfg, dcfg, "ms.materializes_as", c->rel_materializes_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: ensure ms.materializes_as failed"); return false; } if (!amduatd_concepts_seed_relation(store, cfg, dcfg, "represents", &c->rel_represents_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: seed represents failed"); return false; } if (!amduatd_concepts_ensure_alias(c, store, cfg, dcfg, "ms.represents", c->rel_represents_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: ensure ms.represents failed"); return false; } if (!amduatd_concepts_seed_relation(store, cfg, dcfg, "requiresKey", &c->rel_requires_key_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: seed requiresKey failed"); return false; } if (!amduatd_concepts_ensure_alias(c, store, cfg, dcfg, "ms.requires_key", c->rel_requires_key_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: ensure ms.requires_key failed"); return false; } if (!amduatd_concepts_seed_relation(store, cfg, dcfg, "withinDomain", &c->rel_within_domain_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: seed withinDomain failed"); return false; } if (!amduatd_concepts_ensure_alias(c, store, cfg, dcfg, "ms.within_domain", c->rel_within_domain_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: ensure ms.within_domain failed"); return false; } if (!amduatd_concepts_seed_relation(store, cfg, dcfg, "computedBy", &c->rel_computed_by_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: seed computedBy failed"); return false; } if (!amduatd_concepts_ensure_alias(c, store, cfg, dcfg, "ms.computed_by", c->rel_computed_by_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: ensure ms.computed_by failed"); return false; } if (!amduatd_concepts_seed_relation(store, cfg, dcfg, "hasProvenance", &c->rel_has_provenance_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: seed hasProvenance failed"); return false; } if (!amduatd_concepts_ensure_alias(c, store, cfg, dcfg, "ms.has_provenance", c->rel_has_provenance_ref)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: ensure ms.has_provenance failed"); return false; } if (!amduatd_concepts_load_edges(c, store)) { amduat_log(AMDUAT_LOG_ERROR, "concepts init: load edges failed"); return false; } if (c->edges.len == 0u) { if (dcfg != NULL && dcfg->space_enabled) { if (dcfg->migrate_unscoped_edges) { if (!amduatd_concepts_migrate_unscoped_edges( c, store, AMDUATD_EDGE_COLLECTION)) { return false; } if (!amduatd_concepts_load_edges(c, store)) { return false; } } } else { if (!amduatd_concepts_migrate_edges(c, store)) { return false; } if (!amduatd_concepts_load_edges(c, store)) { return false; } } } return true; } static bool amduatd_concepts_derive_name_ref( const amduat_asl_store_fs_config_t *cfg, const char *name, amduat_reference_t *out_ref) { uint8_t *bytes = NULL; size_t bytes_len = 0; amduat_artifact_t artifact; if (out_ref == NULL) { return false; } *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); if (cfg == NULL || name == NULL) { return false; } if (!amduatd_build_prefixed_bytes("AMDUATD/NAME/1", name, &bytes, &bytes_len)) { return false; } artifact = amduat_artifact(amduat_octets(bytes, bytes_len)); if (!amduat_asl_ref_derive(artifact, cfg->config.encoding_profile_id, cfg->config.hash_id, out_ref, NULL)) { free(bytes); return false; } free(bytes); return true; } static bool amduatd_concepts_put_name_artifact(amduat_asl_store_t *store, const char *name, amduat_reference_t *out_ref) { uint8_t *bytes = NULL; size_t bytes_len = 0; amduat_artifact_t artifact; if (out_ref != NULL) { *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); } if (store == NULL || name == NULL || out_ref == NULL) { return false; } if (!amduatd_build_prefixed_bytes("AMDUATD/NAME/1", name, &bytes, &bytes_len)) { return false; } artifact = amduat_artifact(amduat_octets(bytes, bytes_len)); if (amduat_asl_store_put(store, artifact, out_ref) != AMDUAT_ASL_STORE_OK) { free(bytes); return false; } free(bytes); return true; } static bool amduatd_concepts_put_concept_id(amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, amduat_reference_t *out_ref) { uint8_t rnd[16]; uint8_t *bytes = NULL; size_t bytes_len = 0; amduat_artifact_t artifact; if (out_ref != NULL) { *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); } if (store == NULL || cfg == NULL || out_ref == NULL) { return false; } if (!amduatd_read_urandom(rnd, sizeof(rnd))) { return false; } bytes_len = strlen("AMDUATD/CONCEPT-ID/1") + 1u + sizeof(rnd); bytes = (uint8_t *)malloc(bytes_len); if (bytes == NULL) { return false; } memcpy(bytes, "AMDUATD/CONCEPT-ID/1", strlen("AMDUATD/CONCEPT-ID/1")); bytes[strlen("AMDUATD/CONCEPT-ID/1")] = 0; memcpy(bytes + strlen("AMDUATD/CONCEPT-ID/1") + 1u, rnd, sizeof(rnd)); artifact = amduat_artifact(amduat_octets(bytes, bytes_len)); if (!amduat_asl_ref_derive(artifact, cfg->config.encoding_profile_id, cfg->config.hash_id, out_ref, NULL)) { free(bytes); return false; } if (amduat_asl_store_put(store, artifact, out_ref) != AMDUAT_ASL_STORE_OK) { free(bytes); return false; } free(bytes); return true; } static bool amduatd_concepts_put_edge(amduat_asl_store_t *store, amduatd_concepts_t *c, amduat_reference_t from, amduat_reference_t to, amduat_reference_t relation_concept_ref, amduat_octets_t actor, amduat_reference_t *out_edge_ref) { const char *rel_name = NULL; if (out_edge_ref != NULL) { *out_edge_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); } if (store == NULL || c == NULL || out_edge_ref == NULL) { return false; } rel_name = amduatd_relation_name_for_ref(c, relation_concept_ref); if (rel_name == NULL) { return false; } return amduatd_concepts_append_edge_record(c, store, from, to, rel_name, actor, out_edge_ref); } static bool amduatd_concepts_lookup_alias(amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *c, const char *name, amduat_reference_t *out_concept_ref) { amduat_reference_t name_ref; size_t i; if (out_concept_ref != NULL) { *out_concept_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); } if (store == NULL || cfg == NULL || c == NULL || name == NULL || out_concept_ref == NULL) { return false; } if (!amduatd_concepts_derive_name_ref(cfg, name, &name_ref)) { return false; } for (i = c->edges.len; i > 0; --i) { const amduatd_edge_entry_t *entry = &c->edges.items[i - 1u]; if (entry->rel == NULL) { continue; } if (strcmp(entry->rel, AMDUATD_REL_ALIAS) != 0) { continue; } if (amduat_reference_eq(entry->src_ref, name_ref)) { amduat_reference_clone(entry->dst_ref, out_concept_ref); amduat_reference_free(&name_ref); return true; } } amduat_reference_free(&name_ref); return false; } static bool amduatd_concepts_resolve_latest(amduat_asl_store_t *store, const amduatd_concepts_t *c, amduat_reference_t concept_ref, amduat_reference_t *out_ref) { size_t i; if (out_ref != NULL) { *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); } if (store == NULL || c == NULL || out_ref == NULL) { return false; } for (i = c->edges.len; i > 0; --i) { const amduatd_edge_entry_t *entry = &c->edges.items[i - 1u]; if (entry->rel == NULL) { continue; } if (strcmp(entry->rel, AMDUATD_REL_MATERIALIZES) != 0) { continue; } if (amduat_reference_eq(entry->src_ref, concept_ref)) { amduat_reference_clone(entry->dst_ref, out_ref); return true; } } return false; } static bool amduatd_parse_name_artifact(amduat_artifact_t artifact, const amduatd_cfg_t *dcfg, char *out, size_t cap) { const uint8_t *bytes; size_t len; const char *prefix = "AMDUATD/NAME/1"; size_t prefix_len; size_t i; size_t name_len; char scoped[AMDUAT_ASL_POINTER_NAME_MAX + 1u]; if (out != NULL && cap != 0) { out[0] = '\0'; } if (out == NULL || cap == 0) { return false; } if (artifact.bytes.len != 0 && artifact.bytes.data == NULL) { return false; } bytes = artifact.bytes.data; len = artifact.bytes.len; prefix_len = strlen(prefix); if (len < prefix_len + 1u) { return false; } if (memcmp(bytes, prefix, prefix_len) != 0 || bytes[prefix_len] != 0) { return false; } for (i = prefix_len + 1u; i < len; ++i) { if (bytes[i] == 0) { return false; } } name_len = len - (prefix_len + 1u); if (name_len == 0 || name_len >= sizeof(scoped)) { return false; } memcpy(scoped, bytes + prefix_len + 1u, name_len); scoped[name_len] = '\0'; return amduatd_unscoped_name(dcfg, scoped, out, cap); } static bool amduatd_handle_get_concepts(int fd, amduat_asl_store_t *store, const amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg) { amduatd_strbuf_t b; bool first = true; size_t i; if (store == NULL || concepts == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } memset(&b, 0, sizeof(b)); if (!amduatd_strbuf_append_cstr(&b, "{\"concepts\":[")) { amduatd_strbuf_free(&b); return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } for (i = 0; i < concepts->edges.len; ++i) { const amduatd_edge_entry_t *entry = &concepts->edges.items[i]; amduat_artifact_t artifact; amduat_asl_store_error_t err; char name[AMDUAT_ASL_POINTER_NAME_MAX + 1u]; char *concept_hex = NULL; if (entry->rel == NULL || strcmp(entry->rel, AMDUATD_REL_ALIAS) != 0) { continue; } memset(&artifact, 0, sizeof(artifact)); err = amduat_asl_store_get(store, entry->src_ref, &artifact); if (err != AMDUAT_ASL_STORE_OK || !amduatd_parse_name_artifact(artifact, dcfg, name, sizeof(name))) { amduat_asl_artifact_free(&artifact); continue; } amduat_asl_artifact_free(&artifact); if (!amduat_asl_ref_encode_hex(entry->dst_ref, &concept_hex)) { continue; } if (!first) { (void)amduatd_strbuf_append_char(&b, ','); } first = false; (void)amduatd_strbuf_append_cstr(&b, "{\"name\":\""); (void)amduatd_strbuf_append_cstr(&b, name); (void)amduatd_strbuf_append_cstr(&b, "\",\"concept_ref\":\""); (void)amduatd_strbuf_append_cstr(&b, concept_hex); (void)amduatd_strbuf_append_cstr(&b, "\"}"); free(concept_hex); } if (!amduatd_strbuf_append_cstr(&b, "]}\n")) { amduatd_strbuf_free(&b); return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } { bool ok = amduatd_http_send_json(fd, 200, "OK", b.data, false); amduatd_strbuf_free(&b); return ok; } } static bool amduatd_append_relation_entry(amduatd_strbuf_t *b, bool *first, const char *name, amduat_reference_t ref) { char *hex = NULL; if (b == NULL || first == NULL || name == NULL) { return false; } if (!amduat_asl_ref_encode_hex(ref, &hex)) { return false; } if (!*first) { (void)amduatd_strbuf_append_char(b, ','); } *first = false; (void)amduatd_strbuf_append_cstr(b, "{\"name\":\""); (void)amduatd_strbuf_append_cstr(b, name); (void)amduatd_strbuf_append_cstr(b, "\",\"concept_ref\":\""); (void)amduatd_strbuf_append_cstr(b, hex); (void)amduatd_strbuf_append_cstr(b, "\"}"); free(hex); return true; } static bool amduatd_handle_get_relations(int fd, const amduatd_concepts_t *concepts) { amduatd_strbuf_t b; bool first = true; if (concepts == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } memset(&b, 0, sizeof(b)); if (!amduatd_strbuf_append_cstr(&b, "{\"relations\":[")) { amduatd_strbuf_free(&b); return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } if (!amduatd_append_relation_entry(&b, &first, "ms.aliases", concepts->rel_aliases_ref) || !amduatd_append_relation_entry(&b, &first, "ms.materializes_as", concepts->rel_materializes_ref) || !amduatd_append_relation_entry(&b, &first, "ms.represents", concepts->rel_represents_ref) || !amduatd_append_relation_entry(&b, &first, "ms.requires_key", concepts->rel_requires_key_ref) || !amduatd_append_relation_entry(&b, &first, "ms.within_domain", concepts->rel_within_domain_ref) || !amduatd_append_relation_entry(&b, &first, "ms.computed_by", concepts->rel_computed_by_ref) || !amduatd_append_relation_entry(&b, &first, "ms.has_provenance", concepts->rel_has_provenance_ref)) { amduatd_strbuf_free(&b); return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } (void)amduatd_strbuf_append_cstr(&b, "]}\n"); { bool ok = amduatd_http_send_json(fd, 200, "OK", b.data, false); amduatd_strbuf_free(&b); return ok; } } static bool amduatd_handle_get_concept(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const char *name) { amduat_reference_t concept_ref; amduat_reference_t latest_ref; amduatd_strbuf_t b; char *concept_hex = NULL; char *latest_hex = NULL; char *scoped_name = NULL; bool have_latest = false; size_t i; size_t version_count = 0; memset(&concept_ref, 0, sizeof(concept_ref)); memset(&latest_ref, 0, sizeof(latest_ref)); memset(&b, 0, sizeof(b)); if (store == NULL || cfg == NULL || concepts == NULL || name == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } if (!amduatd_scope_name_cstr(dcfg, name, &scoped_name)) { return amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); } amduatd_log_scoped_name(dcfg, name, scoped_name); if (!amduatd_concepts_lookup_alias(store, cfg, concepts, scoped_name, &concept_ref)) { free(scoped_name); return amduatd_send_json_error(fd, 404, "Not Found", "unknown concept"); } free(scoped_name); if (amduatd_concepts_resolve_latest(store, concepts, concept_ref, &latest_ref)) { have_latest = true; } if (!amduat_asl_ref_encode_hex(concept_ref, &concept_hex)) { amduat_reference_free(&concept_ref); amduat_reference_free(&latest_ref); return amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); } if (have_latest) { if (!amduat_asl_ref_encode_hex(latest_ref, &latest_hex)) { free(concept_hex); amduat_reference_free(&concept_ref); amduat_reference_free(&latest_ref); return amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); } } if (!amduatd_strbuf_append_cstr(&b, "{")) { free(concept_hex); free(latest_hex); amduat_reference_free(&concept_ref); amduat_reference_free(&latest_ref); return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } (void)amduatd_strbuf_append_cstr(&b, "\"name\":\""); (void)amduatd_strbuf_append_cstr(&b, name); (void)amduatd_strbuf_append_cstr(&b, "\",\"concept_ref\":\""); (void)amduatd_strbuf_append_cstr(&b, concept_hex); (void)amduatd_strbuf_append_cstr(&b, "\",\"latest_ref\":"); if (latest_hex != NULL) { (void)amduatd_strbuf_append_cstr(&b, "\""); (void)amduatd_strbuf_append_cstr(&b, latest_hex); (void)amduatd_strbuf_append_cstr(&b, "\""); } else { (void)amduatd_strbuf_append_cstr(&b, "null"); } (void)amduatd_strbuf_append_cstr(&b, ",\"versions\":["); for (i = 0; i < concepts->edges.len; ++i) { const amduatd_edge_entry_t *entry = &concepts->edges.items[i]; char *edge_hex = NULL; char *ref_hex = NULL; if (entry->rel == NULL || strcmp(entry->rel, AMDUATD_REL_MATERIALIZES) != 0) { continue; } if (!amduat_reference_eq(entry->src_ref, concept_ref)) { continue; } if (!amduat_asl_ref_encode_hex(entry->record_ref, &edge_hex) || !amduat_asl_ref_encode_hex(entry->dst_ref, &ref_hex)) { free(edge_hex); free(ref_hex); continue; } if (version_count != 0) { (void)amduatd_strbuf_append_char(&b, ','); } version_count++; (void)amduatd_strbuf_append_cstr(&b, "{\"edge_ref\":\""); (void)amduatd_strbuf_append_cstr(&b, edge_hex); (void)amduatd_strbuf_append_cstr(&b, "\",\"ref\":\""); (void)amduatd_strbuf_append_cstr(&b, ref_hex); (void)amduatd_strbuf_append_cstr(&b, "\"}"); free(edge_hex); free(ref_hex); if (version_count >= 64u) { break; } } (void)amduatd_strbuf_append_cstr(&b, "]}\n"); free(concept_hex); free(latest_hex); amduat_reference_free(&concept_ref); amduat_reference_free(&latest_ref); { bool ok = amduatd_http_send_json(fd, 200, "OK", b.data, false); amduatd_strbuf_free(&b); return ok; } } static bool amduatd_read_exact(int fd, uint8_t *buf, size_t len) { size_t off = 0; while (off < len) { ssize_t n = read(fd, buf + off, len - off); if (n < 0) { if (errno == EINTR) { continue; } return false; } if (n == 0) { return false; } off += (size_t)n; } return true; } static bool amduatd_read_line(int fd, char *buf, size_t cap, size_t *out_len) { size_t len = 0; while (len + 1 < cap) { char c = 0; ssize_t n = read(fd, &c, 1); if (n < 0) { if (errno == EINTR) { continue; } return false; } if (n == 0) { return false; } if (c == '\n') { break; } buf[len++] = c; } if (len == 0) { return false; } if (len > 0 && buf[len - 1] == '\r') { len--; } buf[len] = '\0'; if (out_len != NULL) { *out_len = len; } return true; } static void amduatd_http_req_init(amduatd_http_req_t *req) { memset(req, 0, sizeof(*req)); } typedef struct { uid_t *uids; size_t len; size_t cap; } amduatd_allowlist_t; static void amduatd_allowlist_free(amduatd_allowlist_t *list) { if (list == NULL) { return; } free(list->uids); list->uids = NULL; list->len = 0; list->cap = 0; } static bool amduatd_allowlist_add(amduatd_allowlist_t *list, uid_t uid) { if (list == NULL) { return false; } for (size_t i = 0u; i < list->len; ++i) { if (list->uids[i] == uid) { return true; } } if (list->len == list->cap) { size_t next_cap = list->cap != 0 ? list->cap * 2u : 8u; uid_t *next = (uid_t *)realloc(list->uids, next_cap * sizeof(*next)); if (next == NULL) { return false; } list->uids = next; list->cap = next_cap; } list->uids[list->len++] = uid; return true; } static bool amduatd_actor_allowed(const amduatd_allowlist_t *list, bool has_uid, uid_t uid) { if (list == NULL || list->len == 0u) { return true; } if (!has_uid) { return false; } for (size_t i = 0u; i < list->len; ++i) { if (list->uids[i] == uid) { return true; } } return false; } static bool amduatd_get_peer_actor(int client_fd, amduat_octets_t *out_actor, bool *out_has_actor, bool *out_has_uid, uid_t *out_uid) { char *actor = NULL; if (out_actor == NULL || out_has_actor == NULL || out_has_uid == NULL || out_uid == NULL) { return false; } *out_actor = amduat_octets(NULL, 0u); *out_has_actor = false; *out_has_uid = false; *out_uid = 0; #ifdef SO_PEERCRED { struct ucred cred; socklen_t len = (socklen_t)sizeof(cred); if (getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == 0 && len == sizeof(cred)) { int needed = snprintf(NULL, 0, "uid:%u gid:%u pid:%u", (unsigned int)cred.uid, (unsigned int)cred.gid, (unsigned int)cred.pid); if (needed < 0) { return false; } actor = (char *)malloc((size_t)needed + 1u); if (actor == NULL) { return false; } snprintf(actor, (size_t)needed + 1u, "uid:%u gid:%u pid:%u", (unsigned int)cred.uid, (unsigned int)cred.gid, (unsigned int)cred.pid); *out_actor = amduat_octets((uint8_t *)actor, (size_t)needed); *out_has_actor = true; *out_has_uid = true; *out_uid = cred.uid; amduat_log(AMDUAT_LOG_DEBUG, "request actor=%s", actor); return true; } } #endif actor = strdup("unknown"); if (actor == NULL) { return false; } *out_actor = amduat_octets((uint8_t *)actor, strlen(actor)); *out_has_actor = true; *out_has_uid = false; *out_uid = 0; amduat_log(AMDUAT_LOG_DEBUG, "request actor=unknown"); return true; } static bool amduatd_http_parse_request(int fd, amduatd_http_req_t *out_req) { char line[2048]; size_t line_len = 0; char *sp1 = NULL; char *sp2 = NULL; if (out_req == NULL) { return false; } amduatd_http_req_init(out_req); if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) { return false; } sp1 = strchr(line, ' '); if (sp1 == NULL) { return false; } *sp1++ = '\0'; sp2 = strchr(sp1, ' '); if (sp2 == NULL) { return false; } *sp2++ = '\0'; if (strlen(line) >= sizeof(out_req->method)) { return false; } strncpy(out_req->method, line, sizeof(out_req->method) - 1); if (strlen(sp1) >= sizeof(out_req->path)) { return false; } strncpy(out_req->path, sp1, sizeof(out_req->path) - 1); for (;;) { if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) { return false; } if (line_len == 0) { break; } if (strncasecmp(line, "Content-Length:", 15) == 0) { const char *v = line + 15; while (*v == ' ' || *v == '\t') { v++; } out_req->content_length = (size_t)strtoull(v, NULL, 10); } else if (strncasecmp(line, "Content-Type:", 13) == 0) { const char *v = line + 13; while (*v == ' ' || *v == '\t') { v++; } strncpy(out_req->content_type, v, sizeof(out_req->content_type) - 1); } else if (strncasecmp(line, "Accept:", 7) == 0) { const char *v = line + 7; while (*v == ' ' || *v == '\t') { v++; } strncpy(out_req->accept, v, sizeof(out_req->accept) - 1); } else if (strncasecmp(line, "X-Amduat-Type-Tag:", 18) == 0) { const char *v = line + 18; while (*v == ' ' || *v == '\t') { v++; } strncpy(out_req->x_type_tag, v, sizeof(out_req->x_type_tag) - 1); } else if (strncasecmp(line, "X-Amduat-Capability:", 20) == 0) { const char *v = line + 20; while (*v == ' ' || *v == '\t') { v++; } strncpy(out_req->x_capability, v, sizeof(out_req->x_capability) - 1); } } return true; } bool amduatd_http_send_status(int fd, int code, const char *reason, const char *content_type, const uint8_t *body, size_t body_len, bool head_only) { char hdr[512]; int n = snprintf(hdr, sizeof(hdr), "HTTP/1.1 %d %s\r\n" "Content-Length: %zu\r\n" "Content-Type: %s\r\n" "Connection: close\r\n" "\r\n", code, reason != NULL ? reason : "", body_len, content_type != NULL ? content_type : "text/plain"); if (n <= 0 || (size_t)n >= sizeof(hdr)) { return false; } if (!amduatd_write_all(fd, (const uint8_t *)hdr, (size_t)n)) { return false; } if (!head_only && body != NULL && body_len != 0) { return amduatd_write_all(fd, body, body_len); } return true; } bool amduatd_http_send_text(int fd, int code, const char *reason, const char *text, bool head_only) { const uint8_t *body = (const uint8_t *)(text != NULL ? text : ""); size_t len = text != NULL ? strlen(text) : 0; return amduatd_http_send_status( fd, code, reason, "text/plain; charset=utf-8", body, len, head_only); } static bool amduatd_http_send_json(int fd, int code, const char *reason, const char *json, bool head_only) { const uint8_t *body = (const uint8_t *)(json != NULL ? json : "{}"); size_t len = json != NULL ? strlen(json) : 2; return amduatd_http_send_status( fd, code, reason, "application/json", body, len, head_only); } static bool amduatd_http_req_wants_html(const amduatd_http_req_t *req) { if (req == NULL) { return false; } if (req->accept[0] == '\0') { return false; } return strstr(req->accept, "text/html") != NULL; } static bool amduatd_http_send_not_found(int fd, const amduatd_http_req_t *req) { if (amduatd_http_req_wants_html(req)) { const char *path = (req != NULL && req->path[0] != '\0') ? req->path : "/"; const char *method = (req != NULL && req->method[0] != '\0') ? req->method : "GET"; char html[4096]; int n = snprintf( html, sizeof(html), "\n" "\n" "\n" " \n" " \n" " amduatd — Not Found\n" " \n" "\n" "\n" "
\n" "
\n" "

404 — Not Found

\n" "

amduatd didn’t recognize %s %s.

\n" "

Try one of these:

\n" " \n" "

Artifact bytes: /v1/artifacts/<ref>

\n" "
\n" "
\n" "\n" "\n", method, path); if (n <= 0 || (size_t)n >= sizeof(html)) { return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false); } return amduatd_http_send_status(fd, 404, "Not Found", "text/html; charset=utf-8", (const uint8_t *)html, (size_t)n, false); } return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false); } static const char *amduatd_query_param(const char *path, const char *key, char *out_value, size_t out_cap) { const char *q = strchr(path, '?'); size_t key_len = 0; if (out_value != NULL && out_cap != 0) { out_value[0] = '\0'; } if (q == NULL || key == NULL || out_value == NULL || out_cap == 0) { return NULL; } q++; key_len = strlen(key); while (*q != '\0') { const char *k = q; const char *eq = strchr(k, '='); const char *amp = strchr(k, '&'); size_t klen = 0; const char *v = NULL; size_t vlen = 0; if (amp == NULL) { amp = q + strlen(q); } if (eq == NULL || eq > amp) { q = (*amp == '&') ? (amp + 1) : amp; continue; } klen = (size_t)(eq - k); if (klen == key_len && strncmp(k, key, key_len) == 0) { v = eq + 1; vlen = (size_t)(amp - v); if (vlen >= out_cap) { vlen = out_cap - 1; } memcpy(out_value, v, vlen); out_value[vlen] = '\0'; return out_value; } q = (*amp == '&') ? (amp + 1) : amp; } return NULL; } static bool amduatd_bytes_contains(const uint8_t *data, size_t len, const char *needle) { size_t i; size_t nlen; if (data == NULL || needle == NULL) { return false; } nlen = strlen(needle); if (nlen == 0 || nlen > len) { return false; } for (i = 0; i + nlen <= len; ++i) { if (memcmp(data + i, needle, nlen) == 0) { return true; } } return false; } static void amduatd_strbuf_free(amduatd_strbuf_t *b) { if (b == NULL) { return; } free(b->data); b->data = NULL; b->len = 0; b->cap = 0; } static bool amduatd_strbuf_reserve(amduatd_strbuf_t *b, size_t extra) { size_t need; size_t next_cap; char *next; if (b == NULL) { return false; } if (extra > (SIZE_MAX - b->len)) { return false; } need = b->len + extra; if (need <= b->cap) { return true; } next_cap = b->cap != 0 ? b->cap : 256u; while (next_cap < need) { if (next_cap > (SIZE_MAX / 2u)) { next_cap = need; break; } next_cap *= 2u; } next = (char *)realloc(b->data, next_cap); if (next == NULL) { return false; } b->data = next; b->cap = next_cap; return true; } static bool amduatd_strbuf_append(amduatd_strbuf_t *b, const char *s, size_t n) { if (b == NULL) { return false; } if (n == 0) { return true; } if (s == NULL) { return false; } if (!amduatd_strbuf_reserve(b, n + 1u)) { return false; } memcpy(b->data + b->len, s, n); b->len += n; b->data[b->len] = '\0'; return true; } static bool amduatd_strbuf_append_cstr(amduatd_strbuf_t *b, const char *s) { return amduatd_strbuf_append(b, s != NULL ? s : "", s != NULL ? strlen(s) : 0u); } static bool amduatd_strbuf_append_char(amduatd_strbuf_t *b, char c) { return amduatd_strbuf_append(b, &c, 1u); } static const char *amduatd_json_skip_ws(const char *p, const char *end) { while (p < end) { char c = *p; if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { break; } p++; } return p; } static bool amduatd_json_expect(const char **p, const char *end, char expected) { const char *cur; if (p == NULL || *p == NULL) { return false; } cur = amduatd_json_skip_ws(*p, end); if (cur >= end || *cur != expected) { return false; } *p = cur + 1; return true; } static bool amduatd_json_parse_string_noesc(const char **p, const char *end, const char **out_start, size_t *out_len) { const char *cur; const char *s; if (out_start != NULL) { *out_start = NULL; } if (out_len != NULL) { *out_len = 0; } if (p == NULL || *p == NULL) { return false; } cur = amduatd_json_skip_ws(*p, end); if (cur >= end || *cur != '"') { return false; } cur++; s = cur; while (cur < end) { unsigned char c = (unsigned char)*cur; if (c == '"') { if (out_start != NULL) { *out_start = s; } if (out_len != NULL) { *out_len = (size_t)(cur - s); } *p = cur + 1; return true; } if (c == '\\' || c < 0x20u) { return false; } cur++; } return false; } static bool amduatd_json_parse_u32(const char **p, const char *end, uint32_t *out) { const char *cur; const char *start; unsigned long long v; char *tmp = NULL; char *endp = NULL; size_t n; if (out != NULL) { *out = 0; } if (p == NULL || *p == NULL || out == NULL) { return false; } cur = amduatd_json_skip_ws(*p, end); start = cur; if (cur >= end) { return false; } if (*cur < '0' || *cur > '9') { return false; } cur++; while (cur < end && *cur >= '0' && *cur <= '9') { cur++; } n = (size_t)(cur - start); tmp = (char *)malloc(n + 1u); if (tmp == NULL) { return false; } memcpy(tmp, start, n); tmp[n] = '\0'; errno = 0; v = strtoull(tmp, &endp, 10); free(tmp); if (errno != 0 || endp == NULL || *endp != '\0' || v > 0xffffffffULL) { return false; } *out = (uint32_t)v; *p = cur; return true; } static bool amduatd_json_parse_u64(const char **p, const char *end, uint64_t *out) { const char *cur; const char *start; unsigned long long v; char *tmp = NULL; char *endp = NULL; size_t n; if (out != NULL) { *out = 0; } if (p == NULL || *p == NULL || out == NULL) { return false; } cur = amduatd_json_skip_ws(*p, end); start = cur; if (cur >= end) { return false; } if (*cur < '0' || *cur > '9') { return false; } cur++; while (cur < end && *cur >= '0' && *cur <= '9') { cur++; } n = (size_t)(cur - start); tmp = (char *)malloc(n + 1u); if (tmp == NULL) { return false; } memcpy(tmp, start, n); tmp[n] = '\0'; errno = 0; v = strtoull(tmp, &endp, 10); free(tmp); if (errno != 0 || endp == NULL || *endp != '\0') { return false; } *out = (uint64_t)v; *p = cur; return true; } static bool amduatd_json_parse_u32_loose(const char **p, const char *end, uint32_t *out) { const char *cur; const char *sv = NULL; size_t sv_len = 0; uint64_t v = 0; size_t i; if (out != NULL) { *out = 0; } if (p == NULL || *p == NULL || out == NULL) { return false; } cur = amduatd_json_skip_ws(*p, end); if (cur >= end) { return false; } if (*cur == '"') { if (!amduatd_json_parse_string_noesc(p, end, &sv, &sv_len)) { return false; } if (sv_len == 0) { return false; } for (i = 0; i < sv_len; ++i) { unsigned char c = (unsigned char)sv[i]; if (c < '0' || c > '9') { return false; } v = (v * 10u) + (uint64_t)(c - '0'); if (v > 0xffffffffULL) { return false; } } *out = (uint32_t)v; return true; } if (*cur < '0' || *cur > '9') { return false; } for (;;) { unsigned char c = (unsigned char)*cur; if (c < '0' || c > '9') { break; } v = (v * 10u) + (uint64_t)(c - '0'); if (v > 0xffffffffULL) { return false; } cur++; if (cur >= end) { break; } } *out = (uint32_t)v; *p = cur; return true; } static void amduatd_json_peek_token(const char *p, const char *end, char *out, size_t cap) { const char *cur; size_t n = 0; if (out != NULL && cap != 0) { out[0] = '\0'; } if (out == NULL || cap == 0) { return; } cur = amduatd_json_skip_ws(p, end); while (cur < end && n + 1 < cap) { unsigned char c = (unsigned char)*cur; if (c == ',' || c == '}' || c == ']' || c == '\n' || c == '\r') { break; } if (c < 0x20u || c > 0x7eu) { out[n++] = '?'; } else { out[n++] = (char)c; } cur++; if (n >= 24u) { break; } } out[n] = '\0'; } static bool amduatd_json_skip_string(const char **p, const char *end) { const char *cur; if (p == NULL || *p == NULL) { return false; } cur = amduatd_json_skip_ws(*p, end); if (cur >= end || *cur != '"') { return false; } cur++; while (cur < end) { unsigned char c = (unsigned char)*cur++; if (c == '"') { *p = cur; return true; } if (c == '\\') { if (cur >= end) { return false; } cur++; } else if (c < 0x20u) { return false; } } return false; } static bool amduatd_json_skip_value(const char **p, const char *end, int depth); static bool amduatd_json_skip_array(const char **p, const char *end, int depth) { const char *cur; if (!amduatd_json_expect(p, end, '[')) { return false; } cur = amduatd_json_skip_ws(*p, end); if (cur < end && *cur == ']') { *p = cur + 1; return true; } for (;;) { if (!amduatd_json_skip_value(p, end, depth + 1)) { return false; } cur = amduatd_json_skip_ws(*p, end); if (cur >= end) { return false; } if (*cur == ',') { *p = cur + 1; continue; } if (*cur == ']') { *p = cur + 1; return true; } return false; } } static bool amduatd_json_skip_object(const char **p, const char *end, int depth) { const char *cur; if (!amduatd_json_expect(p, end, '{')) { return false; } cur = amduatd_json_skip_ws(*p, end); if (cur < end && *cur == '}') { *p = cur + 1; return true; } for (;;) { if (!amduatd_json_skip_string(p, end)) { return false; } if (!amduatd_json_expect(p, end, ':')) { return false; } if (!amduatd_json_skip_value(p, end, depth + 1)) { return false; } cur = amduatd_json_skip_ws(*p, end); if (cur >= end) { return false; } if (*cur == ',') { *p = cur + 1; continue; } if (*cur == '}') { *p = cur + 1; return true; } return false; } } static bool amduatd_json_skip_value(const char **p, const char *end, int depth) { const char *cur; if (p == NULL || *p == NULL) { return false; } if (depth > 32) { return false; } cur = amduatd_json_skip_ws(*p, end); if (cur >= end) { return false; } if (*cur == '"') { return amduatd_json_skip_string(p, end); } if (*cur == '{') { return amduatd_json_skip_object(p, end, depth); } if (*cur == '[') { return amduatd_json_skip_array(p, end, depth); } if (*cur == 't' && (end - cur) >= 4 && memcmp(cur, "true", 4) == 0) { *p = cur + 4; return true; } if (*cur == 'f' && (end - cur) >= 5 && memcmp(cur, "false", 5) == 0) { *p = cur + 5; return true; } if (*cur == 'n' && (end - cur) >= 4 && memcmp(cur, "null", 4) == 0) { *p = cur + 4; return true; } if (*cur == '-' || (*cur >= '0' && *cur <= '9')) { cur++; while (cur < end) { char c = *cur; if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') { cur++; continue; } break; } *p = cur; return true; } return false; } static bool amduatd_copy_json_str(const char *s, size_t len, char **out) { char *buf; if (out == NULL) { return false; } *out = NULL; if (len > (SIZE_MAX - 1u)) { return false; } buf = (char *)malloc(len + 1u); if (buf == NULL) { return false; } if (len != 0) { memcpy(buf, s, len); } buf[len] = '\0'; *out = buf; return true; } static bool amduatd_decode_ref_hex_str(const char *s, size_t len, amduat_reference_t *out_ref) { char *tmp = NULL; bool ok; if (out_ref == NULL) { return false; } memset(out_ref, 0, sizeof(*out_ref)); if (!amduatd_copy_json_str(s, len, &tmp)) { return false; } ok = amduat_asl_ref_decode_hex(tmp, out_ref); free(tmp); return ok; } typedef enum { AMDUATD_REF_OK = 0, AMDUATD_REF_ERR_INVALID = 1, AMDUATD_REF_ERR_NOT_FOUND = 2, AMDUATD_REF_ERR_INTERNAL = 3 } amduatd_ref_status_t; static amduatd_ref_status_t amduatd_decode_ref_or_name_latest( amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const char *s, size_t len, amduat_reference_t *out_ref) { amduat_reference_t concept_ref; char *tmp = NULL; char *scoped_name = NULL; if (out_ref != NULL) { memset(out_ref, 0, sizeof(*out_ref)); } if (store == NULL || cfg == NULL || concepts == NULL || s == NULL || out_ref == NULL) { return AMDUATD_REF_ERR_INTERNAL; } if (amduatd_decode_ref_hex_str(s, len, out_ref)) { return AMDUATD_REF_OK; } if (!amduatd_copy_json_str(s, len, &tmp)) { return AMDUATD_REF_ERR_INTERNAL; } if (!amduatd_scope_name_cstr(dcfg, tmp, &scoped_name)) { free(tmp); return AMDUATD_REF_ERR_INVALID; } amduatd_log_scoped_name(dcfg, tmp, scoped_name); memset(&concept_ref, 0, sizeof(concept_ref)); if (!amduatd_concepts_lookup_alias(store, cfg, concepts, scoped_name, &concept_ref)) { free(scoped_name); free(tmp); return AMDUATD_REF_ERR_NOT_FOUND; } free(scoped_name); free(tmp); if (!amduatd_concepts_resolve_latest(store, concepts, concept_ref, out_ref)) { amduat_reference_free(&concept_ref); return AMDUATD_REF_ERR_NOT_FOUND; } amduat_reference_free(&concept_ref); return AMDUATD_REF_OK; } static bool amduatd_send_json_error(int fd, int code, const char *reason, const char *msg) { amduatd_strbuf_t b; memset(&b, 0, sizeof(b)); if (!amduatd_strbuf_append_cstr(&b, "{\"error\":\"") || !amduatd_strbuf_append_cstr(&b, msg != NULL ? msg : "error") || !amduatd_strbuf_append_cstr(&b, "\"}\n")) { amduatd_strbuf_free(&b); return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n", false); } { bool ok = amduatd_http_send_json(fd, code, reason, b.data, false); amduatd_strbuf_free(&b); return ok; } } static void amduatd_path_without_query(const char *path, char *out, size_t cap) { const char *q = NULL; size_t n = 0; if (out == NULL || cap == 0) { return; } out[0] = '\0'; if (path == NULL) { return; } q = strchr(path, '?'); n = q != NULL ? (size_t)(q - path) : strlen(path); if (n >= cap) { n = cap - 1; } memcpy(out, path, n); out[n] = '\0'; } static bool amduatd_parse_type_tag_hex(const char *text, bool *out_has_type_tag, amduat_type_tag_t *out_type_tag) { char *end = NULL; unsigned long v = 0; if (out_has_type_tag != NULL) { *out_has_type_tag = false; } if (text == NULL || text[0] == '\0' || out_type_tag == NULL || out_has_type_tag == NULL) { return true; } errno = 0; v = strtoul(text, &end, 0); if (errno != 0 || end == text || *end != '\0' || v > 0xffffffffUL) { return false; } *out_has_type_tag = true; out_type_tag->tag_id = (uint32_t)v; return true; } static bool amduatd_handle_meta(int fd, const amduat_asl_store_fs_config_t *cfg, amduat_reference_t api_contract_ref, bool head_only) { char json[1024]; char *contract_hex = NULL; int n; if (api_contract_ref.hash_id != 0 && api_contract_ref.digest.data != NULL && api_contract_ref.digest.len != 0) { (void)amduat_asl_ref_encode_hex(api_contract_ref, &contract_hex); } if (contract_hex != NULL) { n = snprintf(json, sizeof(json), "{" "\"store_id\":\"%s\"," "\"encoding_profile_id\":\"0x%04x\"," "\"hash_id\":\"0x%04x\"," "\"api_contract_ref\":\"%s\"" "}\n", cfg != NULL ? cfg->store_id : "", cfg != NULL ? (unsigned int)cfg->config.encoding_profile_id : 0u, cfg != NULL ? (unsigned int)cfg->config.hash_id : 0u, contract_hex); } else { n = snprintf(json, sizeof(json), "{" "\"store_id\":\"%s\"," "\"encoding_profile_id\":\"0x%04x\"," "\"hash_id\":\"0x%04x\"," "\"api_contract_ref\":null" "}\n", cfg != NULL ? cfg->store_id : "", cfg != NULL ? (unsigned int)cfg->config.encoding_profile_id : 0u, cfg != NULL ? (unsigned int)cfg->config.hash_id : 0u); } free(contract_hex); if (n <= 0 || (size_t)n >= sizeof(json)) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n", head_only); } return amduatd_http_send_json(fd, 200, "OK", json, head_only); } static bool amduatd_handle_get_contract(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req, amduat_reference_t api_contract_ref) { char *hex = NULL; char format[32]; if (api_contract_ref.hash_id == 0 || api_contract_ref.digest.data == NULL || api_contract_ref.digest.len == 0) { return amduatd_http_send_not_found(fd, req); } memset(format, 0, sizeof(format)); if (req != NULL && amduatd_query_param(req->path, "format", format, sizeof(format)) != NULL && strcmp(format, "ref") == 0) { char json[2048]; int n; if (!amduat_asl_ref_encode_hex(api_contract_ref, &hex)) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "encode error\n", false); } n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", hex); free(hex); if (n <= 0 || (size_t)n >= sizeof(json)) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n", false); } return amduatd_http_send_json(fd, 200, "OK", json, false); } { amduat_artifact_t artifact; amduat_asl_store_error_t err; memset(&artifact, 0, sizeof(artifact)); err = amduat_asl_store_get(store, api_contract_ref, &artifact); if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) { return amduatd_http_send_not_found(fd, req); } if (err != AMDUAT_ASL_STORE_OK) { amduat_asl_artifact_free(&artifact); return amduatd_http_send_text(fd, 500, "Internal Server Error", "store error\n", false); } if (artifact.bytes.len != 0 && artifact.bytes.data == NULL) { amduat_asl_artifact_free(&artifact); return amduatd_http_send_text(fd, 500, "Internal Server Error", "store error\n", false); } if (!amduat_asl_ref_encode_hex(api_contract_ref, &hex)) { amduat_asl_artifact_free(&artifact); return amduatd_http_send_text(fd, 500, "Internal Server Error", "encode error\n", false); } { char hdr[600]; int n = snprintf(hdr, sizeof(hdr), "HTTP/1.1 200 OK\r\n" "Content-Length: %zu\r\n" "Content-Type: application/json\r\n" "X-Amduat-Contract-Ref: %s\r\n" "Connection: close\r\n" "\r\n", artifact.bytes.len, hex); free(hex); if (n <= 0 || (size_t)n >= sizeof(hdr)) { amduat_asl_artifact_free(&artifact); return false; } if (!amduatd_write_all(fd, (const uint8_t *)hdr, (size_t)n)) { amduat_asl_artifact_free(&artifact); return false; } if (artifact.bytes.len != 0) { bool ok = amduatd_write_all(fd, artifact.bytes.data, artifact.bytes.len); amduat_asl_artifact_free(&artifact); return ok; } amduat_asl_artifact_free(&artifact); return true; } } } static bool amduatd_seed_api_contract(amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, amduat_reference_t *out_ref) { amduat_artifact_t artifact; amduat_asl_store_error_t err; if (out_ref != NULL) { memset(out_ref, 0, sizeof(*out_ref)); } if (store == NULL || cfg == NULL || out_ref == NULL) { return false; } artifact = amduat_artifact(amduat_octets(k_amduatd_contract_v1_json, strlen(k_amduatd_contract_v1_json))); (void)amduat_asl_ref_derive(artifact, cfg->config.encoding_profile_id, cfg->config.hash_id, out_ref, NULL); err = amduat_asl_store_put(store, artifact, out_ref); if (err != AMDUAT_ASL_STORE_OK) { return false; } return true; } typedef struct { char *key_hex; char *type_hex; char *key_text; char *value_hex; } amduatd_seed_entry_t; static int amduatd_seed_entry_cmp(const void *a, const void *b) { const amduatd_seed_entry_t *x = (const amduatd_seed_entry_t *)a; const amduatd_seed_entry_t *y = (const amduatd_seed_entry_t *)b; if (x == NULL || y == NULL) { return 0; } return strcmp(x->key_hex != NULL ? x->key_hex : "", y->key_hex != NULL ? y->key_hex : ""); } static bool amduatd_seed_concept_if_missing( amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const char *name, amduat_reference_t *out_concept_ref) { amduat_reference_t name_ref; amduat_reference_t concept_ref; amduat_reference_t edge_ref; char *scoped_name = NULL; bool ok = false; if (out_concept_ref != NULL) { *out_concept_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); } if (store == NULL || cfg == NULL || concepts == NULL || name == NULL || out_concept_ref == NULL) { return false; } if (!amduatd_scope_name_cstr(dcfg, name, &scoped_name)) { return false; } if (amduatd_concepts_lookup_alias(store, cfg, concepts, scoped_name, out_concept_ref)) { ok = true; goto seed_cleanup; } memset(&name_ref, 0, sizeof(name_ref)); memset(&concept_ref, 0, sizeof(concept_ref)); memset(&edge_ref, 0, sizeof(edge_ref)); if (!amduatd_concepts_put_name_artifact(store, scoped_name, &name_ref)) { goto seed_cleanup; } if (!amduatd_concepts_put_concept_id(store, cfg, &concept_ref)) { goto seed_cleanup; } if (!amduatd_concepts_put_edge(store, concepts, name_ref, concept_ref, concepts->rel_aliases_ref, amduat_octets(NULL, 0u), &edge_ref)) { goto seed_cleanup; } *out_concept_ref = concept_ref; ok = true; goto seed_cleanup; seed_cleanup: if (!ok) { amduat_reference_free(&concept_ref); } amduat_reference_free(&name_ref); amduat_reference_free(&edge_ref); free(scoped_name); return ok; } static bool amduatd_seed_store_artifact(amduat_asl_store_t *store, amduat_artifact_t artifact, amduat_reference_t *out_ref) { if (out_ref != NULL) { *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); } if (store == NULL || out_ref == NULL) { return false; } if (amduat_asl_store_put(store, artifact, out_ref) != AMDUAT_ASL_STORE_OK) { return false; } return true; } static bool amduatd_seed_pel_identity_program( amduat_asl_store_t *store, amduat_reference_t *out_program_ref) { amduat_pel_program_t program; amduat_pel_node_t node; amduat_pel_dag_input_t inputs[1]; amduat_pel_root_ref_t root; amduat_octets_t program_bytes; amduat_artifact_t artifact; if (out_program_ref != NULL) { *out_program_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); } if (store == NULL || out_program_ref == NULL) { return false; } memset(&program, 0, sizeof(program)); memset(&node, 0, sizeof(node)); memset(inputs, 0, sizeof(inputs)); memset(&root, 0, sizeof(root)); program_bytes = amduat_octets(NULL, 0u); inputs[0].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; inputs[0].value.external.input_index = 0; node.id = 1; node.op.name = amduat_octets("pel.bytes.concat", strlen("pel.bytes.concat")); node.op.version = 1; node.inputs = inputs; node.inputs_len = 1; node.params = amduat_octets(NULL, 0u); root.node_id = 1; root.output_index = 0; program.nodes = &node; program.nodes_len = 1; program.roots = &root; program.roots_len = 1; if (!amduat_enc_pel_program_dag_encode_v1(&program, &program_bytes)) { return false; } artifact = amduat_artifact_with_type(program_bytes, amduat_type_tag(0x00000101u)); if (!amduatd_seed_store_artifact(store, artifact, out_program_ref)) { free((void *)program_bytes.data); return false; } free((void *)program_bytes.data); return true; } static bool amduatd_seed_materializes_if_missing( amduat_asl_store_t *store, amduatd_concepts_t *concepts, amduat_reference_t concept_ref, amduat_reference_t target_ref) { amduat_reference_t latest; amduat_reference_t edge_ref; memset(&latest, 0, sizeof(latest)); if (amduatd_concepts_resolve_latest(store, concepts, concept_ref, &latest)) { amduat_reference_free(&latest); return true; } if (!amduatd_concepts_put_edge(store, concepts, concept_ref, target_ref, concepts->rel_materializes_ref, amduat_octets(NULL, 0u), &edge_ref)) { return false; } amduat_reference_free(&edge_ref); return true; } static bool amduatd_seed_ms_ui_state(amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg) { amduat_reference_t type_string_ref; amduat_reference_t type_ref_ref; amduat_reference_t key_title_ref; amduat_reference_t key_status_ref; amduat_reference_t key_latest_ref; amduat_reference_t schema_concept_ref; amduat_reference_t registry_concept_ref; amduat_reference_t identity_program_ref; amduat_reference_t schema_input_ref; amduat_reference_t registry_input_ref; amduat_reference_t schema_output_ref; amduat_reference_t registry_output_ref; amduat_reference_t seed_manifest_ref; amduat_reference_t seed_environment_ref; amduat_reference_t seed_executor_ref; amduat_reference_t receipt_ref; amduat_pel_run_result_t run_result; amduatd_seed_entry_t fields[3]; amduatd_seed_entry_t entries[3]; amduatd_strbuf_t b; amduat_artifact_t artifact; char *seed_latest_hex = NULL; size_t i; bool ok = false; memset(&type_string_ref, 0, sizeof(type_string_ref)); memset(&type_ref_ref, 0, sizeof(type_ref_ref)); memset(&key_title_ref, 0, sizeof(key_title_ref)); memset(&key_status_ref, 0, sizeof(key_status_ref)); memset(&key_latest_ref, 0, sizeof(key_latest_ref)); memset(&schema_concept_ref, 0, sizeof(schema_concept_ref)); memset(®istry_concept_ref, 0, sizeof(registry_concept_ref)); memset(&identity_program_ref, 0, sizeof(identity_program_ref)); memset(&schema_input_ref, 0, sizeof(schema_input_ref)); memset(®istry_input_ref, 0, sizeof(registry_input_ref)); memset(&schema_output_ref, 0, sizeof(schema_output_ref)); memset(®istry_output_ref, 0, sizeof(registry_output_ref)); memset(&seed_manifest_ref, 0, sizeof(seed_manifest_ref)); memset(&seed_environment_ref, 0, sizeof(seed_environment_ref)); memset(&seed_executor_ref, 0, sizeof(seed_executor_ref)); memset(&receipt_ref, 0, sizeof(receipt_ref)); memset(&run_result, 0, sizeof(run_result)); memset(fields, 0, sizeof(fields)); memset(entries, 0, sizeof(entries)); memset(&b, 0, sizeof(b)); memset(&artifact, 0, sizeof(artifact)); if (!amduatd_seed_concept_if_missing(store, cfg, concepts, dcfg, "ms.type.string", &type_string_ref) || !amduatd_seed_concept_if_missing(store, cfg, concepts, dcfg, "ms.type.ref", &type_ref_ref) || !amduatd_seed_concept_if_missing(store, cfg, concepts, dcfg, "ms.ui.field.title", &key_title_ref) || !amduatd_seed_concept_if_missing(store, cfg, concepts, dcfg, "ms.ui.field.status", &key_status_ref) || !amduatd_seed_concept_if_missing(store, cfg, concepts, dcfg, "ms.ui.field.latest_ref", &key_latest_ref) || !amduatd_seed_concept_if_missing(store, cfg, concepts, dcfg, "ms.ui.state.schema.v1", &schema_concept_ref) || !amduatd_seed_concept_if_missing(store, cfg, concepts, dcfg, "ms.ui.state.registry.v1", ®istry_concept_ref)) { goto seed_cleanup; } if (!amduatd_seed_pel_identity_program(store, &identity_program_ref)) { goto seed_cleanup; } { amduat_reference_t latest_schema; amduat_reference_t latest_registry; bool have_schema = false; bool have_registry = false; bool schema_ok = false; bool registry_ok = false; memset(&latest_schema, 0, sizeof(latest_schema)); memset(&latest_registry, 0, sizeof(latest_registry)); have_schema = amduatd_concepts_resolve_latest(store, concepts, schema_concept_ref, &latest_schema); have_registry = amduatd_concepts_resolve_latest(store, concepts, registry_concept_ref, &latest_registry); if (have_schema && have_registry) { amduat_artifact_t schema_artifact; amduat_artifact_t registry_artifact; amduat_asl_store_error_t err; memset(&schema_artifact, 0, sizeof(schema_artifact)); memset(®istry_artifact, 0, sizeof(registry_artifact)); err = amduat_asl_store_get(store, latest_schema, &schema_artifact); if (err == AMDUAT_ASL_STORE_OK && schema_artifact.bytes.len != 0 && schema_artifact.bytes.data != NULL && amduatd_bytes_contains(schema_artifact.bytes.data, schema_artifact.bytes.len, "\"key_text\"")) { schema_ok = true; } err = amduat_asl_store_get(store, latest_registry, ®istry_artifact); if (err == AMDUAT_ASL_STORE_OK && registry_artifact.bytes.len != 0 && registry_artifact.bytes.data != NULL && amduatd_bytes_contains(registry_artifact.bytes.data, registry_artifact.bytes.len, "\"value_text\"")) { registry_ok = true; } amduat_asl_artifact_free(&schema_artifact); amduat_asl_artifact_free(®istry_artifact); } amduat_reference_free(&latest_schema); amduat_reference_free(&latest_registry); if (have_schema && have_registry && schema_ok && registry_ok) { ok = true; goto seed_cleanup; } } { const char *payload = "{\"seed\":\"manifest\"}"; artifact = amduat_artifact(amduat_octets(payload, strlen(payload))); if (!amduatd_seed_store_artifact(store, artifact, &seed_manifest_ref)) { goto seed_cleanup; } } { const char *payload = "{\"seed\":\"environment\"}"; artifact = amduat_artifact(amduat_octets(payload, strlen(payload))); if (!amduatd_seed_store_artifact(store, artifact, &seed_environment_ref)) { goto seed_cleanup; } } { const char *payload = "{\"seed\":\"executor\"}"; artifact = amduat_artifact(amduat_octets(payload, strlen(payload))); if (!amduatd_seed_store_artifact(store, artifact, &seed_executor_ref)) { goto seed_cleanup; } } { amduat_reference_t hello_ref; amduat_reference_t title_ref; amduat_reference_t status_ref; char *hello_hex = NULL; const char *payload = "hello"; memset(&hello_ref, 0, sizeof(hello_ref)); memset(&title_ref, 0, sizeof(title_ref)); memset(&status_ref, 0, sizeof(status_ref)); artifact = amduat_artifact(amduat_octets(payload, strlen(payload))); if (!amduatd_seed_store_artifact(store, artifact, &hello_ref)) { goto seed_cleanup; } if (!amduat_asl_ref_encode_hex(hello_ref, &hello_hex)) { goto seed_cleanup; } seed_latest_hex = hello_hex; artifact = amduat_artifact(amduat_octets("Amduat UI", strlen("Amduat UI"))); if (!amduatd_seed_store_artifact(store, artifact, &title_ref)) { free(hello_hex); goto seed_cleanup; } artifact = amduat_artifact(amduat_octets("ready", strlen("ready"))); if (!amduatd_seed_store_artifact(store, artifact, &status_ref)) { free(hello_hex); goto seed_cleanup; } fields[0].key_hex = NULL; fields[1].key_hex = NULL; fields[2].key_hex = NULL; if (!amduat_asl_ref_encode_hex(key_title_ref, &fields[0].key_hex) || !amduat_asl_ref_encode_hex(key_status_ref, &fields[1].key_hex) || !amduat_asl_ref_encode_hex(key_latest_ref, &fields[2].key_hex)) { goto seed_cleanup; } fields[0].type_hex = strdup("ms.type.string"); fields[1].type_hex = strdup("ms.type.string"); fields[2].type_hex = strdup("ms.type.ref"); if (fields[0].type_hex == NULL || fields[1].type_hex == NULL || fields[2].type_hex == NULL) { goto seed_cleanup; } fields[0].key_text = strdup("title"); fields[1].key_text = strdup("status"); fields[2].key_text = strdup("latest_ref"); if (fields[0].key_text == NULL || fields[1].key_text == NULL || fields[2].key_text == NULL) { goto seed_cleanup; } entries[0].key_hex = fields[0].key_hex; entries[1].key_hex = fields[1].key_hex; entries[2].key_hex = fields[2].key_hex; entries[0].value_hex = strdup("Amduat UI"); entries[1].value_hex = strdup("ready"); entries[2].value_hex = hello_hex; if (entries[0].value_hex == NULL || entries[1].value_hex == NULL || entries[2].value_hex == NULL) { goto seed_cleanup; } hello_hex = NULL; seed_latest_hex = NULL; qsort(fields, 3, sizeof(fields[0]), amduatd_seed_entry_cmp); qsort(entries, 3, sizeof(entries[0]), amduatd_seed_entry_cmp); if (!amduatd_strbuf_append_cstr(&b, "{\"schema_version\":1,\"fields\":[")) { goto seed_cleanup; } for (i = 0; i < 3; ++i) { if (i != 0 && !amduatd_strbuf_append_char(&b, ',')) { goto seed_cleanup; } if (!amduatd_strbuf_append_cstr(&b, "{\"key_ref\":\"") || !amduatd_strbuf_append_cstr(&b, fields[i].key_hex) || !amduatd_strbuf_append_cstr(&b, "\",\"key_text\":\"") || !amduatd_strbuf_append_cstr(&b, fields[i].key_text) || !amduatd_strbuf_append_cstr(&b, "\",\"type_ref\":\"") || !amduatd_strbuf_append_cstr(&b, fields[i].type_hex) || !amduatd_strbuf_append_cstr(&b, "\"}")) { goto seed_cleanup; } } if (!amduatd_strbuf_append_cstr(&b, "]}")) { goto seed_cleanup; } artifact = amduat_artifact(amduat_octets(b.data, b.len)); if (!amduatd_seed_store_artifact(store, artifact, &schema_input_ref)) { goto seed_cleanup; } amduatd_strbuf_free(&b); memset(&b, 0, sizeof(b)); if (!amduatd_strbuf_append_cstr(&b, "{\"registry_version\":1,\"entries\":[")) { goto seed_cleanup; } for (i = 0; i < 3; ++i) { if (i != 0 && !amduatd_strbuf_append_char(&b, ',')) { goto seed_cleanup; } if (!amduatd_strbuf_append_cstr(&b, "{\"key_ref\":\"") || !amduatd_strbuf_append_cstr(&b, entries[i].key_hex) || !amduatd_strbuf_append_cstr(&b, i == 2 ? "\",\"value_ref\":\"" : "\",\"value_text\":\"") || !amduatd_strbuf_append_cstr(&b, entries[i].value_hex) || !amduatd_strbuf_append_cstr(&b, "\"}")) { goto seed_cleanup; } } if (!amduatd_strbuf_append_cstr(&b, "]}")) { goto seed_cleanup; } artifact = amduat_artifact(amduat_octets(b.data, b.len)); if (!amduatd_seed_store_artifact(store, artifact, ®istry_input_ref)) { goto seed_cleanup; } } { amduat_reference_t inputs[1]; amduat_reference_t scheme_ref = amduat_pel_program_dag_scheme_ref(); amduat_artifact_t receipt_artifact; amduat_reference_t edge_ref; amduat_octets_t evaluator_id = amduat_octets("amduatd-seed", strlen("amduatd-seed")); inputs[0] = schema_input_ref; if (!amduat_pel_surf_run_with_result(store, scheme_ref, identity_program_ref, inputs, 1, false, amduat_reference(0u, amduat_octets(NULL, 0u)), &run_result)) { goto seed_cleanup; } if (!run_result.has_result_value || run_result.output_refs_len != 1) { goto seed_cleanup; } if (!amduat_reference_clone(run_result.output_refs[0], &schema_output_ref)) { goto seed_cleanup; } if (!amduat_fer1_receipt_from_pel_result(&run_result.result_value, seed_manifest_ref, seed_environment_ref, evaluator_id, seed_executor_ref, false, amduat_reference(0u, amduat_octets(NULL, 0u)), amduat_octets(NULL, 0u), 0, 0, &receipt_artifact)) { goto seed_cleanup; } if (!amduatd_seed_store_artifact(store, receipt_artifact, &receipt_ref)) { amduat_asl_artifact_free(&receipt_artifact); goto seed_cleanup; } amduat_asl_artifact_free(&receipt_artifact); if (!amduatd_concepts_put_edge(store, concepts, schema_output_ref, receipt_ref, concepts->rel_has_provenance_ref, amduat_octets(NULL, 0u), &edge_ref)) { goto seed_cleanup; } amduat_reference_free(&edge_ref); amduat_reference_free(&receipt_ref); amduat_enc_pel1_result_free(&run_result.result_value); amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len); amduat_pel_surf_free_ref(&run_result.result_ref); memset(&run_result, 0, sizeof(run_result)); } { amduat_reference_t inputs[1]; amduat_reference_t scheme_ref = amduat_pel_program_dag_scheme_ref(); amduat_artifact_t receipt_artifact; amduat_reference_t edge_ref; amduat_octets_t evaluator_id = amduat_octets("amduatd-seed", strlen("amduatd-seed")); inputs[0] = registry_input_ref; if (!amduat_pel_surf_run_with_result(store, scheme_ref, identity_program_ref, inputs, 1, false, amduat_reference(0u, amduat_octets(NULL, 0u)), &run_result)) { goto seed_cleanup; } if (!run_result.has_result_value || run_result.output_refs_len != 1) { goto seed_cleanup; } if (!amduat_reference_clone(run_result.output_refs[0], ®istry_output_ref)) { goto seed_cleanup; } if (!amduat_fer1_receipt_from_pel_result(&run_result.result_value, seed_manifest_ref, seed_environment_ref, evaluator_id, seed_executor_ref, false, amduat_reference(0u, amduat_octets(NULL, 0u)), amduat_octets(NULL, 0u), 0, 0, &receipt_artifact)) { goto seed_cleanup; } if (!amduatd_seed_store_artifact(store, receipt_artifact, &receipt_ref)) { amduat_asl_artifact_free(&receipt_artifact); goto seed_cleanup; } amduat_asl_artifact_free(&receipt_artifact); if (!amduatd_concepts_put_edge(store, concepts, registry_output_ref, receipt_ref, concepts->rel_has_provenance_ref, amduat_octets(NULL, 0u), &edge_ref)) { goto seed_cleanup; } amduat_reference_free(&edge_ref); amduat_reference_free(&receipt_ref); amduat_enc_pel1_result_free(&run_result.result_value); amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len); amduat_pel_surf_free_ref(&run_result.result_ref); memset(&run_result, 0, sizeof(run_result)); } if (!amduatd_seed_materializes_if_missing(store, concepts, schema_concept_ref, schema_output_ref) || !amduatd_seed_materializes_if_missing(store, concepts, registry_concept_ref, registry_output_ref)) { goto seed_cleanup; } ok = true; seed_cleanup: if (run_result.has_result_value) { amduat_enc_pel1_result_free(&run_result.result_value); } if (run_result.output_refs != NULL) { amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len); } amduat_pel_surf_free_ref(&run_result.result_ref); amduat_reference_free(&type_string_ref); amduat_reference_free(&type_ref_ref); amduat_reference_free(&key_title_ref); amduat_reference_free(&key_status_ref); amduat_reference_free(&key_latest_ref); amduat_reference_free(&schema_concept_ref); amduat_reference_free(®istry_concept_ref); amduat_reference_free(&identity_program_ref); amduat_reference_free(&schema_input_ref); amduat_reference_free(®istry_input_ref); amduat_reference_free(&schema_output_ref); amduat_reference_free(®istry_output_ref); amduat_reference_free(&seed_manifest_ref); amduat_reference_free(&seed_environment_ref); amduat_reference_free(&seed_executor_ref); for (i = 0; i < 3; ++i) { free(fields[i].key_hex); free(fields[i].type_hex); free(fields[i].key_text); free(entries[i].value_hex); } amduatd_strbuf_free(&b); free(seed_latest_hex); return ok; } static bool amduatd_handle_get_artifact(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req, const char *path, bool head_only) { char no_query[1024]; char format[32]; const char *ref_text = NULL; amduat_reference_t ref; amduat_artifact_t artifact; amduat_asl_store_error_t err; bool want_artifact = false; bool want_info = false; memset(&ref, 0, sizeof(ref)); memset(&artifact, 0, sizeof(artifact)); amduatd_path_without_query(path, no_query, sizeof(no_query)); if (strncmp(no_query, "/v1/artifacts/", 14) != 0) { (void)head_only; return amduatd_http_send_not_found(fd, req); } ref_text = no_query + 14; if (ref_text[0] == '\0') { return amduatd_http_send_text(fd, 400, "Bad Request", "missing ref\n", head_only); } if (!amduat_asl_ref_decode_hex(ref_text, &ref)) { return amduatd_http_send_text(fd, 400, "Bad Request", "invalid ref\n", head_only); } memset(format, 0, sizeof(format)); if (amduatd_query_param(path, "format", format, sizeof(format)) != NULL) { if (strcmp(format, "artifact") == 0) { want_artifact = true; want_info = false; } else if (strcmp(format, "info") == 0) { want_artifact = false; want_info = true; } else if (strcmp(format, "raw") == 0 || format[0] == '\0') { want_artifact = false; want_info = false; } else { free((void *)ref.digest.data); return amduatd_http_send_text(fd, 400, "Bad Request", "invalid format\n", head_only); } } err = amduat_asl_store_get(store, ref, &artifact); free((void *)ref.digest.data); if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) { if (!head_only) { return amduatd_http_send_not_found(fd, req); } return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", true); } if (err != AMDUAT_ASL_STORE_OK) { amduat_asl_artifact_free(&artifact); return amduatd_http_send_text(fd, 500, "Internal Server Error", "store error\n", head_only); } if (!want_artifact) { if (want_info) { char json[256]; int n; if (artifact.has_type_tag) { n = snprintf(json, sizeof(json), "{" "\"len\":%zu," "\"has_type_tag\":true," "\"type_tag\":\"0x%08x\"" "}\n", artifact.bytes.len, (unsigned int)artifact.type_tag.tag_id); } else { n = snprintf(json, sizeof(json), "{" "\"len\":%zu," "\"has_type_tag\":false," "\"type_tag\":null" "}\n", artifact.bytes.len); } amduat_asl_artifact_free(&artifact); if (n <= 0 || (size_t)n >= sizeof(json)) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n", head_only); } return amduatd_http_send_json(fd, 200, "OK", json, head_only); } bool ok = amduatd_http_send_status( fd, 200, "OK", "application/octet-stream", artifact.bytes.data, artifact.bytes.len, head_only); amduat_asl_artifact_free(&artifact); return ok; } { amduat_octets_t out = amduat_octets(NULL, 0); bool ok = amduat_enc_asl1_core_encode_artifact_v1(artifact, &out); amduat_asl_artifact_free(&artifact); if (!ok) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "encode error\n", head_only); } ok = amduatd_http_send_status(fd, 200, "OK", "application/vnd.amduat.asl.artifact+v1", out.data, out.len, head_only); free((void *)out.data); return ok; } } static bool amduatd_handle_head_artifact(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req, const char *path) { return amduatd_handle_get_artifact(fd, store, req, path, true); } static bool amduatd_parse_u64_str(const char *s, uint64_t *out) { char *end = NULL; unsigned long long v; if (s == NULL || out == NULL || s[0] == '\0') { return false; } errno = 0; v = strtoull(s, &end, 10); if (errno != 0 || end == s || *end != '\0') { return false; } *out = (uint64_t)v; return true; } static bool amduatd_parse_u32_str(const char *s, uint32_t *out) { uint64_t tmp = 0; if (!amduatd_parse_u64_str(s, &tmp)) { return false; } if (tmp > UINT32_MAX) { return false; } *out = (uint32_t)tmp; return true; } static bool amduatd_handle_get_fed_records(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req) { char domain_buf[32]; char from_buf[32]; char limit_buf[32]; uint32_t domain_id = 0; uint64_t from_logseq = 0; uint64_t next_logseq = 0; uint64_t limit = 256; uint64_t emitted = 0; amduat_asl_index_state_t state; amduat_asl_log_record_t *records = NULL; size_t record_count = 0; size_t i; amduatd_strbuf_t b; bool first = true; if (store == NULL || req == NULL) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "internal error\n", false); } if (amduatd_query_param(req->path, "domain_id", domain_buf, sizeof(domain_buf)) == NULL || !amduatd_parse_u32_str(domain_buf, &domain_id)) { return amduatd_http_send_text(fd, 400, "Bad Request", "missing domain_id\n", false); } if (amduatd_query_param(req->path, "from_logseq", from_buf, sizeof(from_buf)) != NULL) { if (!amduatd_parse_u64_str(from_buf, &from_logseq)) { return amduatd_http_send_text(fd, 400, "Bad Request", "invalid from_logseq\n", false); } } if (amduatd_query_param(req->path, "limit", limit_buf, sizeof(limit_buf)) != NULL) { if (!amduatd_parse_u64_str(limit_buf, &limit) || limit == 0u || limit > 10000u) { return amduatd_http_send_text(fd, 400, "Bad Request", "invalid limit\n", false); } } if (!amduat_asl_index_current_state(store, &state)) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "store error\n", false); } if (amduat_asl_log_scan(store, &records, &record_count) != AMDUAT_ASL_STORE_OK) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "log scan error\n", false); } memset(&b, 0, sizeof(b)); if (!amduatd_strbuf_append_cstr(&b, "{")) { goto fed_records_oom; } { char header[256]; int n = snprintf(header, sizeof(header), "\"domain_id\":%u," "\"snapshot_id\":%llu," "\"log_prefix\":%llu," "\"records\":[", (unsigned int)domain_id, (unsigned long long)state.snapshot_id, (unsigned long long)state.log_position); if (n <= 0 || (size_t)n >= sizeof(header)) { goto fed_records_oom; } if (!amduatd_strbuf_append_cstr(&b, header)) { goto fed_records_oom; } } next_logseq = from_logseq; for (i = 0; i < record_count; ++i) { const amduat_asl_log_record_t *rec = &records[i]; amduat_reference_t ref; char *ref_hex = NULL; char entry[512]; int n; if (rec->logseq < from_logseq) { continue; } memset(&ref, 0, sizeof(ref)); if (rec->record_type == AMDUAT_ASL_LOG_RECORD_TOMBSTONE || rec->record_type == AMDUAT_ASL_LOG_RECORD_ARTIFACT_PUBLISH || rec->record_type == AMDUAT_ASL_LOG_RECORD_ARTIFACT_UNPUBLISH) { if (!amduat_asl_log_decode_artifact_ref(rec->payload, &ref)) { continue; } } else { continue; } if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { amduat_reference_free(&ref); goto fed_records_oom; } { uint32_t rec_type = AMDUAT_FED_REC_TOMBSTONE; amduat_artifact_t artifact; amduat_asl_store_error_t store_err; if (rec->record_type == AMDUAT_ASL_LOG_RECORD_ARTIFACT_PUBLISH) { rec_type = AMDUAT_FED_REC_ARTIFACT; memset(&artifact, 0, sizeof(artifact)); store_err = amduat_asl_store_get(store, ref, &artifact); if (store_err == AMDUAT_ASL_STORE_OK && artifact.has_type_tag) { if (artifact.type_tag.tag_id == AMDUAT_TYPE_TAG_TGK1_EDGE_V1) { rec_type = AMDUAT_FED_REC_TGK_EDGE; } else if (artifact.type_tag.tag_id == AMDUAT_TYPE_TAG_FER1_RECEIPT_1) { rec_type = AMDUAT_FED_REC_PER; } } if (store_err == AMDUAT_ASL_STORE_OK) { amduat_asl_artifact_free(&artifact); } } else if (rec->record_type == AMDUAT_ASL_LOG_RECORD_ARTIFACT_UNPUBLISH) { rec_type = AMDUAT_FED_REC_TOMBSTONE; } n = snprintf(entry, sizeof(entry), "%s{\"domain_id\":%u," "\"type\":%u," "\"ref\":\"%s\"," "\"logseq\":%llu," "\"snapshot_id\":%llu," "\"log_prefix\":%llu," "\"visibility\":1," "\"has_source\":false," "\"source_domain\":0}", first ? "" : ",", (unsigned int)domain_id, (unsigned int)rec_type, ref_hex, (unsigned long long)rec->logseq, (unsigned long long)state.snapshot_id, (unsigned long long)state.log_position); } free(ref_hex); amduat_reference_free(&ref); if (n <= 0 || (size_t)n >= sizeof(entry)) { goto fed_records_oom; } if (!amduatd_strbuf_append_cstr(&b, entry)) { goto fed_records_oom; } first = false; if (rec->logseq >= next_logseq) { next_logseq = rec->logseq + 1u; } emitted++; if (emitted >= limit) { break; } } { char tail[128]; int n = snprintf(tail, sizeof(tail), "],\"next_logseq\":%llu}\n", (unsigned long long)next_logseq); if (n <= 0 || (size_t)n >= sizeof(tail)) { goto fed_records_oom; } if (!amduatd_strbuf_append_cstr(&b, tail)) { goto fed_records_oom; } } amduat_enc_asl_log_free(records, record_count); { bool ok = amduatd_http_send_json(fd, 200, "OK", b.data, false); amduatd_strbuf_free(&b); return ok; } fed_records_oom: if (records != NULL) { amduat_enc_asl_log_free(records, record_count); } amduatd_strbuf_free(&b); return amduatd_http_send_text(fd, 500, "Internal Server Error", "oom\n", false); } static bool amduatd_handle_get_fed_artifact(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req, const char *path) { char no_query[1024]; const char *ref_text = NULL; amduat_reference_t ref; amduat_artifact_t artifact; amduat_asl_store_error_t err; if (store == NULL || req == NULL || path == NULL) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "internal error\n", false); } amduatd_path_without_query(path, no_query, sizeof(no_query)); if (strncmp(no_query, "/v1/fed/artifacts/", 18) != 0) { return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false); } ref_text = no_query + 18; memset(&ref, 0, sizeof(ref)); if (!amduat_asl_ref_decode_hex(ref_text, &ref)) { return amduatd_http_send_text(fd, 400, "Bad Request", "invalid ref\n", false); } memset(&artifact, 0, sizeof(artifact)); err = amduat_asl_store_get(store, ref, &artifact); amduat_reference_free(&ref); if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) { return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false); } if (err != AMDUAT_ASL_STORE_OK) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "store error\n", false); } { bool ok = amduatd_http_send_status(fd, 200, "OK", "application/octet-stream", artifact.bytes.data, artifact.bytes.len, false); amduat_asl_artifact_free(&artifact); return ok; } } static bool amduatd_handle_get_fed_status(int fd, const amduat_fed_coord_t *coord, const amduatd_http_req_t *req) { amduat_fed_coord_status_t status; char *ref_hex = NULL; char json[512]; int n; (void)req; amduat_fed_coord_get_status(coord, &status); if (status.registry_ref.hash_id != 0 && status.registry_ref.digest.data != NULL) { if (!amduat_asl_ref_encode_hex(status.registry_ref, &ref_hex)) { amduat_reference_free(&status.registry_ref); return amduatd_http_send_text(fd, 500, "Internal Server Error", "encode error\n", false); } } if (ref_hex != NULL) { n = snprintf(json, sizeof(json), "{\"status\":\"ok\",\"domain_id\":%u," "\"registry_ref\":\"%s\",\"last_tick_ms\":%llu}\n", (unsigned int)status.domain_id, ref_hex, (unsigned long long)status.last_tick_ms); } else { n = snprintf(json, sizeof(json), "{\"status\":\"ok\",\"domain_id\":%u," "\"registry_ref\":null,\"last_tick_ms\":%llu}\n", (unsigned int)status.domain_id, (unsigned long long)status.last_tick_ms); } free(ref_hex); amduat_reference_free(&status.registry_ref); if (n <= 0 || (size_t)n >= sizeof(json)) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n", false); } return amduatd_http_send_json(fd, 200, "OK", json, false); } static bool amduatd_handle_post_artifacts(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req) { uint8_t *body = NULL; bool has_type_tag = false; amduat_type_tag_t type_tag; amduat_asl_io_format_t input_format = AMDUAT_ASL_IO_RAW; amduat_artifact_t artifact; amduat_reference_t ref; amduat_asl_store_error_t err; char *ref_hex = NULL; char json[2048]; memset(&artifact, 0, sizeof(artifact)); memset(&ref, 0, sizeof(ref)); memset(&type_tag, 0, sizeof(type_tag)); if (req == NULL) { return amduatd_http_send_text(fd, 400, "Bad Request", "bad request\n", false); } if (req->content_length > (64u * 1024u * 1024u)) { return amduatd_http_send_text(fd, 413, "Payload Too Large", "payload too large\n", false); } if (req->content_length != 0) { body = (uint8_t *)malloc(req->content_length); if (body == NULL) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "oom\n", false); } if (!amduatd_read_exact(fd, body, req->content_length)) { free(body); return false; } } else { body = NULL; } if (req->content_type[0] != '\0' && strstr(req->content_type, "application/vnd.amduat.asl.artifact+v1") != NULL) { input_format = AMDUAT_ASL_IO_ARTIFACT; } if (input_format == AMDUAT_ASL_IO_RAW) { if (!amduatd_parse_type_tag_hex(req->x_type_tag, &has_type_tag, &type_tag)) { free(body); return amduatd_http_send_text(fd, 400, "Bad Request", "invalid X-Amduat-Type-Tag\n", false); } } if (!amduat_asl_artifact_from_bytes(amduat_octets(body, req->content_length), input_format, has_type_tag, type_tag, &artifact)) { free(body); return amduatd_http_send_text(fd, 400, "Bad Request", "invalid artifact\n", false); } err = amduat_asl_store_put(store, artifact, &ref); amduat_asl_artifact_free(&artifact); if (err != AMDUAT_ASL_STORE_OK) { free((void *)ref.digest.data); return amduatd_http_send_text(fd, 500, "Internal Server Error", "store error\n", false); } if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { free((void *)ref.digest.data); return amduatd_http_send_text(fd, 500, "Internal Server Error", "encode ref error\n", false); } free((void *)ref.digest.data); { int n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", ref_hex); free(ref_hex); if (n <= 0 || (size_t)n >= sizeof(json)) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n", false); } return amduatd_http_send_json(fd, 200, "OK", json, false); } } static bool amduatd_handle_post_pel_run(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const amduatd_http_req_t *req) { uint8_t *body = NULL; const char *p = NULL; const char *end = NULL; bool have_program_ref = false; bool have_input_refs = false; bool have_scheme_ref = false; bool scheme_is_dag = false; bool has_params_ref = false; bool want_receipt = false; bool receipt_have_input_manifest = false; bool receipt_have_environment = false; bool receipt_have_evaluator = false; bool receipt_have_executor = false; bool receipt_have_started = false; bool receipt_have_completed = false; bool receipt_has_sbom = false; bool receipt_has_executor_fingerprint = false; bool receipt_has_run_id = false; bool receipt_has_limits = false; bool receipt_has_logs = false; bool receipt_has_determinism = false; bool receipt_has_rng_seed = false; bool receipt_has_signature = false; char *receipt_evaluator_id = NULL; uint8_t *receipt_parity_digest = NULL; size_t receipt_parity_digest_len = 0; uint8_t *receipt_run_id = NULL; size_t receipt_run_id_len = 0; uint8_t *receipt_rng_seed = NULL; size_t receipt_rng_seed_len = 0; uint8_t *receipt_signature = NULL; size_t receipt_signature_len = 0; uint64_t receipt_started_at = 0; uint64_t receipt_completed_at = 0; uint8_t receipt_determinism_level = 0; amduat_reference_t receipt_input_manifest_ref; amduat_reference_t receipt_environment_ref; amduat_reference_t receipt_executor_ref; amduat_reference_t receipt_sbom_ref; amduat_reference_t receipt_executor_fingerprint_ref; amduat_fer1_limits_t receipt_limits; amduat_fer1_log_entry_t *receipt_logs = NULL; size_t receipt_logs_len = 0; size_t receipt_logs_cap = 0; amduat_reference_t scheme_ref; amduat_reference_t program_ref; amduat_reference_t params_ref; amduat_reference_t *input_refs = NULL; size_t input_refs_len = 0; amduat_pel_run_result_t run_result; amduat_artifact_t receipt_artifact; amduat_reference_t receipt_ref; bool receipt_emitted = false; int status_code = 200; const char *status_reason = "OK"; bool ok = false; memset(&scheme_ref, 0, sizeof(scheme_ref)); memset(&program_ref, 0, sizeof(program_ref)); memset(¶ms_ref, 0, sizeof(params_ref)); memset(&run_result, 0, sizeof(run_result)); memset(&receipt_input_manifest_ref, 0, sizeof(receipt_input_manifest_ref)); memset(&receipt_environment_ref, 0, sizeof(receipt_environment_ref)); memset(&receipt_executor_ref, 0, sizeof(receipt_executor_ref)); memset(&receipt_sbom_ref, 0, sizeof(receipt_sbom_ref)); memset(&receipt_executor_fingerprint_ref, 0, sizeof(receipt_executor_fingerprint_ref)); memset(&receipt_limits, 0, sizeof(receipt_limits)); memset(&receipt_artifact, 0, sizeof(receipt_artifact)); memset(&receipt_ref, 0, sizeof(receipt_ref)); if (store == NULL || req == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } if (cfg == NULL || concepts == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } if (req->content_length > (1u * 1024u * 1024u)) { return amduatd_send_json_error(fd, 413, "Payload Too Large", "payload too large"); } if (req->content_length == 0) { return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); } body = NULL; if (req->content_length != 0) { body = (uint8_t *)malloc(req->content_length); if (body == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } if (!amduatd_read_exact(fd, body, req->content_length)) { free(body); return false; } } p = (const char *)body; end = (const char *)body + req->content_length; if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_run_cleanup; } for (;;) { const char *key = NULL; size_t key_len = 0; const char *sv = NULL; size_t sv_len = 0; const char *cur = NULL; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json key"); goto pel_run_cleanup; } if (!amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_run_cleanup; } if (key_len == strlen("program_ref") && memcmp(key, "program_ref", key_len) == 0) { amduatd_ref_status_t st; if (have_program_ref) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate program_ref"); goto pel_run_cleanup; } if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid program_ref"); goto pel_run_cleanup; } st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, &program_ref); if (st == AMDUATD_REF_ERR_NOT_FOUND) { ok = amduatd_send_json_error(fd, 404, "Not Found", "program_ref not found"); goto pel_run_cleanup; } if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid program_ref"); goto pel_run_cleanup; } have_program_ref = true; } else if (key_len == strlen("scheme_ref") && memcmp(key, "scheme_ref", key_len) == 0) { if (have_scheme_ref) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate scheme_ref"); goto pel_run_cleanup; } if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid scheme_ref"); goto pel_run_cleanup; } if (sv_len == 3 && memcmp(sv, "dag", 3) == 0) { scheme_ref = amduat_pel_program_dag_scheme_ref(); scheme_is_dag = true; } else if (!amduatd_decode_ref_hex_str(sv, sv_len, &scheme_ref)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid scheme_ref"); goto pel_run_cleanup; } have_scheme_ref = true; } else if (key_len == strlen("params_ref") && memcmp(key, "params_ref", key_len) == 0) { amduatd_ref_status_t st; if (has_params_ref) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate params_ref"); goto pel_run_cleanup; } if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid params_ref"); goto pel_run_cleanup; } st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, ¶ms_ref); if (st == AMDUATD_REF_ERR_NOT_FOUND) { ok = amduatd_send_json_error(fd, 404, "Not Found", "params_ref not found"); goto pel_run_cleanup; } if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid params_ref"); goto pel_run_cleanup; } has_params_ref = true; } else if (key_len == strlen("input_refs") && memcmp(key, "input_refs", key_len) == 0) { size_t cap = 0; if (have_input_refs) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate input_refs"); goto pel_run_cleanup; } if (!amduatd_json_expect(&p, end, '[')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_refs"); goto pel_run_cleanup; } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ']') { p = cur + 1; have_input_refs = true; } else { for (;;) { amduat_reference_t ref; memset(&ref, 0, sizeof(ref)); if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_ref"); goto pel_run_cleanup; } { amduatd_ref_status_t st = amduatd_decode_ref_or_name_latest( store, cfg, concepts, dcfg, sv, sv_len, &ref); if (st == AMDUATD_REF_ERR_NOT_FOUND) { ok = amduatd_send_json_error(fd, 404, "Not Found", "input_ref not found"); goto pel_run_cleanup; } if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_ref"); goto pel_run_cleanup; } } if (input_refs_len == cap) { size_t next_cap = cap != 0 ? cap * 2u : 4u; amduat_reference_t *next; if (next_cap > (SIZE_MAX / sizeof(*input_refs))) { amduat_reference_free(&ref); ok = amduatd_send_json_error(fd, 400, "Bad Request", "too many input_refs"); goto pel_run_cleanup; } next = (amduat_reference_t *)realloc( input_refs, next_cap * sizeof(*input_refs)); if (next == NULL) { amduat_reference_free(&ref); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_run_cleanup; } input_refs = next; cap = next_cap; } input_refs[input_refs_len++] = ref; cur = amduatd_json_skip_ws(p, end); if (cur >= end) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_refs"); goto pel_run_cleanup; } if (*cur == ',') { p = cur + 1; continue; } if (*cur == ']') { p = cur + 1; have_input_refs = true; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_refs"); goto pel_run_cleanup; } } } else if (key_len == strlen("receipt") && memcmp(key, "receipt", key_len) == 0) { const char *rkey = NULL; size_t rkey_len = 0; if (want_receipt) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate receipt"); goto pel_run_cleanup; } if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid receipt"); goto pel_run_cleanup; } want_receipt = true; for (;;) { cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &rkey, &rkey_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid receipt"); goto pel_run_cleanup; } if (rkey_len == strlen("input_manifest_ref") && memcmp(rkey, "input_manifest_ref", rkey_len) == 0) { amduatd_ref_status_t st; if (receipt_have_input_manifest || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_manifest_ref"); goto pel_run_cleanup; } st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, &receipt_input_manifest_ref); if (st == AMDUATD_REF_ERR_NOT_FOUND) { ok = amduatd_send_json_error(fd, 404, "Not Found", "input_manifest_ref not found"); goto pel_run_cleanup; } if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_manifest_ref"); goto pel_run_cleanup; } receipt_have_input_manifest = true; } else if (rkey_len == strlen("environment_ref") && memcmp(rkey, "environment_ref", rkey_len) == 0) { amduatd_ref_status_t st; if (receipt_have_environment || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid environment_ref"); goto pel_run_cleanup; } st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, &receipt_environment_ref); if (st == AMDUATD_REF_ERR_NOT_FOUND) { ok = amduatd_send_json_error(fd, 404, "Not Found", "environment_ref not found"); goto pel_run_cleanup; } if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid environment_ref"); goto pel_run_cleanup; } receipt_have_environment = true; } else if (rkey_len == strlen("evaluator_id") && memcmp(rkey, "evaluator_id", rkey_len) == 0) { char *tmp = NULL; if (receipt_have_evaluator || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &tmp)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid evaluator_id"); free(tmp); goto pel_run_cleanup; } free(receipt_evaluator_id); receipt_evaluator_id = tmp; receipt_have_evaluator = true; } else if (rkey_len == strlen("executor_ref") && memcmp(rkey, "executor_ref", rkey_len) == 0) { amduatd_ref_status_t st; if (receipt_have_executor || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid executor_ref"); goto pel_run_cleanup; } st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, &receipt_executor_ref); if (st == AMDUATD_REF_ERR_NOT_FOUND) { ok = amduatd_send_json_error(fd, 404, "Not Found", "executor_ref not found"); goto pel_run_cleanup; } if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid executor_ref"); goto pel_run_cleanup; } receipt_have_executor = true; } else if (rkey_len == strlen("sbom_ref") && memcmp(rkey, "sbom_ref", rkey_len) == 0) { amduatd_ref_status_t st; if (receipt_has_sbom || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid sbom_ref"); goto pel_run_cleanup; } st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, &receipt_sbom_ref); if (st == AMDUATD_REF_ERR_NOT_FOUND) { ok = amduatd_send_json_error(fd, 404, "Not Found", "sbom_ref not found"); goto pel_run_cleanup; } if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid sbom_ref"); goto pel_run_cleanup; } receipt_has_sbom = true; } else if (rkey_len == strlen("executor_fingerprint_ref") && memcmp(rkey, "executor_fingerprint_ref", rkey_len) == 0) { amduatd_ref_status_t st; if (receipt_has_executor_fingerprint || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid executor_fingerprint_ref"); goto pel_run_cleanup; } st = amduatd_decode_ref_or_name_latest( store, cfg, concepts, dcfg, sv, sv_len, &receipt_executor_fingerprint_ref); if (st == AMDUATD_REF_ERR_NOT_FOUND) { ok = amduatd_send_json_error(fd, 404, "Not Found", "executor_fingerprint_ref not found"); goto pel_run_cleanup; } if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid executor_fingerprint_ref"); goto pel_run_cleanup; } receipt_has_executor_fingerprint = true; } else if (rkey_len == strlen("parity_digest_hex") && memcmp(rkey, "parity_digest_hex", rkey_len) == 0) { char *tmp = NULL; uint8_t *bytes = NULL; size_t bytes_len = 0; if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &tmp)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid parity_digest_hex"); free(tmp); goto pel_run_cleanup; } if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) { free(tmp); ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid parity_digest_hex"); goto pel_run_cleanup; } free(tmp); free(receipt_parity_digest); receipt_parity_digest = bytes; receipt_parity_digest_len = bytes_len; } else if (rkey_len == strlen("run_id_hex") && memcmp(rkey, "run_id_hex", rkey_len) == 0) { char *tmp = NULL; uint8_t *bytes = NULL; size_t bytes_len = 0; if (receipt_has_run_id || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &tmp)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid run_id_hex"); free(tmp); goto pel_run_cleanup; } if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) { free(tmp); ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid run_id_hex"); goto pel_run_cleanup; } free(tmp); free(receipt_run_id); receipt_run_id = bytes; receipt_run_id_len = bytes_len; receipt_has_run_id = true; } else if (rkey_len == strlen("limits") && memcmp(rkey, "limits", rkey_len) == 0) { bool have_cpu = false; bool have_wall = false; bool have_rss = false; bool have_reads = false; bool have_writes = false; if (receipt_has_limits || !amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits"); goto pel_run_cleanup; } for (;;) { cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits"); goto pel_run_cleanup; } if (sv_len == strlen("cpu_ms") && memcmp(sv, "cpu_ms", sv_len) == 0) { if (have_cpu || !amduatd_json_parse_u64(&p, end, &receipt_limits.cpu_ms)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits cpu_ms"); goto pel_run_cleanup; } have_cpu = true; } else if (sv_len == strlen("wall_ms") && memcmp(sv, "wall_ms", sv_len) == 0) { if (have_wall || !amduatd_json_parse_u64(&p, end, &receipt_limits.wall_ms)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits wall_ms"); goto pel_run_cleanup; } have_wall = true; } else if (sv_len == strlen("max_rss_kib") && memcmp(sv, "max_rss_kib", sv_len) == 0) { if (have_rss || !amduatd_json_parse_u64(&p, end, &receipt_limits.max_rss_kib)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits max_rss_kib"); goto pel_run_cleanup; } have_rss = true; } else if (sv_len == strlen("io_reads") && memcmp(sv, "io_reads", sv_len) == 0) { if (have_reads || !amduatd_json_parse_u64(&p, end, &receipt_limits.io_reads)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits io_reads"); goto pel_run_cleanup; } have_reads = true; } else if (sv_len == strlen("io_writes") && memcmp(sv, "io_writes", sv_len) == 0) { if (have_writes || !amduatd_json_parse_u64(&p, end, &receipt_limits.io_writes)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits io_writes"); goto pel_run_cleanup; } have_writes = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits"); goto pel_run_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur >= end) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits"); goto pel_run_cleanup; } if (*cur == ',') { p = cur + 1; continue; } if (*cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid limits"); goto pel_run_cleanup; } if (!have_cpu || !have_wall || !have_rss || !have_reads || !have_writes) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing limits fields"); goto pel_run_cleanup; } receipt_has_limits = true; } else if (rkey_len == strlen("logs") && memcmp(rkey, "logs", rkey_len) == 0) { if (receipt_has_logs || !amduatd_json_expect(&p, end, '[')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid logs"); goto pel_run_cleanup; } receipt_has_logs = true; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ']') { p = cur + 1; } else { for (;;) { amduat_fer1_log_entry_t entry; bool have_kind = false; bool have_log_ref = false; bool have_sha = false; memset(&entry, 0, sizeof(entry)); if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid log entry"); goto pel_run_cleanup; } for (;;) { const char *lkey = NULL; size_t lkey_len = 0; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &lkey, &lkey_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid log entry"); goto pel_run_cleanup; } if (lkey_len == strlen("kind") && memcmp(lkey, "kind", lkey_len) == 0) { uint32_t kind = 0; if (have_kind || !amduatd_json_parse_u32_loose(&p, end, &kind)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid log kind"); goto pel_run_cleanup; } entry.kind = kind; have_kind = true; } else if (lkey_len == strlen("log_ref") && memcmp(lkey, "log_ref", lkey_len) == 0) { amduatd_ref_status_t st; if (have_log_ref || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid log_ref"); goto pel_run_cleanup; } st = amduatd_decode_ref_or_name_latest( store, cfg, concepts, dcfg, sv, sv_len, &entry.log_ref); if (st == AMDUATD_REF_ERR_NOT_FOUND) { ok = amduatd_send_json_error(fd, 404, "Not Found", "log_ref not found"); goto pel_run_cleanup; } if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid log_ref"); goto pel_run_cleanup; } have_log_ref = true; } else if (lkey_len == strlen("sha256_hex") && memcmp(lkey, "sha256_hex", lkey_len) == 0) { char *tmp = NULL; uint8_t *bytes = NULL; size_t bytes_len = 0; if (have_sha || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &tmp)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid sha256_hex"); free(tmp); goto pel_run_cleanup; } if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) { free(tmp); ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid sha256_hex"); goto pel_run_cleanup; } free(tmp); entry.sha256 = amduat_octets(bytes, bytes_len); have_sha = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid log entry"); goto pel_run_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur >= end) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid log entry"); goto pel_run_cleanup; } if (*cur == ',') { p = cur + 1; continue; } if (*cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid log entry"); goto pel_run_cleanup; } if (!have_kind || !have_log_ref || !have_sha) { if (have_log_ref) { amduat_reference_free(&entry.log_ref); } if (have_sha) { amduat_octets_free(&entry.sha256); } ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing log fields"); goto pel_run_cleanup; } if (receipt_logs_len == receipt_logs_cap) { size_t next_cap = receipt_logs_cap != 0 ? receipt_logs_cap * 2u : 4u; amduat_fer1_log_entry_t *next = (amduat_fer1_log_entry_t *)realloc( receipt_logs, next_cap * sizeof(*receipt_logs)); if (next == NULL) { amduat_reference_free(&entry.log_ref); amduat_octets_free(&entry.sha256); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_run_cleanup; } receipt_logs = next; receipt_logs_cap = next_cap; } receipt_logs[receipt_logs_len++] = entry; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == ']') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid logs"); goto pel_run_cleanup; } } } else if (rkey_len == strlen("determinism_level") && memcmp(rkey, "determinism_level", rkey_len) == 0) { uint32_t level = 0; if (receipt_has_determinism || !amduatd_json_parse_u32_loose(&p, end, &level) || level > 255u) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid determinism_level"); goto pel_run_cleanup; } receipt_determinism_level = (uint8_t)level; receipt_has_determinism = true; } else if (rkey_len == strlen("rng_seed_hex") && memcmp(rkey, "rng_seed_hex", rkey_len) == 0) { char *tmp = NULL; uint8_t *bytes = NULL; size_t bytes_len = 0; if (receipt_has_rng_seed || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &tmp)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid rng_seed_hex"); free(tmp); goto pel_run_cleanup; } if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) { free(tmp); ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid rng_seed_hex"); goto pel_run_cleanup; } free(tmp); free(receipt_rng_seed); receipt_rng_seed = bytes; receipt_rng_seed_len = bytes_len; receipt_has_rng_seed = true; } else if (rkey_len == strlen("signature_hex") && memcmp(rkey, "signature_hex", rkey_len) == 0) { char *tmp = NULL; uint8_t *bytes = NULL; size_t bytes_len = 0; if (receipt_has_signature || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &tmp)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid signature_hex"); free(tmp); goto pel_run_cleanup; } if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) { free(tmp); ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid signature_hex"); goto pel_run_cleanup; } free(tmp); free(receipt_signature); receipt_signature = bytes; receipt_signature_len = bytes_len; receipt_has_signature = true; } else if (rkey_len == strlen("started_at") && memcmp(rkey, "started_at", rkey_len) == 0) { if (receipt_have_started || !amduatd_json_parse_u64(&p, end, &receipt_started_at)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid started_at"); goto pel_run_cleanup; } receipt_have_started = true; } else if (rkey_len == strlen("completed_at") && memcmp(rkey, "completed_at", rkey_len) == 0) { if (receipt_have_completed || !amduatd_json_parse_u64(&p, end, &receipt_completed_at)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid completed_at"); goto pel_run_cleanup; } receipt_have_completed = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid receipt"); goto pel_run_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur >= end) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid receipt"); goto pel_run_cleanup; } if (*cur == ',') { p = cur + 1; continue; } if (*cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid receipt"); goto pel_run_cleanup; } } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_run_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur >= end) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_run_cleanup; } if (*cur == ',') { p = cur + 1; continue; } if (*cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_run_cleanup; } p = amduatd_json_skip_ws(p, end); if (p != end) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_run_cleanup; } free(body); body = NULL; if (!have_program_ref) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing program_ref"); goto pel_run_cleanup; } if (!have_input_refs) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing input_refs"); goto pel_run_cleanup; } if (!have_scheme_ref) { scheme_ref = amduat_pel_program_dag_scheme_ref(); scheme_is_dag = true; } if (want_receipt && (!receipt_have_input_manifest || !receipt_have_environment || !receipt_have_evaluator || !receipt_have_executor || !receipt_have_started || !receipt_have_completed)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing receipt fields"); goto pel_run_cleanup; } if (!amduat_pel_surf_run_with_result(store, scheme_ref, program_ref, input_refs, input_refs_len, has_params_ref, params_ref, &run_result)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "pel run failed"); goto pel_run_cleanup; } if (run_result.has_result_value && run_result.result_value.has_store_failure) { if (run_result.result_value.store_failure.error_code == AMDUAT_PEL_STORE_ERROR_NOT_FOUND) { status_code = 404; status_reason = "Not Found"; } else { status_code = 500; status_reason = "Internal Server Error"; } } if (want_receipt) { amduat_octets_t evaluator_id; amduat_octets_t parity_digest; bool use_receipt_v1_1 = receipt_has_executor_fingerprint || receipt_has_run_id || receipt_has_limits || receipt_has_logs || receipt_has_determinism || receipt_has_rng_seed || receipt_has_signature; if (!run_result.has_result_value) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "receipt unavailable"); goto pel_run_cleanup; } if (run_result.result_value.output_refs_len != 1) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "receipt requires single output"); goto pel_run_cleanup; } evaluator_id = amduat_octets(receipt_evaluator_id, receipt_evaluator_id != NULL ? strlen(receipt_evaluator_id) : 0u); parity_digest = amduat_octets(receipt_parity_digest, receipt_parity_digest_len); if (use_receipt_v1_1) { amduat_octets_t run_id = amduat_octets(receipt_run_id, receipt_run_id_len); amduat_octets_t rng_seed = amduat_octets(receipt_rng_seed, receipt_rng_seed_len); amduat_octets_t signature = amduat_octets(receipt_signature, receipt_signature_len); if (!amduat_fer1_receipt_from_pel_run_v1_1( &run_result, receipt_input_manifest_ref, receipt_environment_ref, evaluator_id, receipt_executor_ref, receipt_has_sbom, receipt_sbom_ref, parity_digest, receipt_started_at, receipt_completed_at, receipt_has_executor_fingerprint, receipt_executor_fingerprint_ref, receipt_has_run_id, run_id, receipt_has_limits, receipt_limits, receipt_logs, receipt_logs_len, receipt_has_determinism, receipt_determinism_level, receipt_has_rng_seed, rng_seed, receipt_has_signature, signature, &receipt_artifact)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "receipt build failed"); goto pel_run_cleanup; } } else { if (!amduat_fer1_receipt_from_pel_result( &run_result.result_value, receipt_input_manifest_ref, receipt_environment_ref, evaluator_id, receipt_executor_ref, receipt_has_sbom, receipt_sbom_ref, parity_digest, receipt_started_at, receipt_completed_at, &receipt_artifact)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "receipt build failed"); goto pel_run_cleanup; } } if (amduat_asl_store_put(store, receipt_artifact, &receipt_ref) != AMDUAT_ASL_STORE_OK) { amduat_asl_artifact_free(&receipt_artifact); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "receipt store failed"); goto pel_run_cleanup; } amduat_asl_artifact_free(&receipt_artifact); receipt_emitted = true; } { amduatd_strbuf_t resp; char *result_hex = NULL; char *trace_hex = NULL; char *receipt_hex = NULL; const char *status = "UNKNOWN"; size_t i; memset(&resp, 0, sizeof(resp)); if (!amduat_asl_ref_encode_hex(run_result.result_ref, &result_hex)) { amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto pel_run_cleanup; } if (run_result.has_result_value) { status = amduat_format_pel_status_name( run_result.result_value.core_result.status); if (run_result.result_value.has_trace_ref) { if (!amduat_asl_ref_encode_hex(run_result.result_value.trace_ref, &trace_hex)) { free(result_hex); amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto pel_run_cleanup; } } } if (!amduatd_strbuf_append_cstr(&resp, "{\"result_ref\":\"") || !amduatd_strbuf_append_cstr(&resp, result_hex) || !amduatd_strbuf_append_cstr(&resp, "\"")) { free(result_hex); free(trace_hex); amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_run_cleanup; } free(result_hex); if (trace_hex != NULL) { if (!amduatd_strbuf_append_cstr(&resp, ",\"trace_ref\":\"") || !amduatd_strbuf_append_cstr(&resp, trace_hex) || !amduatd_strbuf_append_cstr(&resp, "\"")) { free(trace_hex); amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_run_cleanup; } free(trace_hex); } if (receipt_emitted) { if (!amduat_asl_ref_encode_hex(receipt_ref, &receipt_hex)) { amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto pel_run_cleanup; } if (!amduatd_strbuf_append_cstr(&resp, ",\"receipt_ref\":\"") || !amduatd_strbuf_append_cstr(&resp, receipt_hex) || !amduatd_strbuf_append_cstr(&resp, "\"")) { free(receipt_hex); amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_run_cleanup; } free(receipt_hex); } if (!amduatd_strbuf_append_cstr(&resp, ",\"output_refs\":[")) { amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_run_cleanup; } for (i = 0; i < run_result.output_refs_len; ++i) { char *out_hex = NULL; if (!amduat_asl_ref_encode_hex(run_result.output_refs[i], &out_hex)) { amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto pel_run_cleanup; } if (i != 0) { if (!amduatd_strbuf_append_char(&resp, ',')) { free(out_hex); amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_run_cleanup; } } if (!amduatd_strbuf_append_cstr(&resp, "\"") || !amduatd_strbuf_append_cstr(&resp, out_hex) || !amduatd_strbuf_append_cstr(&resp, "\"")) { free(out_hex); amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_run_cleanup; } free(out_hex); } if (!amduatd_strbuf_append_cstr(&resp, "],\"status\":\"") || !amduatd_strbuf_append_cstr(&resp, status) || !amduatd_strbuf_append_cstr(&resp, "\"}\n")) { amduatd_strbuf_free(&resp); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_run_cleanup; } ok = amduatd_http_send_json(fd, status_code, status_reason, resp.data, false); amduatd_strbuf_free(&resp); } pel_run_cleanup: if (body != NULL) { free(body); } if (run_result.has_result_value) { amduat_enc_pel1_result_free(&run_result.result_value); } if (run_result.output_refs != NULL) { amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len); } amduat_pel_surf_free_ref(&run_result.result_ref); if (input_refs != NULL) { size_t i; for (i = 0; i < input_refs_len; ++i) { amduat_reference_free(&input_refs[i]); } free(input_refs); } if (has_params_ref) { amduat_reference_free(¶ms_ref); } if (have_program_ref) { amduat_reference_free(&program_ref); } if (have_scheme_ref && !scheme_is_dag) { amduat_reference_free(&scheme_ref); } if (receipt_emitted) { amduat_reference_free(&receipt_ref); } if (receipt_have_input_manifest) { amduat_reference_free(&receipt_input_manifest_ref); } if (receipt_have_environment) { amduat_reference_free(&receipt_environment_ref); } if (receipt_have_executor) { amduat_reference_free(&receipt_executor_ref); } if (receipt_has_sbom) { amduat_reference_free(&receipt_sbom_ref); } if (receipt_has_executor_fingerprint) { amduat_reference_free(&receipt_executor_fingerprint_ref); } if (receipt_logs != NULL) { size_t i; for (i = 0; i < receipt_logs_len; ++i) { amduat_reference_free(&receipt_logs[i].log_ref); amduat_octets_free(&receipt_logs[i].sha256); } free(receipt_logs); } free(receipt_evaluator_id); free(receipt_parity_digest); free(receipt_run_id); free(receipt_rng_seed); free(receipt_signature); return ok; } typedef struct { amduat_pel_node_t *nodes; size_t nodes_len; amduat_pel_root_ref_t *roots; size_t roots_len; } amduatd_pel_program_tmp_t; static void amduatd_pel_program_tmp_free(amduatd_pel_program_tmp_t *tmp) { size_t i; if (tmp == NULL) { return; } if (tmp->nodes != NULL) { for (i = 0; i < tmp->nodes_len; ++i) { free(tmp->nodes[i].inputs); tmp->nodes[i].inputs = NULL; tmp->nodes[i].inputs_len = 0; free((void *)tmp->nodes[i].params.data); tmp->nodes[i].params.data = NULL; tmp->nodes[i].params.len = 0; } } free(tmp->nodes); tmp->nodes = NULL; tmp->nodes_len = 0; free(tmp->roots); tmp->roots = NULL; tmp->roots_len = 0; } static bool amduatd_parse_params_hex(const char *s, size_t len, amduat_octets_t *out) { char *tmp = NULL; uint8_t *bytes = NULL; size_t out_len = 0; if (out != NULL) { *out = amduat_octets(NULL, 0); } if (s == NULL || out == NULL) { return false; } if (!amduatd_copy_json_str(s, len, &tmp)) { return false; } if (tmp[0] == '\0') { free(tmp); *out = amduat_octets(NULL, 0); return true; } if (!amduat_hex_decode_alloc(tmp, &bytes, &out_len)) { free(tmp); return false; } free(tmp); *out = amduat_octets(bytes, out_len); return true; } static bool amduatd_handle_post_pel_programs(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req) { uint8_t *body = NULL; const char *p = NULL; const char *end = NULL; amduatd_pel_program_tmp_t tmp; amduat_pel_program_t program; amduat_octets_t program_bytes; amduat_artifact_t artifact; amduat_reference_t program_ref; char *ref_hex = NULL; char json[2048]; bool ok = false; memset(&tmp, 0, sizeof(tmp)); memset(&program, 0, sizeof(program)); program_bytes = amduat_octets(NULL, 0); memset(&artifact, 0, sizeof(artifact)); memset(&program_ref, 0, sizeof(program_ref)); if (store == NULL || req == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } if (req->content_length == 0) { return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); } if (req->content_length > (2u * 1024u * 1024u)) { return amduatd_send_json_error(fd, 413, "Payload Too Large", "payload too large"); } body = (uint8_t *)malloc(req->content_length); if (body == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } if (!amduatd_read_exact(fd, body, req->content_length)) { free(body); return false; } p = (const char *)body; end = (const char *)body + req->content_length; if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_programs_cleanup; } { bool have_nodes = false; bool have_roots = false; for (;;) { const char *key = NULL; size_t key_len = 0; const char *cur = NULL; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_programs_cleanup; } if (key_len == strlen("nodes") && memcmp(key, "nodes", key_len) == 0) { size_t cap = 0; if (have_nodes) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate nodes"); goto pel_programs_cleanup; } if (!amduatd_json_expect(&p, end, '[')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid nodes"); goto pel_programs_cleanup; } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ']') { p = cur + 1; have_nodes = true; } else { for (;;) { amduat_pel_node_t node; bool have_id = false; bool have_op = false; bool have_inputs = false; bool have_params_hex = false; amduat_octets_t op_name = amduat_octets(NULL, 0); uint32_t op_ver = 0; memset(&node, 0, sizeof(node)); if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid node"); goto pel_programs_cleanup; } for (;;) { const char *nk = NULL; size_t nk_len = 0; const char *sv = NULL; size_t sv_len = 0; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &nk, &nk_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid node"); goto pel_programs_cleanup; } if (nk_len == strlen("id") && memcmp(nk, "id", nk_len) == 0) { uint32_t id = 0; if (have_id) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate node id"); goto pel_programs_cleanup; } if (!amduatd_json_parse_u32_loose(&p, end, &id)) { char near[32]; char msg[96]; int n; amduatd_json_peek_token(p, end, near, sizeof(near)); n = snprintf(msg, sizeof(msg), "invalid node id near '%s'", near[0] != '\0' ? near : "?"); if (n > 0 && (size_t)n < sizeof(msg)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", msg); } else { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid node id"); } goto pel_programs_cleanup; } node.id = (amduat_pel_node_id_t)id; have_id = true; } else if (nk_len == strlen("op") && memcmp(nk, "op", nk_len) == 0) { bool have_name = false; bool have_version = false; if (have_op || !amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid op"); goto pel_programs_cleanup; } for (;;) { const char *okey = NULL; size_t okey_len = 0; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &okey, &okey_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid op"); goto pel_programs_cleanup; } if (okey_len == strlen("name") && memcmp(okey, "name", okey_len) == 0) { if (have_name || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid op name"); goto pel_programs_cleanup; } op_name = amduat_octets(sv, sv_len); have_name = true; } else if (okey_len == strlen("version") && memcmp(okey, "version", okey_len) == 0) { if (have_version || !amduatd_json_parse_u32_loose(&p, end, &op_ver)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid op version"); goto pel_programs_cleanup; } have_version = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid op"); goto pel_programs_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid op"); goto pel_programs_cleanup; } if (!have_name || !have_version) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing op fields"); goto pel_programs_cleanup; } node.op.name = op_name; node.op.version = op_ver; have_op = true; } else if (nk_len == strlen("inputs") && memcmp(nk, "inputs", nk_len) == 0) { size_t icap = 0; if (have_inputs || !amduatd_json_expect(&p, end, '[')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid inputs"); goto pel_programs_cleanup; } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ']') { p = cur + 1; have_inputs = true; } else { for (;;) { amduat_pel_dag_input_t in; memset(&in, 0, sizeof(in)); if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input"); goto pel_programs_cleanup; } { const char *ik = NULL; size_t ik_len = 0; if (!amduatd_json_parse_string_noesc(&p, end, &ik, &ik_len) || !amduatd_json_expect(&p, end, ':') || !amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input"); goto pel_programs_cleanup; } if (ik_len == strlen("external") && memcmp(ik, "external", ik_len) == 0) { uint32_t idx = 0; const char *k2 = NULL; size_t k2_len = 0; if (!amduatd_json_parse_string_noesc(&p, end, &k2, &k2_len) || k2_len != strlen("input_index") || memcmp(k2, "input_index", k2_len) != 0 || !amduatd_json_expect(&p, end, ':') || !amduatd_json_parse_u32_loose(&p, end, &idx) || !amduatd_json_expect(&p, end, '}')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid external input"); goto pel_programs_cleanup; } in.kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; in.value.external.input_index = idx; } else if (ik_len == strlen("node") && memcmp(ik, "node", ik_len) == 0) { bool have_node_id = false; bool have_output_index = false; uint32_t nid = 0; uint32_t oidx = 0; for (;;) { const char *k2 = NULL; size_t k2_len = 0; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &k2, &k2_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid node input"); goto pel_programs_cleanup; } if (k2_len == strlen("node_id") && memcmp(k2, "node_id", k2_len) == 0) { if (have_node_id || !amduatd_json_parse_u32_loose(&p, end, &nid)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid node_id"); goto pel_programs_cleanup; } have_node_id = true; } else if (k2_len == strlen("output_index") && memcmp(k2, "output_index", k2_len) == 0) { if (have_output_index || !amduatd_json_parse_u32_loose(&p, end, &oidx)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid output_index"); goto pel_programs_cleanup; } have_output_index = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid node input"); goto pel_programs_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid node input"); goto pel_programs_cleanup; } if (!have_node_id || !have_output_index) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing node input fields"); goto pel_programs_cleanup; } in.kind = AMDUAT_PEL_DAG_INPUT_NODE; in.value.node.node_id = (amduat_pel_node_id_t)nid; in.value.node.output_index = oidx; } else { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input kind"); goto pel_programs_cleanup; } if (!amduatd_json_expect(&p, end, '}')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input"); goto pel_programs_cleanup; } } if (node.inputs_len == icap) { size_t next_cap = icap != 0 ? icap * 2u : 4u; amduat_pel_dag_input_t *next = (amduat_pel_dag_input_t *)realloc( node.inputs, next_cap * sizeof(*node.inputs)); if (next == NULL) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_programs_cleanup; } node.inputs = next; icap = next_cap; } node.inputs[node.inputs_len++] = in; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == ']') { p = cur + 1; have_inputs = true; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid inputs"); goto pel_programs_cleanup; } } } else if (nk_len == strlen("params_hex") && memcmp(nk, "params_hex", nk_len) == 0) { if (have_params_hex || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_parse_params_hex(sv, sv_len, &node.params)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid params_hex"); goto pel_programs_cleanup; } have_params_hex = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid node"); goto pel_programs_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } } if (!have_id || !have_op || !have_inputs || !have_params_hex) { free(node.inputs); free((void *)node.params.data); ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing node fields"); goto pel_programs_cleanup; } if (amduat_pel_kernel_op_lookup(node.op.name, node.op.version) == NULL) { free(node.inputs); free((void *)node.params.data); ok = amduatd_send_json_error(fd, 400, "Bad Request", "unknown op"); goto pel_programs_cleanup; } if (tmp.nodes_len == cap) { size_t next_cap = cap != 0 ? cap * 2u : 4u; amduat_pel_node_t *next = (amduat_pel_node_t *)realloc(tmp.nodes, next_cap * sizeof(*tmp.nodes)); if (next == NULL) { free(node.inputs); free((void *)node.params.data); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_programs_cleanup; } tmp.nodes = next; cap = next_cap; } tmp.nodes[tmp.nodes_len++] = node; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == ']') { p = cur + 1; have_nodes = true; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid nodes"); goto pel_programs_cleanup; } } } else if (key_len == strlen("roots") && memcmp(key, "roots", key_len) == 0) { size_t cap = 0; if (have_roots) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate roots"); goto pel_programs_cleanup; } if (!amduatd_json_expect(&p, end, '[')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid roots"); goto pel_programs_cleanup; } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ']') { p = cur + 1; have_roots = true; } else { for (;;) { amduat_pel_root_ref_t root; bool have_node_id = false; bool have_output_index = false; memset(&root, 0, sizeof(root)); if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid root"); goto pel_programs_cleanup; } for (;;) { const char *rk = NULL; size_t rk_len = 0; uint32_t v = 0; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &rk, &rk_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid root"); goto pel_programs_cleanup; } if (rk_len == strlen("node_id") && memcmp(rk, "node_id", rk_len) == 0) { if (have_node_id || !amduatd_json_parse_u32_loose(&p, end, &v)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid root node_id"); goto pel_programs_cleanup; } root.node_id = (amduat_pel_node_id_t)v; have_node_id = true; } else if (rk_len == strlen("output_index") && memcmp(rk, "output_index", rk_len) == 0) { if (have_output_index || !amduatd_json_parse_u32_loose(&p, end, &v)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid root output_index"); goto pel_programs_cleanup; } root.output_index = v; have_output_index = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid root"); goto pel_programs_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid root"); goto pel_programs_cleanup; } if (!have_node_id || !have_output_index) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing root fields"); goto pel_programs_cleanup; } if (tmp.roots_len == cap) { size_t next_cap = cap != 0 ? cap * 2u : 2u; amduat_pel_root_ref_t *next = (amduat_pel_root_ref_t *)realloc( tmp.roots, next_cap * sizeof(*tmp.roots)); if (next == NULL) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto pel_programs_cleanup; } tmp.roots = next; cap = next_cap; } tmp.roots[tmp.roots_len++] = root; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == ']') { p = cur + 1; have_roots = true; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid roots"); goto pel_programs_cleanup; } } } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_programs_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_programs_cleanup; } p = amduatd_json_skip_ws(p, end); if (p != end) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto pel_programs_cleanup; } if (!have_nodes || !have_roots) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing nodes/roots"); goto pel_programs_cleanup; } } program.nodes = tmp.nodes; program.nodes_len = tmp.nodes_len; program.roots = tmp.roots; program.roots_len = tmp.roots_len; if (!amduat_pel_program_dag_validate(&program)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid program"); goto pel_programs_cleanup; } if (!amduat_enc_pel_program_dag_encode_v1(&program, &program_bytes)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto pel_programs_cleanup; } artifact = amduat_artifact_with_type(program_bytes, amduat_type_tag(AMDUAT_PEL_TYPE_TAG_PROGRAM_DAG_1)); if (amduat_asl_store_put(store, artifact, &program_ref) != AMDUAT_ASL_STORE_OK) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); goto pel_programs_cleanup; } if (!amduat_asl_ref_encode_hex(program_ref, &ref_hex)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode ref error"); goto pel_programs_cleanup; } { int n = snprintf(json, sizeof(json), "{\"program_ref\":\"%s\"}\n", ref_hex); free(ref_hex); if (n <= 0 || (size_t)n >= sizeof(json)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); goto pel_programs_cleanup; } ok = amduatd_http_send_json(fd, 200, "OK", json, false); goto pel_programs_cleanup; } pel_programs_cleanup: free(body); free((void *)program_bytes.data); amduat_reference_free(&program_ref); amduatd_pel_program_tmp_free(&tmp); return ok; } static bool amduatd_handle_post_concepts(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const amduatd_http_req_t *req) { uint8_t *body = NULL; const char *p = NULL; const char *end = NULL; char *name = NULL; bool have_name = false; bool have_ref = false; amduat_reference_t target_ref; amduat_reference_t name_ref; amduat_reference_t concept_ref; amduat_reference_t edge_ref; char *concept_hex = NULL; char *scoped_name = NULL; char json[4096]; bool ok = false; amduat_octets_t actor = amduat_octets(NULL, 0u); memset(&target_ref, 0, sizeof(target_ref)); memset(&name_ref, 0, sizeof(name_ref)); memset(&concept_ref, 0, sizeof(concept_ref)); memset(&edge_ref, 0, sizeof(edge_ref)); if (store == NULL || cfg == NULL || concepts == NULL || req == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } if (req->has_actor) { actor = req->actor; } if (req->content_length == 0) { return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); } if (req->content_length > (256u * 1024u)) { return amduatd_send_json_error(fd, 413, "Payload Too Large", "payload too large"); } body = (uint8_t *)malloc(req->content_length); if (body == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } if (!amduatd_read_exact(fd, body, req->content_length)) { free(body); return false; } p = (const char *)body; end = (const char *)body + req->content_length; if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto concepts_cleanup; } for (;;) { const char *key = NULL; size_t key_len = 0; const char *sv = NULL; size_t sv_len = 0; const char *cur = NULL; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto concepts_cleanup; } if (key_len == strlen("name") && memcmp(key, "name", key_len) == 0) { if (have_name || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &name)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); goto concepts_cleanup; } have_name = true; } else if (key_len == strlen("ref") && memcmp(key, "ref", key_len) == 0) { if (have_ref || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_decode_ref_hex_str(sv, sv_len, &target_ref)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid ref"); goto concepts_cleanup; } have_ref = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto concepts_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto concepts_cleanup; } if (!have_name) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing name"); goto concepts_cleanup; } if (!amduatd_scope_name_cstr(dcfg, name, &scoped_name)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); goto concepts_cleanup; } amduatd_log_scoped_name(dcfg, name, scoped_name); if (!amduatd_concepts_put_name_artifact(store, scoped_name, &name_ref)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); goto concepts_cleanup; } { amduat_reference_t existing; memset(&existing, 0, sizeof(existing)); if (amduatd_concepts_lookup_alias(store, cfg, concepts, scoped_name, &existing)) { amduat_reference_free(&existing); ok = amduatd_send_json_error(fd, 409, "Conflict", "name exists"); goto concepts_cleanup; } } if (!amduatd_concepts_put_concept_id(store, cfg, &concept_ref)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); goto concepts_cleanup; } if (!amduatd_concepts_put_edge(store, concepts, name_ref, concept_ref, concepts->rel_aliases_ref, actor, &edge_ref)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); goto concepts_cleanup; } if (have_ref) { if (!amduatd_concepts_put_edge(store, concepts, concept_ref, target_ref, concepts->rel_materializes_ref, actor, &edge_ref)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); goto concepts_cleanup; } } if (!amduat_asl_ref_encode_hex(concept_ref, &concept_hex)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto concepts_cleanup; } { int n = snprintf(json, sizeof(json), "{" "\"name\":\"%s\"," "\"concept_ref\":\"%s\"" "}\n", name, concept_hex); free(concept_hex); if (n <= 0 || (size_t)n >= sizeof(json)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); goto concepts_cleanup; } ok = amduatd_http_send_json(fd, 200, "OK", json, false); } concepts_cleanup: free(body); free(name); free(scoped_name); amduat_reference_free(&target_ref); amduat_reference_free(&name_ref); amduat_reference_free(&concept_ref); amduat_reference_free(&edge_ref); return ok; } typedef struct { char *key_hex; char *value_hex; uint64_t value_int; int value_kind; } amduatd_cf_binding_t; static void amduatd_cf_binding_free(amduatd_cf_binding_t *b) { if (b == NULL) { return; } free(b->key_hex); free(b->value_hex); b->key_hex = NULL; b->value_hex = NULL; b->value_int = 0; b->value_kind = 0; } static int amduatd_cf_binding_cmp(const void *a, const void *b) { const amduatd_cf_binding_t *x = (const amduatd_cf_binding_t *)a; const amduatd_cf_binding_t *y = (const amduatd_cf_binding_t *)b; int c; if (x == NULL || y == NULL) { return 0; } c = strcmp(x->key_hex != NULL ? x->key_hex : "", y->key_hex != NULL ? y->key_hex : ""); if (c != 0) { return c; } if (x->value_kind != y->value_kind) { return x->value_kind < y->value_kind ? -1 : 1; } if (x->value_kind == 1) { if (x->value_int < y->value_int) { return -1; } if (x->value_int > y->value_int) { return 1; } return 0; } return strcmp(x->value_hex != NULL ? x->value_hex : "", y->value_hex != NULL ? y->value_hex : ""); } static bool amduatd_handle_post_context_frames( int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const amduatd_http_req_t *req) { uint8_t *body = NULL; const char *p = NULL; const char *end = NULL; amduatd_cf_binding_t *bindings = NULL; size_t bindings_len = 0; size_t bindings_cap = 0; amduatd_strbuf_t b; amduat_artifact_t artifact; amduat_reference_t ref; char *ref_hex = NULL; bool ok = false; size_t i; memset(&b, 0, sizeof(b)); memset(&artifact, 0, sizeof(artifact)); memset(&ref, 0, sizeof(ref)); if (store == NULL || cfg == NULL || concepts == NULL || req == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } if (req->content_length == 0) { return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); } if (req->content_length > (256u * 1024u)) { return amduatd_send_json_error(fd, 413, "Payload Too Large", "payload too large"); } body = (uint8_t *)malloc(req->content_length); if (body == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } if (!amduatd_read_exact(fd, body, req->content_length)) { free(body); return false; } p = (const char *)body; end = (const char *)body + req->content_length; if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto cf_cleanup; } for (;;) { const char *key = NULL; size_t key_len = 0; const char *cur = NULL; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto cf_cleanup; } if (key_len == strlen("bindings") && memcmp(key, "bindings", key_len) == 0) { if (!amduatd_json_expect(&p, end, '[')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid bindings"); goto cf_cleanup; } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ']') { p = cur + 1; } else { for (;;) { const char *bk = NULL; size_t bk_len = 0; const char *sv = NULL; size_t sv_len = 0; char *key_text = NULL; char *value_text = NULL; char *value_ref_text = NULL; char *value_enum_text = NULL; amduat_reference_t key_ref; amduat_reference_t value_ref; amduat_reference_t enum_ref; bool have_key = false; bool have_value = false; bool have_value_ref = false; bool have_value_scalar = false; bool have_value_int = false; bool have_value_enum = false; uint64_t value_int = 0; memset(&key_ref, 0, sizeof(key_ref)); memset(&value_ref, 0, sizeof(value_ref)); memset(&enum_ref, 0, sizeof(enum_ref)); if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid binding"); goto cf_cleanup; } for (;;) { cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &bk, &bk_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid binding"); goto cf_cleanup; } if (bk_len == strlen("key") && memcmp(bk, "key", bk_len) == 0) { if (have_key || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &key_text)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid key"); goto cf_cleanup; } have_key = true; } else if (bk_len == strlen("value") && memcmp(bk, "value", bk_len) == 0) { if (have_value || have_value_ref || have_value_scalar || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &value_text)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid value"); goto cf_cleanup; } have_value = true; } else if (bk_len == strlen("value_ref") && memcmp(bk, "value_ref", bk_len) == 0) { if (have_value || have_value_ref || have_value_scalar || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &value_ref_text)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid value_ref"); goto cf_cleanup; } have_value_ref = true; } else if (bk_len == strlen("value_scalar") && memcmp(bk, "value_scalar", bk_len) == 0) { if (have_value || have_value_ref || have_value_scalar || !amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid value_scalar"); goto cf_cleanup; } have_value_scalar = true; for (;;) { const char *sk = NULL; size_t sk_len = 0; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &sk, &sk_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid value_scalar"); goto cf_cleanup; } if (sk_len == strlen("int") && memcmp(sk, "int", sk_len) == 0) { if (have_value_int || have_value_enum || !amduatd_json_parse_u64(&p, end, &value_int)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid int"); goto cf_cleanup; } have_value_int = true; } else if (sk_len == strlen("enum") && memcmp(sk, "enum", sk_len) == 0) { if (have_value_int || have_value_enum || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &value_enum_text)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid enum"); goto cf_cleanup; } have_value_enum = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid value_scalar"); goto cf_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } } } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid binding"); goto cf_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } } if (!have_key || (!have_value && !have_value_ref && !have_value_scalar)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing binding fields"); free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); goto cf_cleanup; } if (amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, key_text, strlen(key_text), &key_ref) != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid key"); free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); goto cf_cleanup; } if (have_value || have_value_ref) { const char *vtext = have_value ? value_text : value_ref_text; if (amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, vtext, strlen(vtext), &value_ref) != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid value"); free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); amduat_reference_free(&key_ref); goto cf_cleanup; } } else if (have_value_scalar) { if (have_value_enum) { if (amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, value_enum_text, strlen(value_enum_text), &enum_ref) != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid enum"); free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); amduat_reference_free(&key_ref); goto cf_cleanup; } } else if (!have_value_int) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid value_scalar"); free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); amduat_reference_free(&key_ref); goto cf_cleanup; } } if (bindings_len == bindings_cap) { size_t next_cap = bindings_cap != 0 ? bindings_cap * 2u : 4u; amduatd_cf_binding_t *next = (amduatd_cf_binding_t *)realloc(bindings, next_cap * sizeof(*bindings)); if (next == NULL) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); free(key_text); free(value_text); amduat_reference_free(&key_ref); amduat_reference_free(&value_ref); goto cf_cleanup; } bindings = next; bindings_cap = next_cap; } { char *key_hex = NULL; char *value_hex = NULL; if (!amduat_asl_ref_encode_hex(key_ref, &key_hex)) { free(key_hex); free(value_hex); free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); amduat_reference_free(&key_ref); amduat_reference_free(&value_ref); amduat_reference_free(&enum_ref); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto cf_cleanup; } if (have_value || have_value_ref) { if (!amduat_asl_ref_encode_hex(value_ref, &value_hex)) { free(key_hex); free(value_hex); free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); amduat_reference_free(&key_ref); amduat_reference_free(&value_ref); amduat_reference_free(&enum_ref); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto cf_cleanup; } bindings[bindings_len].value_kind = 0; bindings[bindings_len].value_hex = value_hex; } else if (have_value_int) { bindings[bindings_len].value_kind = 1; bindings[bindings_len].value_int = value_int; } else if (have_value_enum) { if (!amduat_asl_ref_encode_hex(enum_ref, &value_hex)) { free(key_hex); free(value_hex); free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); amduat_reference_free(&key_ref); amduat_reference_free(&value_ref); amduat_reference_free(&enum_ref); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto cf_cleanup; } bindings[bindings_len].value_kind = 2; bindings[bindings_len].value_hex = value_hex; } else { free(key_hex); free(value_hex); free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); amduat_reference_free(&key_ref); amduat_reference_free(&value_ref); amduat_reference_free(&enum_ref); ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid value_scalar"); goto cf_cleanup; } bindings[bindings_len].key_hex = key_hex; bindings_len++; } free(key_text); free(value_text); free(value_ref_text); free(value_enum_text); amduat_reference_free(&key_ref); amduat_reference_free(&value_ref); amduat_reference_free(&enum_ref); cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == ']') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid bindings"); goto cf_cleanup; } } } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto cf_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } } if (bindings_len == 0) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing bindings"); goto cf_cleanup; } qsort(bindings, bindings_len, sizeof(*bindings), amduatd_cf_binding_cmp); if (!amduatd_strbuf_append_cstr(&b, "{\"bindings\":[")) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto cf_cleanup; } for (i = 0; i < bindings_len; ++i) { if (i != 0) { (void)amduatd_strbuf_append_char(&b, ','); } (void)amduatd_strbuf_append_cstr(&b, "{\"key_ref\":\""); (void)amduatd_strbuf_append_cstr(&b, bindings[i].key_hex); if (bindings[i].value_kind == 0) { (void)amduatd_strbuf_append_cstr(&b, "\",\"value_ref\":\""); (void)amduatd_strbuf_append_cstr(&b, bindings[i].value_hex); (void)amduatd_strbuf_append_cstr(&b, "\"}"); } else if (bindings[i].value_kind == 1) { char tmp[32]; int n = snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)bindings[i].value_int); if (n <= 0 || (size_t)n >= sizeof(tmp)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); goto cf_cleanup; } (void)amduatd_strbuf_append_cstr(&b, "\",\"value_int\":"); (void)amduatd_strbuf_append_cstr(&b, tmp); (void)amduatd_strbuf_append_cstr(&b, "}"); } else if (bindings[i].value_kind == 2) { (void)amduatd_strbuf_append_cstr(&b, "\",\"value_enum_ref\":\""); (void)amduatd_strbuf_append_cstr(&b, bindings[i].value_hex); (void)amduatd_strbuf_append_cstr(&b, "\"}"); } else { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); goto cf_cleanup; } } (void)amduatd_strbuf_append_cstr(&b, "]}\n"); artifact = amduat_artifact(amduat_octets(b.data, b.len)); if (amduat_asl_store_put(store, artifact, &ref) != AMDUAT_ASL_STORE_OK) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); goto cf_cleanup; } if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto cf_cleanup; } { char json[2048]; int n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", ref_hex); free(ref_hex); if (n <= 0 || (size_t)n >= sizeof(json)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); goto cf_cleanup; } ok = amduatd_http_send_json(fd, 200, "OK", json, false); } cf_cleanup: free(body); for (i = 0; i < bindings_len; ++i) { amduatd_cf_binding_free(&bindings[i]); } free(bindings); amduatd_strbuf_free(&b); amduat_reference_free(&ref); return ok; } static bool amduatd_path_extract_name(const char *path, const char *prefix, char *out, size_t cap) { size_t plen; const char *p; size_t len; if (out != NULL && cap != 0) { out[0] = '\0'; } if (path == NULL || prefix == NULL || out == NULL || cap == 0) { return false; } plen = strlen(prefix); if (strncmp(path, prefix, plen) != 0) { return false; } p = path + plen; if (*p == '\0') { return false; } len = strlen(p); if (len >= cap) { len = cap - 1; } memcpy(out, p, len); out[len] = '\0'; return true; } static bool amduatd_handle_post_concept_publish(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const char *name, const amduatd_http_req_t *req) { uint8_t *body = NULL; const char *p = NULL; const char *end = NULL; amduat_reference_t target_ref; amduat_reference_t concept_ref; amduat_reference_t edge_ref; bool have_ref = false; bool ok = false; amduat_octets_t actor = amduat_octets(NULL, 0u); char *scoped_name = NULL; memset(&target_ref, 0, sizeof(target_ref)); memset(&concept_ref, 0, sizeof(concept_ref)); memset(&edge_ref, 0, sizeof(edge_ref)); if (store == NULL || cfg == NULL || concepts == NULL || name == NULL || req == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } if (req->has_actor) { actor = req->actor; } if (!amduatd_scope_name_cstr(dcfg, name, &scoped_name)) { return amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); } amduatd_log_scoped_name(dcfg, name, scoped_name); if (req->content_length == 0) { free(scoped_name); return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); } if (req->content_length > (256u * 1024u)) { free(scoped_name); return amduatd_send_json_error(fd, 413, "Payload Too Large", "payload too large"); } if (!amduatd_concepts_lookup_alias(store, cfg, concepts, scoped_name, &concept_ref)) { free(scoped_name); return amduatd_send_json_error(fd, 404, "Not Found", "unknown concept"); } free(scoped_name); body = (uint8_t *)malloc(req->content_length); if (body == NULL) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto publish_cleanup; } if (!amduatd_read_exact(fd, body, req->content_length)) { free(body); amduat_reference_free(&concept_ref); return false; } p = (const char *)body; end = (const char *)body + req->content_length; if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto publish_cleanup; } for (;;) { const char *key = NULL; size_t key_len = 0; const char *sv = NULL; size_t sv_len = 0; const char *cur = NULL; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto publish_cleanup; } if (key_len == strlen("ref") && memcmp(key, "ref", key_len) == 0) { if (have_ref || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_decode_ref_hex_str(sv, sv_len, &target_ref)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid ref"); goto publish_cleanup; } have_ref = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto publish_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto publish_cleanup; } if (!have_ref) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing ref"); goto publish_cleanup; } if (!amduatd_concepts_put_edge(store, concepts, concept_ref, target_ref, concepts->rel_materializes_ref, actor, &edge_ref)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); goto publish_cleanup; } ok = amduatd_http_send_json(fd, 200, "OK", "{\"ok\":true}\n", false); publish_cleanup: free(body); amduat_reference_free(&target_ref); amduat_reference_free(&concept_ref); amduat_reference_free(&edge_ref); return ok; } static bool amduatd_handle_get_resolve(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const char *name) { amduat_reference_t concept_ref; amduat_reference_t latest_ref; char *hex = NULL; char json[2048]; int n; char *scoped_name = NULL; memset(&concept_ref, 0, sizeof(concept_ref)); memset(&latest_ref, 0, sizeof(latest_ref)); if (store == NULL || cfg == NULL || concepts == NULL || name == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } if (!amduatd_scope_name_cstr(dcfg, name, &scoped_name)) { return amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); } amduatd_log_scoped_name(dcfg, name, scoped_name); if (!amduatd_concepts_lookup_alias(store, cfg, concepts, scoped_name, &concept_ref)) { free(scoped_name); return amduatd_send_json_error(fd, 404, "Not Found", "unknown concept"); } free(scoped_name); if (!amduatd_concepts_resolve_latest(store, concepts, concept_ref, &latest_ref)) { amduat_reference_free(&concept_ref); return amduatd_send_json_error(fd, 404, "Not Found", "no versions"); } amduat_reference_free(&concept_ref); if (!amduat_asl_ref_encode_hex(latest_ref, &hex)) { amduat_reference_free(&latest_ref); return amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); } amduat_reference_free(&latest_ref); n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", hex); free(hex); if (n <= 0 || (size_t)n >= sizeof(json)) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); } return amduatd_http_send_json(fd, 200, "OK", json, false); } static bool amduatd_handle_post_capabilities( int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const amduatd_cap_state_t *cap, const amduatd_http_req_t *req) { uint8_t *body = NULL; const char *p = NULL; const char *end = NULL; bool have_kind = false; bool have_target = false; bool have_expiry = false; char *kind = NULL; uint64_t expiry_seconds = 0u; amduatd_cap_token_t token; bool target_have_ref = false; bool target_have_name = false; bool target_have_program_ref = false; bool target_have_inputs = false; bool target_have_params_ref = false; size_t input_refs_cap = 0u; uint8_t *token_bytes = NULL; size_t token_len = 0u; char *token_b64 = NULL; char *token_text = NULL; char token_hash[9]; char json[4096]; bool ok = false; const char *space = NULL; size_t space_len = 0u; time_t now; memset(&token, 0, sizeof(token)); memset(token_hash, 0, sizeof(token_hash)); if (store == NULL || cfg == NULL || concepts == NULL || dcfg == NULL || cap == NULL || req == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } if (!cap->enabled) { return amduatd_send_json_error(fd, 403, "Forbidden", "capabilities disabled"); } if (req->content_length == 0) { return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); } if (req->content_length > (256u * 1024u)) { return amduatd_send_json_error(fd, 413, "Payload Too Large", "payload too large"); } body = (uint8_t *)malloc(req->content_length); if (body == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } if (!amduatd_read_exact(fd, body, req->content_length)) { free(body); return false; } p = (const char *)body; end = (const char *)body + req->content_length; if (!amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto cap_mint_cleanup; } for (;;) { const char *key = NULL; size_t key_len = 0; const char *cur = NULL; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto cap_mint_cleanup; } if (key_len == strlen("kind") && memcmp(key, "kind", key_len) == 0) { const char *sv = NULL; size_t sv_len = 0; if (have_kind || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &kind)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid kind"); goto cap_mint_cleanup; } have_kind = true; } else if (key_len == strlen("expiry_seconds") && memcmp(key, "expiry_seconds", key_len) == 0) { if (have_expiry || !amduatd_json_parse_u64(&p, end, &expiry_seconds)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid expiry_seconds"); goto cap_mint_cleanup; } have_expiry = true; } else if (key_len == strlen("target") && memcmp(key, "target", key_len) == 0) { if (have_target || !amduatd_json_expect(&p, end, '{')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid target"); goto cap_mint_cleanup; } for (;;) { const char *tkey = NULL; size_t tkey_len = 0; const char *sv = NULL; size_t sv_len = 0; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == '}') { p = cur + 1; break; } if (!amduatd_json_parse_string_noesc(&p, end, &tkey, &tkey_len) || !amduatd_json_expect(&p, end, ':')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid target"); goto cap_mint_cleanup; } if (tkey_len == strlen("ref") && memcmp(tkey, "ref", tkey_len) == 0) { amduatd_ref_status_t st; if (target_have_ref || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid ref"); goto cap_mint_cleanup; } st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, &token.artifact_ref); if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid ref"); goto cap_mint_cleanup; } target_have_ref = true; } else if (tkey_len == strlen("name") && memcmp(tkey, "name", tkey_len) == 0) { if (target_have_name || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || !amduatd_copy_json_str(sv, sv_len, &token.pointer_name)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); goto cap_mint_cleanup; } target_have_name = true; } else if (tkey_len == strlen("program_ref") && memcmp(tkey, "program_ref", tkey_len) == 0) { amduatd_ref_status_t st; if (target_have_program_ref || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid program_ref"); goto cap_mint_cleanup; } st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, &token.program_ref); if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid program_ref"); goto cap_mint_cleanup; } target_have_program_ref = true; } else if (tkey_len == strlen("input_refs") && memcmp(tkey, "input_refs", tkey_len) == 0) { if (target_have_inputs || !amduatd_json_expect(&p, end, '[')) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_refs"); goto cap_mint_cleanup; } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ']') { p = cur + 1; target_have_inputs = true; } else { for (;;) { amduat_reference_t ref; amduatd_ref_status_t st; if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_refs"); goto cap_mint_cleanup; } memset(&ref, 0, sizeof(ref)); st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, &ref); if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_ref"); goto cap_mint_cleanup; } if (token.input_refs_len == 0u) { input_refs_cap = 4u; token.input_refs = (amduat_reference_t *)calloc( input_refs_cap, sizeof(*token.input_refs)); if (token.input_refs == NULL) { amduat_reference_free(&ref); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto cap_mint_cleanup; } token.input_refs_len = 0u; } else if (token.input_refs_len == input_refs_cap) { size_t next_cap = input_refs_cap * 2u; amduat_reference_t *next = (amduat_reference_t *)realloc( token.input_refs, next_cap * sizeof(*token.input_refs)); if (next == NULL) { amduat_reference_free(&ref); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto cap_mint_cleanup; } token.input_refs = next; input_refs_cap = next_cap; } token.input_refs[token.input_refs_len++] = ref; target_have_inputs = true; cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == ']') { p = cur + 1; target_have_inputs = true; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_refs"); goto cap_mint_cleanup; } } } else if (tkey_len == strlen("params_ref") && memcmp(tkey, "params_ref", tkey_len) == 0) { amduatd_ref_status_t st; if (target_have_params_ref || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid params_ref"); goto cap_mint_cleanup; } st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv, sv_len, &token.params_ref); if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid params_ref"); goto cap_mint_cleanup; } token.has_params_ref = true; target_have_params_ref = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid target"); goto cap_mint_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid target"); goto cap_mint_cleanup; } have_target = true; } else { if (!amduatd_json_skip_value(&p, end, 0)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto cap_mint_cleanup; } } cur = amduatd_json_skip_ws(p, end); if (cur < end && *cur == ',') { p = cur + 1; continue; } if (cur < end && *cur == '}') { p = cur + 1; break; } ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); goto cap_mint_cleanup; } if (!have_kind || !have_target || !have_expiry) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing fields"); goto cap_mint_cleanup; } if (expiry_seconds == 0u || expiry_seconds > AMDUATD_CAP_MAX_EXPIRY_SECONDS) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "expiry_seconds out of range"); goto cap_mint_cleanup; } now = time(NULL); if (now < 0 || expiry_seconds > (uint64_t)(UINT64_MAX - (uint64_t)now)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "expiry_seconds out of range"); goto cap_mint_cleanup; } token.expiry_unix = (uint64_t)now + expiry_seconds; if (strcmp(kind, "artifact_ref") == 0) { token.kind = AMDUATD_CAP_KIND_ARTIFACT_REF; if (token.artifact_ref.hash_id == 0 || token.artifact_ref.digest.data == NULL) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing ref"); goto cap_mint_cleanup; } } else if (strcmp(kind, "pointer_name") == 0) { token.kind = AMDUATD_CAP_KIND_POINTER_NAME; if (token.pointer_name == NULL || !amduatd_pointer_name_scoped_for_space(dcfg, token.pointer_name)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); goto cap_mint_cleanup; } } else if (strcmp(kind, "pel_view") == 0) { token.kind = AMDUATD_CAP_KIND_PEL_VIEW; if (token.program_ref.hash_id == 0 || token.program_ref.digest.data == NULL) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing program_ref"); goto cap_mint_cleanup; } if (!target_have_inputs) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing input_refs"); goto cap_mint_cleanup; } } else { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid kind"); goto cap_mint_cleanup; } if (dcfg->space_enabled && dcfg->space_id != NULL) { space = dcfg->space_id; space_len = strlen(space); token.space = (char *)malloc(space_len + 1u); if (token.space == NULL) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto cap_mint_cleanup; } memcpy(token.space, space, space_len); token.space[space_len] = '\0'; token.space_len = space_len; } if (!amduatd_cap_build_token(&token, cap->hmac_key, cap->hmac_key_len, &token_bytes, &token_len)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "token encode failed"); goto cap_mint_cleanup; } if (!amduatd_b64url_encode(token_bytes, token_len, &token_b64)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "token encode failed"); goto cap_mint_cleanup; } token_text = (char *)malloc(strlen(token_b64) + 5u); if (token_text == NULL) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto cap_mint_cleanup; } snprintf(token_text, strlen(token_b64) + 5u, "cap_%s", token_b64); amduatd_cap_token_hash_prefix(token_bytes, token_len, token_hash); { char uid_buf[32]; if (req->has_uid) { snprintf(uid_buf, sizeof(uid_buf), "%u", (unsigned int)req->uid); } else { strncpy(uid_buf, "unknown", sizeof(uid_buf) - 1u); uid_buf[sizeof(uid_buf) - 1u] = '\0'; } amduat_log(AMDUAT_LOG_INFO, "cap mint uid=%s kind=%s expiry=%llu space=%s token=%s", uid_buf, amduatd_cap_kind_name(token.kind), (unsigned long long)token.expiry_unix, token.space_len != 0u ? token.space : "none", token_hash); } { int n = snprintf(json, sizeof(json), "{\"token\":\"%s\"}\n", token_text); if (n <= 0 || (size_t)n >= sizeof(json)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); goto cap_mint_cleanup; } ok = amduatd_http_send_json(fd, 200, "OK", json, false); } cap_mint_cleanup: free(body); free(kind); free(token_bytes); free(token_b64); free(token_text); amduatd_cap_token_free(&token); return ok; } static bool amduatd_handle_get_cap_resolve( int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const amduatd_cap_state_t *cap, const char *root_path, const amduatd_http_req_t *req) { const char *cap_header = NULL; uint8_t *token_bytes = NULL; size_t token_len = 0u; amduatd_cap_token_t token; const char *reason = "invalid"; char token_hash[9]; bool ok = false; memset(&token, 0, sizeof(token)); memset(token_hash, 0, sizeof(token_hash)); if (store == NULL || cfg == NULL || concepts == NULL || dcfg == NULL || cap == NULL || req == NULL || root_path == NULL) { return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } (void)cfg; (void)concepts; if (!cap->enabled || !cap->reads_enabled) { return amduatd_send_json_error(fd, 403, "Forbidden", "cap reads disabled"); } cap_header = req->x_capability; if (cap_header == NULL || cap_header[0] == '\0') { amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=missing token=none"); return amduatd_send_json_error(fd, 401, "Unauthorized", "missing capability"); } if (strncmp(cap_header, "cap_", 4u) != 0) { amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=bad-prefix token=none"); return amduatd_send_json_error(fd, 401, "Unauthorized", "invalid capability"); } if (!amduatd_b64url_decode(cap_header + 4u, &token_bytes, &token_len)) { amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=bad-base64 token=none"); return amduatd_send_json_error(fd, 401, "Unauthorized", "invalid capability"); } amduatd_cap_token_hash_prefix(token_bytes, token_len, token_hash); if (!amduatd_cap_verify_token(cap, dcfg, token_bytes, token_len, &token, &reason)) { amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=%s kind=%s space=%s token=%s", reason, token.kind != 0u ? amduatd_cap_kind_name(token.kind) : "unknown", token.space_len != 0u && token.space != NULL ? token.space : "none", token_hash); free(token_bytes); amduatd_cap_token_free(&token); return amduatd_send_json_error(fd, 403, "Forbidden", "invalid capability"); } amduat_log(AMDUAT_LOG_INFO, "cap use ok kind=%s space=%s token=%s", amduatd_cap_kind_name(token.kind), token.space_len != 0u ? token.space : "none", token_hash); if (token.kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { char *ref_hex = NULL; char path[1024]; if (!amduat_asl_ref_encode_hex(token.artifact_ref, &ref_hex)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto cap_resolve_cleanup; } if (snprintf(path, sizeof(path), "/v1/artifacts/%s", ref_hex) <= 0 || strlen(path) >= sizeof(path)) { free(ref_hex); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); goto cap_resolve_cleanup; } ok = amduatd_handle_get_artifact(fd, store, req, path, false); free(ref_hex); goto cap_resolve_cleanup; } else if (token.kind == AMDUATD_CAP_KIND_POINTER_NAME) { amduat_asl_pointer_store_t pointer_store; amduat_reference_t ref; bool exists = false; char format[32]; memset(&ref, 0, sizeof(ref)); if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "pointer store error"); goto cap_resolve_cleanup; } if (amduat_asl_pointer_get(&pointer_store, token.pointer_name, &exists, &ref) != AMDUAT_ASL_POINTER_OK) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "pointer read failed"); goto cap_resolve_cleanup; } if (!exists) { ok = amduatd_send_json_error(fd, 404, "Not Found", "not found"); goto cap_resolve_cleanup; } memset(format, 0, sizeof(format)); if (amduatd_query_param(req->path, "format", format, sizeof(format)) != NULL && strcmp(format, "artifact") == 0) { char *ref_hex = NULL; char path[1024]; if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); amduat_reference_free(&ref); goto cap_resolve_cleanup; } if (snprintf(path, sizeof(path), "/v1/artifacts/%s", ref_hex) <= 0 || strlen(path) >= sizeof(path)) { free(ref_hex); amduat_reference_free(&ref); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); goto cap_resolve_cleanup; } ok = amduatd_handle_get_artifact(fd, store, req, path, false); free(ref_hex); amduat_reference_free(&ref); goto cap_resolve_cleanup; } else { char *ref_hex = NULL; char json_out[2048]; int n; if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { amduat_reference_free(&ref); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto cap_resolve_cleanup; } n = snprintf(json_out, sizeof(json_out), "{\"ref\":\"%s\"}\n", ref_hex); free(ref_hex); amduat_reference_free(&ref); if (n <= 0 || (size_t)n >= sizeof(json_out)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); goto cap_resolve_cleanup; } ok = amduatd_http_send_json(fd, 200, "OK", json_out, false); goto cap_resolve_cleanup; } } else if (token.kind == AMDUATD_CAP_KIND_PEL_VIEW) { amduat_pel_run_result_t run_result; char *result_hex = NULL; const char *status = "UNKNOWN"; amduatd_strbuf_t resp; size_t i; memset(&run_result, 0, sizeof(run_result)); memset(&resp, 0, sizeof(resp)); if (!amduat_pel_surf_run_with_result( store, amduat_pel_program_dag_scheme_ref(), token.program_ref, token.input_refs, token.input_refs_len, token.has_params_ref, token.params_ref, &run_result)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "pel run failed"); goto cap_view_cleanup; } if (!run_result.has_result_value) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "pel run failed"); goto cap_view_cleanup; } status = amduat_format_pel_status_name( run_result.result_value.core_result.status); if (!amduat_asl_ref_encode_hex(run_result.result_ref, &result_hex)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto cap_view_cleanup; } if (!amduatd_strbuf_append_cstr(&resp, "{\"result_ref\":\"") || !amduatd_strbuf_append_cstr(&resp, result_hex) || !amduatd_strbuf_append_cstr(&resp, "\",\"output_refs\":[")) { free(result_hex); ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto cap_view_cleanup; } free(result_hex); for (i = 0; i < run_result.output_refs_len; ++i) { char *out_hex = NULL; if (!amduat_asl_ref_encode_hex(run_result.output_refs[i], &out_hex)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "encode error"); goto cap_view_cleanup; } if (i != 0) { (void)amduatd_strbuf_append_char(&resp, ','); } (void)amduatd_strbuf_append_cstr(&resp, "\""); (void)amduatd_strbuf_append_cstr(&resp, out_hex); (void)amduatd_strbuf_append_cstr(&resp, "\""); free(out_hex); } if (!amduatd_strbuf_append_cstr(&resp, "],\"status\":\"") || !amduatd_strbuf_append_cstr(&resp, status) || !amduatd_strbuf_append_cstr(&resp, "\"}\n")) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); goto cap_view_cleanup; } ok = amduatd_http_send_json(fd, 200, "OK", resp.data, false); goto cap_view_cleanup; cap_view_cleanup: amduatd_strbuf_free(&resp); if (run_result.has_result_value) { amduat_enc_pel1_result_free(&run_result.result_value); } if (run_result.output_refs != NULL) { amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len); } amduat_pel_surf_free_ref(&run_result.result_ref); goto cap_resolve_cleanup; } else { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid capability"); } cap_resolve_cleanup: free(token_bytes); amduatd_cap_token_free(&token); return ok; } static bool amduatd_handle_conn(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, amduat_reference_t api_contract_ref, amduat_reference_t ui_ref, amduatd_concepts_t *concepts, const amduatd_cfg_t *dcfg, const amduat_fed_coord_t *coord, const amduatd_allowlist_t *allowlist, const amduatd_cap_state_t *cap_state, const char *root_path) { amduatd_http_req_t req; char no_query[1024]; bool ok = false; amduat_octets_t actor = amduat_octets(NULL, 0u); bool has_actor = false; bool has_uid = false; uid_t uid = 0; if (!amduatd_http_parse_request(fd, &req)) { return false; } if (!amduatd_get_peer_actor(fd, &actor, &has_actor, &has_uid, &uid)) { actor = amduat_octets(NULL, 0u); has_actor = false; has_uid = false; } req.has_actor = has_actor; req.actor = actor; req.has_uid = has_uid; req.uid = uid; amduatd_path_without_query(req.path, no_query, sizeof(no_query)); if (!(strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/cap/resolve") == 0) && !amduatd_actor_allowed(allowlist, has_uid, uid)) { amduat_log(AMDUAT_LOG_DEBUG, "reject uid=%u (not in allowlist)", (unsigned int)uid); ok = amduatd_http_send_text(fd, 403, "Forbidden", "forbidden\n", false); goto conn_cleanup; } { amduatd_ctx_t ui_ctx; amduatd_http_resp_t ui_resp; ui_ctx.store = store; ui_ctx.ui_ref = ui_ref; ui_resp.fd = fd; ui_resp.ok = false; if (amduatd_ui_can_handle(&req)) { if (amduatd_ui_handle(&ui_ctx, &req, &ui_resp)) { ok = ui_resp.ok; goto conn_cleanup; } } } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/meta") == 0) { ok = amduatd_handle_meta(fd, cfg, api_contract_ref, false); goto conn_cleanup; } if (strcmp(req.method, "HEAD") == 0 && strcmp(no_query, "/v1/meta") == 0) { ok = amduatd_handle_meta(fd, cfg, api_contract_ref, true); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/contract") == 0) { ok = amduatd_handle_get_contract(fd, store, &req, api_contract_ref); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/fed/records") == 0) { ok = amduatd_handle_get_fed_records(fd, store, &req); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/fed/status") == 0) { ok = amduatd_handle_get_fed_status(fd, coord, &req); goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/artifacts") == 0) { ok = amduatd_handle_post_artifacts(fd, store, &req); goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/capabilities") == 0) { ok = amduatd_handle_post_capabilities(fd, store, cfg, concepts, dcfg, cap_state, &req); goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/run") == 0) { ok = amduatd_handle_post_pel_run(fd, store, cfg, concepts, dcfg, &req); goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/programs") == 0) { ok = amduatd_handle_post_pel_programs(fd, store, &req); goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/context_frames") == 0) { ok = amduatd_handle_post_context_frames(fd, store, cfg, concepts, dcfg, &req); goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/concepts") == 0) { ok = amduatd_handle_post_concepts(fd, store, cfg, concepts, dcfg, &req); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/concepts") == 0) { ok = amduatd_handle_get_concepts(fd, store, concepts, dcfg); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/relations") == 0) { ok = amduatd_handle_get_relations(fd, concepts); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/fed/artifacts/", 18) == 0) { ok = amduatd_handle_get_fed_artifact(fd, store, &req, req.path); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/concepts/", 13) == 0) { const char *name = no_query + 13; if (name[0] == '\0') { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing name"); goto conn_cleanup; } ok = amduatd_handle_get_concept(fd, store, cfg, concepts, dcfg, name); goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strncmp(no_query, "/v1/concepts/", 13) == 0 && strstr(no_query, "/publish") != NULL) { char name[256]; char *slash; if (!amduatd_path_extract_name(no_query, "/v1/concepts/", name, sizeof(name))) { return amduatd_send_json_error(fd, 400, "Bad Request", "invalid path"); } slash = strstr(name, "/publish"); if (slash == NULL || strcmp(slash, "/publish") != 0) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid path"); goto conn_cleanup; } *slash = '\0'; ok = amduatd_handle_post_concept_publish(fd, store, cfg, concepts, dcfg, name, &req); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/resolve/", 12) == 0) { const char *name = no_query + 12; if (name[0] == '\0') { ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing name"); goto conn_cleanup; } ok = amduatd_handle_get_resolve(fd, store, cfg, concepts, dcfg, name); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/cap/resolve") == 0) { ok = amduatd_handle_get_cap_resolve(fd, store, cfg, concepts, dcfg, cap_state, root_path, &req); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/artifacts/", 14) == 0) { ok = amduatd_handle_get_artifact(fd, store, &req, req.path, false); goto conn_cleanup; } if (strcmp(req.method, "HEAD") == 0 && strncmp(no_query, "/v1/artifacts/", 14) == 0) { ok = amduatd_handle_head_artifact(fd, store, &req, req.path); goto conn_cleanup; } ok = amduatd_http_send_not_found(fd, &req); conn_cleanup: free((void *)req.actor.data); return ok; } static void amduatd_print_usage(FILE *stream) { fprintf(stream, "usage:\n" " amduatd [--root PATH] [--sock PATH]\n" " [--space SPACE_ID] [--migrate-unscoped-edges]\n" " [--allow-uid UID] [--allow-user NAME]\n" " [--enable-cap-reads]\n" "\n" "defaults:\n" " --root %s\n" " --sock %s\n", AMDUATD_DEFAULT_ROOT, AMDUATD_DEFAULT_SOCK); } int main(int argc, char **argv) { const char *root = AMDUATD_DEFAULT_ROOT; const char *sock_path = AMDUATD_DEFAULT_SOCK; const char *space_id = NULL; bool migrate_unscoped_edges = false; bool enable_cap_reads = false; amduatd_cfg_t dcfg; amduatd_cap_state_t cap_state; amduat_asl_store_fs_config_t cfg; amduat_asl_store_fs_t fs; amduat_asl_store_t store; amduat_reference_t api_contract_ref; amduat_reference_t ui_ref; amduatd_concepts_t concepts; amduat_fed_transport_stub_t fed_stub; amduat_fed_coord_cfg_t fed_cfg; amduat_fed_coord_t *fed_coord = NULL; amduatd_allowlist_t allowlist; int i; int sfd = -1; uint64_t last_tick_ms = 0; memset(&api_contract_ref, 0, sizeof(api_contract_ref)); memset(&ui_ref, 0, sizeof(ui_ref)); memset(&concepts, 0, sizeof(concepts)); memset(&allowlist, 0, sizeof(allowlist)); memset(&dcfg, 0, sizeof(dcfg)); memset(&cap_state, 0, sizeof(cap_state)); for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "--root") == 0) { if (i + 1 >= argc) { fprintf(stderr, "error: --root requires a path\n"); return 2; } root = argv[++i]; } else if (strcmp(argv[i], "--sock") == 0) { if (i + 1 >= argc) { fprintf(stderr, "error: --sock requires a path\n"); return 2; } sock_path = argv[++i]; } else if (strcmp(argv[i], "--space") == 0) { if (i + 1 >= argc) { fprintf(stderr, "error: --space requires a value\n"); return 2; } space_id = argv[++i]; if (!amduatd_space_id_valid(space_id)) { fprintf(stderr, "error: invalid --space\n"); return 2; } } else if (strcmp(argv[i], "--migrate-unscoped-edges") == 0) { migrate_unscoped_edges = true; } else if (strcmp(argv[i], "--allow-uid") == 0) { char *endp = NULL; unsigned long uid_val; if (i + 1 >= argc) { fprintf(stderr, "error: --allow-uid requires a value\n"); return 2; } uid_val = strtoul(argv[++i], &endp, 10); if (endp == argv[i] || *endp != '\0' || uid_val > UINT32_MAX) { fprintf(stderr, "error: invalid --allow-uid\n"); return 2; } if (!amduatd_allowlist_add(&allowlist, (uid_t)uid_val)) { fprintf(stderr, "error: failed to add allow-uid\n"); return 2; } } else if (strcmp(argv[i], "--allow-user") == 0) { struct passwd *pwd; if (i + 1 >= argc) { fprintf(stderr, "error: --allow-user requires a value\n"); return 2; } pwd = getpwnam(argv[++i]); if (pwd == NULL) { fprintf(stderr, "error: unknown user for --allow-user\n"); return 2; } if (!amduatd_allowlist_add(&allowlist, pwd->pw_uid)) { fprintf(stderr, "error: failed to add allow-user\n"); return 2; } } else if (strcmp(argv[i], "--enable-cap-reads") == 0) { enable_cap_reads = true; } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { amduatd_print_usage(stdout); return 0; } else { fprintf(stderr, "error: unknown option: %s\n", argv[i]); return 2; } } dcfg.space_id = space_id; dcfg.space_enabled = (space_id != NULL); dcfg.migrate_unscoped_edges = migrate_unscoped_edges; memset(&cfg, 0, sizeof(cfg)); if (!amduat_asl_store_fs_load_config(root, &cfg)) { fprintf(stderr, "error: failed to load store config: %s\n", root); return 8; } memset(&fs, 0, sizeof(fs)); if (!amduat_asl_store_fs_init(&fs, cfg.config, root)) { fprintf(stderr, "error: failed to initialize store: %s\n", root); return 8; } amduat_asl_store_init(&store, cfg.config, amduat_asl_store_fs_ops(), &fs); if (!amduatd_cap_init(root, &cap_state)) { amduat_log(AMDUAT_LOG_WARN, "capabilities unavailable"); } cap_state.reads_enabled = enable_cap_reads; if (!amduatd_seed_api_contract(&store, &cfg, &api_contract_ref)) { fprintf(stderr, "error: failed to seed api contract\n"); return 8; } #if AMDUATD_ENABLE_UI if (!amduatd_seed_ui_html(&store, &cfg, &ui_ref)) { fprintf(stderr, "error: failed to seed ui html\n"); return 8; } #endif if (!amduatd_concepts_init(&concepts, &store, &cfg, &dcfg, root)) { fprintf(stderr, "error: failed to init concept edges\n"); return 8; } #if AMDUATD_ENABLE_UI if (!amduatd_seed_ms_ui_state(&store, &cfg, &concepts, &dcfg)) { fprintf(stderr, "error: failed to seed ms ui state\n"); return 8; } #endif amduat_fed_transport_stub_init(&fed_stub); memset(&fed_cfg, 0, sizeof(fed_cfg)); fed_cfg.local_domain_id = 0; fed_cfg.authoritative_store = &store; fed_cfg.cache_store = NULL; fed_cfg.transport = amduat_fed_transport_stub_ops(&fed_stub); if (amduat_fed_coord_open(&fed_cfg, &fed_coord) != AMDUAT_FED_COORD_OK) { fed_coord = NULL; } signal(SIGINT, amduatd_on_signal); signal(SIGTERM, amduatd_on_signal); sfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sfd < 0) { perror("socket"); return 8; } { struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; if (strlen(sock_path) >= sizeof(addr.sun_path)) { fprintf(stderr, "error: socket path too long\n"); close(sfd); return 2; } strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1); (void)unlink(sock_path); if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); close(sfd); return 8; } (void)chmod(sock_path, 0660); if (listen(sfd, 16) < 0) { perror("listen"); (void)unlink(sock_path); close(sfd); return 8; } } fprintf(stderr, "amduatd: root=%s sock=%s\n", root, sock_path); while (!amduatd_should_exit) { fd_set rfds; struct timeval tv; struct timeval *tvp = NULL; int rc; if (fed_coord != NULL) { uint64_t now_ms = amduatd_now_ms(); uint64_t due_ms = last_tick_ms == 0 ? now_ms : last_tick_ms + AMDUATD_FED_TICK_MS; uint64_t wait_ms = due_ms <= now_ms ? 0 : (due_ms - now_ms); tv.tv_sec = (time_t)(wait_ms / 1000u); tv.tv_usec = (suseconds_t)((wait_ms % 1000u) * 1000u); tvp = &tv; } FD_ZERO(&rfds); FD_SET(sfd, &rfds); rc = select(sfd + 1, &rfds, NULL, NULL, tvp); if (rc < 0) { if (errno == EINTR) { continue; } perror("select"); break; } if (fed_coord != NULL) { uint64_t now_ms = amduatd_now_ms(); if (last_tick_ms == 0 || now_ms - last_tick_ms >= AMDUATD_FED_TICK_MS) { (void)amduat_fed_coord_tick(fed_coord, now_ms); last_tick_ms = now_ms; } } if (rc == 0) { continue; } if (!FD_ISSET(sfd, &rfds)) { continue; } { int cfd = accept(sfd, NULL, NULL); if (cfd < 0) { if (errno == EINTR) { continue; } perror("accept"); break; } (void)amduatd_handle_conn(cfd, &store, &cfg, api_contract_ref, ui_ref, &concepts, &dcfg, fed_coord, &allowlist, &cap_state, root); (void)close(cfd); } } amduat_reference_free(&api_contract_ref); amduat_reference_free(&ui_ref); amduatd_concepts_free(&concepts); if (fed_coord != NULL) { amduat_fed_coord_close(fed_coord); } amduatd_allowlist_free(&allowlist); (void)unlink(sock_path); (void)close(sfd); return 0; }