From eb355613d53ec3428a578d49b86d603c65636dc7 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Mon, 22 Dec 2025 20:14:47 +0100 Subject: [PATCH] Add PEL program authoring endpoint and UI --- README.md | 19 + registry/amduatd-api-contract.v1.json | 3 +- registry/api-contract.jsonl | 2 +- src/amduatd.c | 918 +++++++++++++++++++++++++- 4 files changed, 938 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eec9ef0..ab1a1a4 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,14 @@ Query store meta: curl --unix-socket amduatd.sock http://localhost/v1/meta ``` +Browser UI (use `socat` to forward the Unix socket): + +```sh +socat TCP-LISTEN:8080,fork,reuseaddr UNIX-CONNECT:amduatd.sock +``` + +Then open `http://localhost:8080/v1/ui`. + Discover the store-backed API contract ref: ```sh @@ -84,6 +92,14 @@ curl --unix-socket amduatd.sock -X POST http://localhost/v1/pel/run \ -d '{"program_ref":"","input_refs":[""],"params_ref":""}' ``` +Define a PEL/PROGRAM-DAG/1 program (store-backed): + +```sh +curl --unix-socket amduatd.sock -X POST http://localhost/v1/pel/programs \ + -H 'Content-Type: application/json' \ + -d '{"nodes":[{"id":1,"op":{"name":"pel.bytes.concat","version":1},"inputs":[{"external":{"input_index":0}},{"external":{"input_index":1}}],"params_hex":""}],"roots":[{"node_id":1,"output_index":0}]}' +``` + ## Current endpoints - `GET /v1/meta` → `{store_id, encoding_profile_id, hash_id, api_contract_ref}` @@ -98,6 +114,9 @@ curl --unix-socket amduatd.sock -X POST http://localhost/v1/pel/run \ - `POST /v1/pel/run` - request: `{program_ref, input_refs[], params_ref?, scheme_ref?}` (refs are hex strings; omit `scheme_ref` to use `dag`) - response: `{result_ref, trace_ref?, output_refs[], status}` +- `POST /v1/pel/programs` + - request: authoring JSON for `PEL/PROGRAM-DAG/1` (kernel ops only; `params_hex` is raw hex bytes) + - response: `{program_ref}` ## Notes diff --git a/registry/amduatd-api-contract.v1.json b/registry/amduatd-api-contract.v1.json index d4961e8..d16fd64 100644 --- a/registry/amduatd-api-contract.v1.json +++ b/registry/amduatd-api-contract.v1.json @@ -1,2 +1 @@ -{"contract":"AMDUATD/API/1","base_path":"/v1","endpoints":[{"method":"GET","path":"/v1/meta"},{"method":"HEAD","path":"/v1/meta"},{"method":"GET","path":"/v1/contract"},{"method":"POST","path":"/v1/artifacts"},{"method":"GET","path":"/v1/artifacts/{ref}"},{"method":"HEAD","path":"/v1/artifacts/{ref}"},{"method":"POST","path":"/v1/pel/run"}],"schemas":{"pel_run_request":{"type":"object","required":["program_ref","input_refs"],"properties":{"program_ref":{"type":"string","description":"hex ref"},"input_refs":{"type":"array","items":{"type":"string","description":"hex ref"}},"params_ref":{"type":"string","description":"hex ref"},"scheme_ref":{"type":"string","description":"hex ref or 'dag'"}}},"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"},"output_refs":{"type":"array","items":{"type":"string","description":"hex ref"}},"status":{"type":"string"}}}}} - +{"contract":"AMDUATD/API/1","base_path":"/v1","endpoints":[{"method":"GET","path":"/v1/meta"},{"method":"HEAD","path":"/v1/meta"},{"method":"GET","path":"/v1/contract"},{"method":"POST","path":"/v1/artifacts"},{"method":"GET","path":"/v1/artifacts/{ref}"},{"method":"HEAD","path":"/v1/artifacts/{ref}"},{"method":"POST","path":"/v1/pel/run"},{"method":"POST","path":"/v1/pel/programs"}],"schemas":{"pel_run_request":{"type":"object","required":["program_ref","input_refs"],"properties":{"program_ref":{"type":"string","description":"hex ref"},"input_refs":{"type":"array","items":{"type":"string","description":"hex ref"}},"params_ref":{"type":"string","description":"hex ref"},"scheme_ref":{"type":"string","description":"hex ref or 'dag'"}}},"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"},"output_refs":{"type":"array","items":{"type":"string","description":"hex ref"}},"status":{"type":"string"}}},"pel_program_author_request":{"type":"object","required":["nodes","roots"],"properties":{"nodes":{"type":"array"},"roots":{"type":"array"}}}}} diff --git a/registry/api-contract.jsonl b/registry/api-contract.jsonl index c28b90c..620a134 100644 --- a/registry/api-contract.jsonl +++ b/registry/api-contract.jsonl @@ -1 +1 @@ -{"registry":"AMDUATD/API","contract":"AMDUATD/API/1","handle":"amduat.api.amduatd.contract.v1@1","media_type":"application/json","status":"active","bytes_sha256":"9bc4d4c8cf9512afebfb4b6674c58c93d6f47831e948f1795804a27872556263","notes":"Seeded into the ASL store at amduatd startup; ref is advertised via /v1/meta."} +{"registry":"AMDUATD/API","contract":"AMDUATD/API/1","handle":"amduat.api.amduatd.contract.v1@1","media_type":"application/json","status":"active","bytes_sha256":"7c15cf17844d0b4637f4c164088a260ab898f235643659bc8fd5230a6b5745ee","notes":"Seeded into the ASL store at amduatd startup; ref is advertised via /v1/meta."} diff --git a/src/amduatd.c b/src/amduatd.c index 3932773..bc9dea3 100644 --- a/src/amduatd.c +++ b/src/amduatd.c @@ -8,9 +8,13 @@ #include "amduat/asl/ref_derive.h" #include "amduat/enc/asl1_core_codec.h" #include "amduat/enc/pel1_result.h" +#include "amduat/enc/pel_program_dag.h" #include "amduat/format/pel.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 #include @@ -42,7 +46,8 @@ static const char k_amduatd_contract_v1_json[] = "{\"method\":\"POST\",\"path\":\"/v1/artifacts\"}," "{\"method\":\"GET\",\"path\":\"/v1/artifacts/{ref}\"}," "{\"method\":\"HEAD\",\"path\":\"/v1/artifacts/{ref}\"}," - "{\"method\":\"POST\",\"path\":\"/v1/pel/run\"}" + "{\"method\":\"POST\",\"path\":\"/v1/pel/run\"}," + "{\"method\":\"POST\",\"path\":\"/v1/pel/programs\"}" "]," "\"schemas\":{" "\"pel_run_request\":{" @@ -64,10 +69,159 @@ static const char k_amduatd_contract_v1_json[] = "\"output_refs\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"description\":\"hex ref\"}}," "\"status\":{\"type\":\"string\"}" "}" + "}," + "\"pel_program_author_request\":{" + "\"type\":\"object\"," + "\"required\":[\"nodes\",\"roots\"]," + "\"properties\":{" + "\"nodes\":{\"type\":\"array\"}," + "\"roots\":{\"type\":\"array\"}" + "}" "}" "}" "}\n"; +static const char k_amduatd_ui_html[] = + "\n" + "\n" + "\n" + " \n" + " \n" + " amduatd — PEL Program Builder\n" + " \n" + "\n" + "\n" + "
\n" + "

amduatd — PEL/PROGRAM-DAG/1 builder

\n" + "
POSTs to /v1/pel/programs and /v1/pel/run on this daemon.
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
Program authoring JSON
\n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + "
\n" + "\n" + "
\n" + "
Run
\n" + "
program_ref
\n" + " \" />\n" + "
input_refs (comma-separated hex refs)
\n" + " ,,...\" />\n" + "
params_ref (optional)
\n" + " \" />\n" + "
scheme_ref (optional, default dag)
\n" + " \n" + "
\n" + " \n" + " /v1/contract\n" + " /v1/meta\n" + "
\n" + "
Response
\n" + "
\n"
+    "      
\n" + "
\n" + "
\n" + "\n" + " \n" + "\n" + "\n"; + static volatile sig_atomic_t amduatd_should_exit = 0; static void amduatd_on_signal(int signo) { @@ -546,6 +700,53 @@ static bool amduatd_json_parse_string_noesc(const char **p, 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_skip_string(const char **p, const char *end) { const char *cur; if (p == NULL || *p == NULL) { @@ -1564,6 +1765,713 @@ pel_run_cleanup: 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 || !amduatd_json_parse_u32(&p, end, &id)) { + 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(&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(&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(&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(&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(&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(&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_get_ui(int fd) { + return amduatd_http_send_status(fd, + 200, + "OK", + "text/html; charset=utf-8", + (const uint8_t *)k_amduatd_ui_html, + strlen(k_amduatd_ui_html), + false); +} + static bool amduatd_handle_conn(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, @@ -1577,6 +2485,10 @@ static bool amduatd_handle_conn(int fd, amduatd_path_without_query(req.path, no_query, sizeof(no_query)); + if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/ui") == 0) { + return amduatd_handle_get_ui(fd); + } + if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/meta") == 0) { return amduatd_handle_meta(fd, cfg, api_contract_ref, false); } @@ -1593,6 +2505,10 @@ static bool amduatd_handle_conn(int fd, if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/run") == 0) { return amduatd_handle_post_pel_run(fd, store, &req); } + if (strcmp(req.method, "POST") == 0 && + strcmp(no_query, "/v1/pel/programs") == 0) { + return amduatd_handle_post_pel_programs(fd, store, &req); + } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/artifacts/", 14) == 0) { return amduatd_handle_get_artifact(fd, store, &req, req.path, false);