amduat-api/src/amduatd.c
2025-12-23 09:14:58 +01:00

6213 lines
216 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#define _POSIX_C_SOURCE 200809L
#include "amduat/asl/artifact_io.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/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 "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 <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
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);
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 uint32_t AMDUATD_TGK_EDGE_TYPE = 0u;
typedef struct {
amduat_reference_t *refs;
size_t len;
size_t cap;
} amduatd_ref_list_t;
static void amduatd_ref_list_free(amduatd_ref_list_t *list) {
size_t i;
if (list == NULL) {
return;
}
for (i = 0; i < list->len; ++i) {
amduat_reference_free(&list->refs[i]);
}
free(list->refs);
list->refs = NULL;
list->len = 0;
list->cap = 0;
}
static bool amduatd_ref_list_push(amduatd_ref_list_t *list,
amduat_reference_t ref) {
amduat_reference_t cloned;
amduat_reference_t *next;
if (list == NULL) {
return false;
}
memset(&cloned, 0, sizeof(cloned));
if (!amduat_reference_clone(ref, &cloned)) {
return false;
}
if (list->len == list->cap) {
size_t next_cap = list->cap != 0 ? list->cap * 2u : 64u;
next = (amduat_reference_t *)realloc(list->refs,
next_cap * sizeof(*list->refs));
if (next == NULL) {
amduat_reference_free(&cloned);
return false;
}
list->refs = next;
list->cap = next_cap;
}
list->refs[list->len++] = cloned;
return true;
}
typedef struct {
const char *root_path;
char edges_path[1024];
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;
amduatd_ref_list_t edge_refs;
} 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_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_ref_list_free(&c->edge_refs);
}
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/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\":{"
"\"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\"},"
"\"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\"}"
"}"
"},"
"\"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 const char k_amduatd_ui_html[] =
"<!doctype html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"utf-8\" />\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
" <title>amduatd — Concept editor</title>\n"
" <style>\n"
" :root{\n"
" --bg:#0b1220;--card:#111a2e;--text:#eaf0ff;--muted:#b7c3e6;--border:rgba(255,255,255,.10);\n"
" --shadow:0 10px 30px rgba(0,0,0,.35);--radius:18px;--max:980px;--pad:clamp(16px,3.5vw,28px);\n"
" }\n"
" *{box-sizing:border-box;}\n"
" html,body{min-height:100%;}\n"
" html{background:var(--bg);}\n"
" body{margin:0;min-height:100vh;font-family:\"Avenir Next\",\"Avenir\",\"Trebuchet MS\",\"Segoe UI\",sans-serif;color:var(--text);line-height:1.55;"
" background:radial-gradient(900px 400px at 15% 10%,rgba(95,145,255,.35),transparent 60%),"
" radial-gradient(800px 450px at 85% 20%,rgba(255,140,92,.25),transparent 60%),"
" radial-gradient(700px 500px at 50% 95%,rgba(56,220,181,.18),transparent 60%),var(--bg);}\n"
" a{color:inherit;text-decoration:none;}\n"
" a:hover{text-decoration:underline;text-underline-offset:4px;}\n"
" .wrap{max-width:var(--max);margin:0 auto;padding:26px var(--pad) 70px;min-height:100vh;}\n"
" header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 0 22px;}\n"
" .brand{display:flex;align-items:center;gap:10px;font-weight:700;letter-spacing:.2px;}\n"
" .logo{width:38px;height:38px;border-radius:12px;border:1px solid var(--border);"
" background:linear-gradient(135deg,rgba(95,145,255,.9),rgba(56,220,181,.8));box-shadow:var(--shadow);}\n"
" nav{display:flex;gap:14px;flex-wrap:wrap;color:var(--muted);font-size:14px;}\n"
" nav a{padding:6px 10px;border-radius:10px;}\n"
" nav a:hover{background:rgba(255,255,255,.06);text-decoration:none;}\n"
" .hero{border:1px solid var(--border);background:rgba(17,26,46,.72);border-radius:var(--radius);box-shadow:var(--shadow);"
" padding:clamp(22px,4.5vw,42px);backdrop-filter:blur(10px);}\n"
" h1{margin:0 0 10px;font-size:clamp(28px,3.6vw,40px);line-height:1.1;letter-spacing:-0.6px;}\n"
" .lead{margin:0 0 18px;color:var(--muted);font-size:clamp(14px,2vw,17px);max-width:70ch;}\n"
" .cta-row{display:flex;flex-wrap:wrap;gap:12px;margin-top:10px;}\n"
" .grid{display:grid;grid-template-columns:repeat(12,1fr);gap:14px;margin-top:16px;}\n"
" .card{grid-column:span 12;border:1px solid var(--border);background:rgba(17,26,46,.62);border-radius:16px;padding:16px;"
" box-shadow:0 8px 22px rgba(0,0,0,.25);backdrop-filter:blur(10px);}\n"
" .card h2{margin:2px 0 6px;font-size:16px;letter-spacing:.1px;}\n"
" .muted{color:var(--muted);font-size:13px;}\n"
" .span-7{grid-column:span 12;}\n"
" .span-5{grid-column:span 12;}\n"
" .span-6{grid-column:span 12;}\n"
" .stack{display:grid;gap:14px;}\n"
" @media (min-width: 980px){.span-7{grid-column:span 7;}.span-5{grid-column:span 5;}.span-6{grid-column:span 6;}}\n"
" textarea,input,select{width:100%;box-sizing:border-box;border-radius:12px;padding:10px;border:1px solid var(--border);"
" background:rgba(0,0,0,.12);color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
" font-size:12.5px;}\n"
" textarea{min-height:420px;resize:vertical;}\n"
" .btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;padding:10px 14px;border-radius:12px;border:1px solid var(--border);"
" background:rgba(255,255,255,.06);color:var(--text);font-weight:600;font-size:14px;cursor:pointer;}\n"
" .btn:hover{background:rgba(255,255,255,.10);}\n"
" .btn.primary{background:linear-gradient(135deg,rgba(95,145,255,.95),rgba(56,220,181,.85));border-color:rgba(255,255,255,.18);}\n"
" .btn.primary:hover{filter:brightness(1.05);}\n"
" .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;}\n"
" .row > *{flex:1 1 auto;}\n"
" .row .btn{flex:0 0 auto;}\n"
" pre{white-space:pre-wrap;word-break:break-word;margin:0;padding:10px;border-radius:12px;border:1px solid var(--border);"
" background:rgba(0,0,0,.2);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
" font-size:12.5px;min-height:120px;color:var(--text);}\n"
" footer{margin-top:26px;color:var(--muted);font-size:13px;display:flex;gap:10px;justify-content:space-between;flex-wrap:wrap;}\n"
" .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <div class=\"wrap\">\n"
" <header>\n"
" <div class=\"brand\" aria-label=\"Site brand\">\n"
" <div class=\"logo\" aria-hidden=\"true\"></div>\n"
" <span>amduatd</span>\n"
" </div>\n"
" <nav aria-label=\"Primary\">\n"
" <a href=\"#editor\">Editor</a>\n"
" <a href=\"#runner\">Run</a>\n"
" <a href=\"#relations\">Relations</a>\n"
" <a href=\"#about\">About</a>\n"
" </nav>\n"
" </header>\n"
" <main>\n"
" <section class=\"hero\" aria-labelledby=\"title\">\n"
" <h1 id=\"title\">Concept editor + PEL runner</h1>\n"
" <p class=\"lead\">Load shows the latest materialized bytes; Save uploads a new artifact and publishes a new version. Use the runner to execute PEL programs against stored artifacts.</p>\n"
" <div class=\"cta-row\">\n"
" <a class=\"btn primary\" href=\"#editor\" role=\"button\">Open editor</a>\n"
" <a class=\"btn\" href=\"#runner\" role=\"button\">Run program</a>\n"
" </div>\n"
" </section>\n"
" <section class=\"grid\" style=\"margin-top:16px;\">\n"
" <div class=\"card span-7\" id=\"editor\">\n"
" <div class=\"row\">\n"
" <input id=\"conceptName\" placeholder=\"concept name (e.g. hello)\" />\n"
" <button class=\"btn\" id=\"btnConceptCreate\" type=\"button\">Create</button>\n"
" <button class=\"btn\" id=\"btnLoad\" type=\"button\">Load</button>\n"
" <button class=\"btn\" id=\"btnSave\" type=\"button\">Save</button>\n"
" </div>\n"
" <div class=\"row\" style=\"margin-top:10px;\">\n"
" <select id=\"mode\">\n"
" <option value=\"text\">bytes: text (utf-8)</option>\n"
" <option value=\"base64\">bytes: base64</option>\n"
" <option value=\"hex\">bytes: hex</option>\n"
" <option value=\"pel_program\">PEL program: JSON</option>\n"
" </select>\n"
" <input id=\"typeTag\" placeholder=\"X-Amduat-Type-Tag (optional)\" />\n"
" <input id=\"latestRef\" placeholder=\"latest_ref\" readonly />\n"
" </div>\n"
" <textarea id=\"editor\" spellcheck=\"false\" placeholder=\"(bytes or PEL program authoring JSON)\"></textarea>\n"
" <div class=\"row\" style=\"margin-top:10px;\">\n"
" <button class=\"btn\" id=\"btnProgramTemplate\" type=\"button\">Insert identity program</button>\n"
" </div>\n"
" <div class=\"row\" style=\"margin-top:10px;\">\n"
" <input id=\"publishRef\" placeholder=\"publish existing ref\" />\n"
" <button class=\"btn\" id=\"btnPublishRef\" type=\"button\">Publish ref</button>\n"
" </div>\n"
" </div>\n"
"\n"
" <div class=\"stack span-5\">\n"
" <div class=\"card\" id=\"runner\">\n"
" <div class=\"muted\" style=\"margin-bottom:8px;\">Upload bytes (sets program_ref)</div>\n"
" <div class=\"row\">\n"
" <input id=\"uploadFile\" type=\"file\" />\n"
" <button class=\"btn\" id=\"btnUpload\" type=\"button\">Upload</button>\n"
" </div>\n"
" <hr style=\"border:none;border-top:1px solid rgba(255,255,255,.10);margin:14px 0;\" />\n"
" <div class=\"muted\" style=\"margin-bottom:8px;\">Run</div>\n"
" <input id=\"programRef\" placeholder=\"program_ref (hex ref or concept name)\" />\n"
" <div class=\"muted\" style=\"margin:10px 0 8px;\">input_refs (comma-separated hex refs or names)</div>\n"
" <input id=\"inputRefs\" placeholder=\"in0,in1,...\" />\n"
" <div class=\"muted\" style=\"margin:10px 0 8px;\">params_ref (optional)</div>\n"
" <input id=\"paramsRef\" placeholder=\"params\" />\n"
" <div class=\"muted\" style=\"margin:10px 0 8px;\">scheme_ref (optional, default dag)</div>\n"
" <input id=\"schemeRef\" placeholder=\"dag\" />\n"
" <div class=\"row\" style=\"margin-top:12px;\">\n"
" <button class=\"btn primary\" id=\"btnRun\" type=\"button\">Run</button>\n"
" <a class=\"muted\" href=\"/v1/contract\">/v1/contract</a>\n"
" <a class=\"muted\" href=\"/v1/meta\">/v1/meta</a>\n"
" </div>\n"
" <div class=\"muted\" style=\"margin:14px 0 8px;\">Response</div>\n"
" <pre id=\"out\"></pre>\n"
" </div>\n"
" <div class=\"card\" id=\"relations\">\n"
" <div class=\"muted\" style=\"margin-bottom:8px;\">Relations</div>\n"
" <div class=\"row\" style=\"margin-top:10px;\">\n"
" <button class=\"btn\" id=\"btnRelations\" type=\"button\">Refresh</button>\n"
" </div>\n"
" <pre id=\"relationsOut\"></pre>\n"
" </div>\n"
" </div>\n"
" </section>\n"
" <section id=\"about\" class=\"grid\" style=\"margin-top:16px;\">\n"
" <article class=\"card span-6\">\n"
" <h2>About</h2>\n"
" <p class=\"muted\">amduatd is a local-first mapping surface over a single ASL store root. This UI is a lightweight editor and runner for concepts and PEL programs.</p>\n"
" </article>\n"
" <article class=\"card span-6\">\n"
" <h2>Links</h2>\n"
" <p class=\"muted\"><a href=\"/v1/contract\">/v1/contract</a> • <a href=\"/v1/meta\">/v1/meta</a> • <a href=\"/v1/relations\">/v1/relations</a></p>\n"
" </article>\n"
" </section>\n"
" </main>\n"
" <footer>\n"
" <span>© 2025 Niklas Rydberg.</span>\n"
" <span><a href=\"#title\">Back to top</a></span>\n"
" </footer>\n"
" </div>\n"
"\n"
" <script>\n"
" const el = (id) => document.getElementById(id);\n"
" const out = (v) => { el('out').textContent = typeof v === 'string' ? v : JSON.stringify(v, null, 2); };\n"
" const td = new TextDecoder('utf-8');\n"
" const te = new TextEncoder();\n"
" const toHex = (u8) => Array.from(u8).map(b => b.toString(16).padStart(2,'0')).join('');\n"
" const fromHex = (s) => { const t=(s||'').trim(); if(t.length%2) throw new Error('hex length must be even'); const o=new Uint8Array(t.length/2); for(let i=0;i<o.length;i++){o[i]=parseInt(t.slice(i*2,i*2+2),16);} return o; };\n"
" const toB64 = (u8) => { let bin=''; for(let i=0;i<u8.length;i++) bin += String.fromCharCode(u8[i]); return btoa(bin); };\n"
" const fromB64 = (s) => { const bin=atob((s||'').trim()); const o=new Uint8Array(bin.length); for(let i=0;i<bin.length;i++) o[i]=bin.charCodeAt(i); return o; };\n"
"\n"
" async function ensureConcept(name){\n"
" const resp = await fetch('/v1/concepts',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name})});\n"
" if(resp.status === 409) return; // already exists\n"
" if(!resp.ok) throw new Error(await resp.text());\n"
" }\n"
"\n"
" async function loadConcept(){\n"
" const name = el('conceptName').value.trim();\n"
" if(!name){ out('missing concept name'); return; }\n"
" const resp = await fetch(`/v1/concepts/${encodeURIComponent(name)}`);\n"
" const text = await resp.text();\n"
" if(!resp.ok){ out(text); return; }\n"
" const j = JSON.parse(text);\n"
" el('latestRef').value = j.latest_ref || '';\n"
" el('programRef').value = name;\n"
" if(!j.latest_ref){ el('editor').value=''; out(text); return; }\n"
" const mode = el('mode').value;\n"
" const infoResp = await fetch(`/v1/artifacts/${j.latest_ref}?format=info`);\n"
" if(infoResp.ok){ const info = JSON.parse(await infoResp.text()); el('typeTag').value = info.has_type_tag ? info.type_tag : ''; }\n"
" if(mode === 'pel_program'){\n"
" if(!el('editor').value.trim()){\n"
" el('editor').value = JSON.stringify({\n"
" nodes:[{id:1,op:{name:'pel.bytes.concat',version:1},inputs:[{external:{input_index:0}}],params_hex:''}],\n"
" roots:[{node_id:1,output_index:0}]\n"
" }, null, 2);\n"
" }\n"
" out(text);\n"
" return;\n"
" }\n"
" const aResp = await fetch(`/v1/artifacts/${j.latest_ref}`);\n"
" if(!aResp.ok){ out(await aResp.text()); return; }\n"
" const u8 = new Uint8Array(await aResp.arrayBuffer());\n"
" if(mode==='hex') el('editor').value = toHex(u8);\n"
" else if(mode==='base64') el('editor').value = toB64(u8);\n"
" else el('editor').value = td.decode(u8);\n"
" out(text);\n"
" }\n"
"\n"
" async function saveConcept(){\n"
" const name = el('conceptName').value.trim();\n"
" if(!name){ out('missing concept name'); return; }\n"
" await ensureConcept(name);\n"
" const mode = el('mode').value;\n"
" if(mode === 'pel_program'){\n"
" const body = JSON.parse(el('editor').value || '{}');\n"
" const mkResp = await fetch('/v1/pel/programs',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});\n"
" const mkText = await mkResp.text();\n"
" if(!mkResp.ok){ out(mkText); return; }\n"
" const mk = JSON.parse(mkText);\n"
" const pref = mk.program_ref;\n"
" const pubResp = await fetch(`/v1/concepts/${encodeURIComponent(name)}/publish`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ref:pref})});\n"
" const pubText = await pubResp.text();\n"
" out(pubText);\n"
" if(pubResp.ok){ el('typeTag').value = '0x00000101'; await loadConcept(); }\n"
" return;\n"
" }\n"
" let u8;\n"
" if(mode==='hex') u8 = fromHex(el('editor').value);\n"
" else if(mode==='base64') u8 = fromB64(el('editor').value);\n"
" else u8 = te.encode(el('editor').value);\n"
" const headers = {'Content-Type':'application/octet-stream'};\n"
" const typeTag = el('typeTag').value.trim();\n"
" if(typeTag) headers['X-Amduat-Type-Tag'] = typeTag;\n"
" const putResp = await fetch('/v1/artifacts',{method:'POST',headers,body:u8});\n"
" const putText = await putResp.text();\n"
" if(!putResp.ok){ out(putText); return; }\n"
" const put = JSON.parse(putText);\n"
" const pubResp = await fetch(`/v1/concepts/${encodeURIComponent(name)}/publish`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ref:put.ref})});\n"
" const pubText = await pubResp.text();\n"
" out(pubText);\n"
" if(pubResp.ok) await loadConcept();\n"
" }\n"
"\n"
" el('btnConceptCreate').addEventListener('click', async () => { try{ await ensureConcept(el('conceptName').value.trim()); out('{\"ok\":true}\\n'); }catch(e){ out(String(e)); } });\n"
" el('btnLoad').addEventListener('click', () => loadConcept().catch(e => out(String(e))));\n"
" el('btnSave').addEventListener('click', () => saveConcept().catch(e => out(String(e))));\n"
" el('mode').addEventListener('change', () => {\n"
" if(el('mode').value === 'pel_program'){\n"
" el('typeTag').value = '0x00000101';\n"
" }\n"
" loadConcept().catch(() => {});\n"
" });\n"
"\n"
" el('btnProgramTemplate').addEventListener('click', () => {\n"
" el('mode').value = 'pel_program';\n"
" el('typeTag').value = '0x00000101';\n"
" el('editor').value = JSON.stringify({\n"
" nodes:[{id:1,op:{name:'pel.bytes.concat',version:1},inputs:[{external:{input_index:0}}],params_hex:''}],\n"
" roots:[{node_id:1,output_index:0}]\n"
" }, null, 2);\n"
" });\n"
"\n"
" el('btnPublishRef').addEventListener('click', async () => {\n"
" try{\n"
" const name = el('conceptName').value.trim();\n"
" const ref = el('publishRef').value.trim();\n"
" if(!name||!ref){ out('missing name/ref'); return; }\n"
" await ensureConcept(name);\n"
" const resp = await fetch(`/v1/concepts/${encodeURIComponent(name)}/publish`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ref})});\n"
" out(await resp.text());\n"
" }catch(e){ out(String(e)); }\n"
" });\n"
"\n"
" el('btnUpload').addEventListener('click', async () => {\n"
" try {\n"
" const file = el('uploadFile').files && el('uploadFile').files[0];\n"
" if (!file) { out('no file selected'); return; }\n"
" const resp = await fetch('/v1/artifacts', { method:'POST', headers:{'Content-Type':'application/octet-stream'}, body:file });\n"
" const text = await resp.text();\n"
" out(text);\n"
" if (resp.ok) { const j = JSON.parse(text); if (j && j.ref) el('programRef').value = j.ref; }\n"
" } catch (e) { out(String(e)); }\n"
" });\n"
"\n"
" el('btnRun').addEventListener('click', async () => {\n"
" try {\n"
" const program_ref = el('programRef').value.trim();\n"
" const input_refs = (el('inputRefs').value || '').split(',').map(s => s.trim()).filter(Boolean);\n"
" const params_ref = el('paramsRef').value.trim();\n"
" const scheme_ref = el('schemeRef').value.trim();\n"
" const body = { program_ref, input_refs };\n"
" if (params_ref) body.params_ref = params_ref;\n"
" if (scheme_ref) body.scheme_ref = scheme_ref;\n"
" const resp = await fetch('/v1/pel/run', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) });\n"
" out(await resp.text());\n"
" } catch (e) { out(String(e)); }\n"
" });\n"
"\n"
" async function loadRelations(){\n"
" const resp = await fetch('/v1/relations');\n"
" const text = await resp.text();\n"
" el('relationsOut').textContent = text;\n"
" }\n"
" el('btnRelations').addEventListener('click', () => loadRelations().catch(e => out(String(e))));\n"
" loadRelations().catch(() => {});\n"
" </script>\n"
"</body>\n"
"</html>\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_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_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 char *relation_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 (store == NULL || cfg == NULL || relation_name == NULL) {
return false;
}
if (!amduatd_build_prefixed_bytes("AMDUATD/RELATION/1", relation_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;
}
(void)amduat_asl_store_put(store, artifact, out_ref);
free(bytes);
return true;
}
static bool amduatd_concepts_ensure_alias(amduatd_concepts_t *c,
amduat_asl_store_t *store,
const amduat_asl_store_fs_config_t *cfg,
const char *alias_name,
amduat_reference_t concept_ref) {
amduat_reference_t existing;
amduat_reference_t name_ref;
amduat_reference_t edge_ref;
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_name_valid(alias_name)) {
return false;
}
if (amduatd_concepts_lookup_alias(store, cfg, c, alias_name, &existing)) {
amduat_reference_free(&existing);
return true;
}
if (!amduatd_concepts_put_name_artifact(store, alias_name, &name_ref)) {
return false;
}
if (!amduatd_concepts_put_edge(store,
c,
name_ref,
concept_ref,
c->rel_aliases_ref,
&edge_ref)) {
amduat_reference_free(&name_ref);
amduat_reference_free(&edge_ref);
return false;
}
amduat_reference_free(&name_ref);
amduat_reference_free(&edge_ref);
return true;
}
static bool amduatd_concepts_init(amduatd_concepts_t *c,
amduat_asl_store_t *store,
const amduat_asl_store_fs_config_t *cfg,
const char *root_path) {
uint8_t *file_bytes = NULL;
size_t file_len = 0;
size_t i;
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_concepts_seed_relation(store, cfg, "aliases",
&c->rel_aliases_ref)) {
return false;
}
if (!amduatd_concepts_ensure_alias(c, store, cfg, "ms.aliases",
c->rel_aliases_ref)) {
return false;
}
if (!amduatd_concepts_seed_relation(store, cfg, "materializesAs",
&c->rel_materializes_ref)) {
return false;
}
if (!amduatd_concepts_ensure_alias(c, store, cfg, "ms.materializes_as",
c->rel_materializes_ref)) {
return false;
}
if (!amduatd_concepts_seed_relation(store, cfg, "represents",
&c->rel_represents_ref)) {
return false;
}
if (!amduatd_concepts_ensure_alias(c, store, cfg, "ms.represents",
c->rel_represents_ref)) {
return false;
}
if (!amduatd_concepts_seed_relation(store, cfg, "requiresKey",
&c->rel_requires_key_ref)) {
return false;
}
if (!amduatd_concepts_ensure_alias(c, store, cfg, "ms.requires_key",
c->rel_requires_key_ref)) {
return false;
}
if (!amduatd_concepts_seed_relation(store, cfg, "withinDomain",
&c->rel_within_domain_ref)) {
return false;
}
if (!amduatd_concepts_ensure_alias(c, store, cfg, "ms.within_domain",
c->rel_within_domain_ref)) {
return false;
}
if (!amduatd_concepts_seed_relation(store, cfg, "computedBy",
&c->rel_computed_by_ref)) {
return false;
}
if (!amduatd_concepts_ensure_alias(c, store, cfg, "ms.computed_by",
c->rel_computed_by_ref)) {
return false;
}
if (!amduatd_concepts_seed_relation(store, cfg, "hasProvenance",
&c->rel_has_provenance_ref)) {
return false;
}
if (!amduatd_concepts_ensure_alias(c, store, cfg, "ms.has_provenance",
c->rel_has_provenance_ref)) {
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;
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) {
free(file_bytes);
return false;
}
memcpy(tmp, line, n);
tmp[n] = '\0';
memset(&ref, 0, sizeof(ref));
ok = amduat_asl_ref_decode_hex(tmp, &ref);
free(tmp);
if (ok) {
(void)amduatd_ref_list_push(&c->edge_refs, ref);
amduat_reference_free(&ref);
}
}
if (nl == NULL) {
break;
}
line = nl + 1;
}
}
free(file_bytes);
for (i = 0; i < c->edge_refs.len; ++i) {
if (c->edge_refs.refs[i].hash_id == 0 ||
c->edge_refs.refs[i].digest.data == NULL ||
c->edge_refs.refs[i].digest.len == 0) {
continue;
}
}
return true;
}
static bool amduatd_concepts_append_edge_ref(amduatd_concepts_t *c,
amduat_reference_t edge_ref) {
char *hex = NULL;
FILE *f;
bool ok;
if (c == NULL) {
return false;
}
if (!amduat_asl_ref_encode_hex(edge_ref, &hex)) {
return false;
}
f = fopen(c->edges_path, "ab");
if (f == NULL) {
free(hex);
return false;
}
ok = fprintf(f, "%s\n", hex) > 0;
fclose(f);
if (!ok) {
free(hex);
return false;
}
free(hex);
return amduatd_ref_list_push(&c->edge_refs, edge_ref);
}
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_reference_t *out_edge_ref) {
amduat_tgk_edge_body_t edge;
amduat_reference_t from_arr[1];
amduat_reference_t to_arr[1];
amduat_octets_t bytes;
amduat_artifact_t artifact;
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;
}
memset(&edge, 0, sizeof(edge));
edge.type = AMDUATD_TGK_EDGE_TYPE;
from_arr[0] = from;
to_arr[0] = to;
edge.from = from_arr;
edge.from_len = 1;
edge.to = to_arr;
edge.to_len = 1;
edge.payload = relation_concept_ref;
bytes = amduat_octets(NULL, 0);
if (!amduat_enc_tgk1_edge_encode_v1(&edge, &bytes)) {
return false;
}
artifact = amduat_artifact_with_type(bytes,
amduat_type_tag(AMDUAT_TYPE_TAG_TGK1_EDGE_V1));
if (amduat_asl_store_put(store, artifact, out_edge_ref) != AMDUAT_ASL_STORE_OK) {
free((void *)bytes.data);
return false;
}
free((void *)bytes.data);
return amduatd_concepts_append_edge_ref(c, *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->edge_refs.len; i > 0; --i) {
amduat_artifact_t artifact;
amduat_tgk_edge_body_t edge;
amduat_asl_store_error_t err;
memset(&artifact, 0, sizeof(artifact));
err = amduat_asl_store_get(store, c->edge_refs.refs[i - 1u], &artifact);
if (err != AMDUAT_ASL_STORE_OK) {
continue;
}
if (!artifact.has_type_tag ||
artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) {
amduat_asl_artifact_free(&artifact);
continue;
}
memset(&edge, 0, sizeof(edge));
if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) {
amduat_asl_artifact_free(&artifact);
continue;
}
if (edge.from_len == 1 && edge.to_len == 1 &&
amduat_reference_eq(edge.payload, c->rel_aliases_ref) &&
amduat_reference_eq(edge.from[0], name_ref)) {
amduat_reference_clone(edge.to[0], out_concept_ref);
amduat_enc_tgk1_edge_free(&edge);
amduat_asl_artifact_free(&artifact);
amduat_reference_free(&name_ref);
return true;
}
amduat_enc_tgk1_edge_free(&edge);
amduat_asl_artifact_free(&artifact);
}
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->edge_refs.len; i > 0; --i) {
amduat_artifact_t artifact;
amduat_tgk_edge_body_t edge;
amduat_asl_store_error_t err;
memset(&artifact, 0, sizeof(artifact));
err = amduat_asl_store_get(store, c->edge_refs.refs[i - 1u], &artifact);
if (err != AMDUAT_ASL_STORE_OK) {
continue;
}
if (!artifact.has_type_tag ||
artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) {
amduat_asl_artifact_free(&artifact);
continue;
}
memset(&edge, 0, sizeof(edge));
if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) {
amduat_asl_artifact_free(&artifact);
continue;
}
if (edge.from_len == 1 && edge.to_len == 1 &&
amduat_reference_eq(edge.payload, c->rel_materializes_ref) &&
amduat_reference_eq(edge.from[0], concept_ref)) {
amduat_reference_clone(edge.to[0], out_ref);
amduat_enc_tgk1_edge_free(&edge);
amduat_asl_artifact_free(&artifact);
return true;
}
amduat_enc_tgk1_edge_free(&edge);
amduat_asl_artifact_free(&artifact);
}
return false;
}
static bool amduatd_parse_name_artifact(amduat_artifact_t artifact,
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;
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 >= cap) {
return false;
}
memcpy(out, bytes + prefix_len + 1u, name_len);
out[name_len] = '\0';
return amduatd_name_valid(out);
}
static bool amduatd_handle_get_concepts(int fd,
amduat_asl_store_t *store,
const amduatd_concepts_t *concepts) {
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->edge_refs.len; ++i) {
amduat_reference_t edge_ref = concepts->edge_refs.refs[i];
amduat_artifact_t artifact;
amduat_tgk_edge_body_t edge;
amduat_asl_store_error_t err;
char name[128];
char *concept_hex = NULL;
memset(&artifact, 0, sizeof(artifact));
err = amduat_asl_store_get(store, edge_ref, &artifact);
if (err != AMDUAT_ASL_STORE_OK) {
continue;
}
if (!artifact.has_type_tag ||
artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) {
amduat_asl_artifact_free(&artifact);
continue;
}
memset(&edge, 0, sizeof(edge));
if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) {
amduat_asl_artifact_free(&artifact);
continue;
}
if (!(edge.from_len == 1 && edge.to_len == 1 &&
amduat_reference_eq(edge.payload, concepts->rel_aliases_ref))) {
amduat_enc_tgk1_edge_free(&edge);
amduat_asl_artifact_free(&artifact);
continue;
}
amduat_asl_artifact_free(&artifact);
memset(&artifact, 0, sizeof(artifact));
err = amduat_asl_store_get(store, edge.from[0], &artifact);
if (err != AMDUAT_ASL_STORE_OK ||
!amduatd_parse_name_artifact(artifact, name, sizeof(name))) {
amduat_enc_tgk1_edge_free(&edge);
amduat_asl_artifact_free(&artifact);
continue;
}
amduat_asl_artifact_free(&artifact);
if (!amduat_asl_ref_encode_hex(edge.to[0], &concept_hex)) {
amduat_enc_tgk1_edge_free(&edge);
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);
amduat_enc_tgk1_edge_free(&edge);
}
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 char *name) {
amduat_reference_t concept_ref;
amduat_reference_t latest_ref;
amduatd_strbuf_t b;
char *concept_hex = NULL;
char *latest_hex = 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_name_valid(name)) {
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid name");
}
if (!amduatd_concepts_lookup_alias(store, cfg, concepts, name, &concept_ref)) {
return amduatd_send_json_error(fd, 404, "Not Found", "unknown concept");
}
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->edge_refs.len; ++i) {
amduat_reference_t edge_ref = concepts->edge_refs.refs[i];
amduat_artifact_t artifact;
amduat_tgk_edge_body_t edge;
amduat_asl_store_error_t err;
char *edge_hex = NULL;
char *ref_hex = NULL;
memset(&artifact, 0, sizeof(artifact));
err = amduat_asl_store_get(store, edge_ref, &artifact);
if (err != AMDUAT_ASL_STORE_OK) {
continue;
}
if (!artifact.has_type_tag ||
artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) {
amduat_asl_artifact_free(&artifact);
continue;
}
memset(&edge, 0, sizeof(edge));
if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) {
amduat_asl_artifact_free(&artifact);
continue;
}
amduat_asl_artifact_free(&artifact);
if (!(edge.from_len == 1 && edge.to_len == 1 &&
amduat_reference_eq(edge.payload, concepts->rel_materializes_ref) &&
amduat_reference_eq(edge.from[0], concept_ref))) {
amduat_enc_tgk1_edge_free(&edge);
continue;
}
if (!amduat_asl_ref_encode_hex(edge_ref, &edge_hex) ||
!amduat_asl_ref_encode_hex(edge.to[0], &ref_hex)) {
free(edge_hex);
free(ref_hex);
amduat_enc_tgk1_edge_free(&edge);
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);
amduat_enc_tgk1_edge_free(&edge);
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;
}
typedef struct {
char method[8];
char path[1024];
char content_type[128];
char accept[128];
char x_type_tag[64];
size_t content_length;
} amduatd_http_req_t;
static void amduatd_http_req_init(amduatd_http_req_t *req) {
memset(req, 0, sizeof(*req));
}
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);
}
}
return true;
}
static 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;
}
static 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),
"<!doctype html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"utf-8\" />\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
" <title>amduatd — Not Found</title>\n"
" <style>\n"
" :root{color-scheme:light dark;}\n"
" body{font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,Arial,sans-serif;"
" margin:0;line-height:1.4;}\n"
" .wrap{max-width:860px;margin:0 auto;padding:40px 20px;}\n"
" .card{border:1px solid rgba(127,127,127,.35);border-radius:14px;padding:22px;"
" background:rgba(127,127,127,.06);}\n"
" h1{margin:0 0 6px;font-size:22px;}\n"
" p{margin:8px 0;opacity:.9;}\n"
" code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
" font-size:13px;}\n"
" ul{margin:10px 0 0;padding-left:18px;}\n"
" a{color:inherit;}\n"
" .muted{opacity:.75;}\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <div class=\"wrap\">\n"
" <div class=\"card\">\n"
" <h1>404 — Not Found</h1>\n"
" <p>amduatd didnt recognize <code>%s %s</code>.</p>\n"
" <p class=\"muted\">Try one of these:</p>\n"
" <ul>\n"
" <li><a href=\"/v1/meta\">/v1/meta</a></li>\n"
" <li><a href=\"/v1/contract\">/v1/contract</a></li>\n"
" </ul>\n"
" <p class=\"muted\">Artifact bytes: <code>/v1/artifacts/&lt;ref&gt;</code></p>\n"
" </div>\n"
" </div>\n"
"</body>\n"
"</html>\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 char *s,
size_t len,
amduat_reference_t *out_ref) {
amduat_reference_t concept_ref;
char *tmp = 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_name_valid(tmp)) {
free(tmp);
return AMDUATD_REF_ERR_INVALID;
}
memset(&concept_ref, 0, sizeof(concept_ref));
if (!amduatd_concepts_lookup_alias(store, cfg, concepts, tmp, &concept_ref)) {
free(tmp);
return AMDUATD_REF_ERR_NOT_FOUND;
}
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 char *name,
amduat_reference_t *out_concept_ref) {
amduat_reference_t name_ref;
amduat_reference_t concept_ref;
amduat_reference_t edge_ref;
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_concepts_lookup_alias(store, cfg, concepts, name,
out_concept_ref)) {
return true;
}
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, name, &name_ref)) {
return false;
}
if (!amduatd_concepts_put_concept_id(store, cfg, &concept_ref)) {
amduat_reference_free(&name_ref);
return false;
}
if (!amduatd_concepts_put_edge(store,
concepts,
name_ref,
concept_ref,
concepts->rel_aliases_ref,
&edge_ref)) {
amduat_reference_free(&name_ref);
amduat_reference_free(&concept_ref);
return false;
}
amduat_reference_free(&name_ref);
amduat_reference_free(&edge_ref);
*out_concept_ref = concept_ref;
return true;
}
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,
&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) {
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(&registry_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(&registry_input_ref, 0, sizeof(registry_input_ref));
memset(&schema_output_ref, 0, sizeof(schema_output_ref));
memset(&registry_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, "ms.type.string",
&type_string_ref) ||
!amduatd_seed_concept_if_missing(store, cfg, concepts, "ms.type.ref",
&type_ref_ref) ||
!amduatd_seed_concept_if_missing(store, cfg, concepts, "ms.ui.field.title",
&key_title_ref) ||
!amduatd_seed_concept_if_missing(store, cfg, concepts, "ms.ui.field.status",
&key_status_ref) ||
!amduatd_seed_concept_if_missing(store, cfg, concepts,
"ms.ui.field.latest_ref",
&key_latest_ref) ||
!amduatd_seed_concept_if_missing(store, cfg, concepts,
"ms.ui.state.schema.v1",
&schema_concept_ref) ||
!amduatd_seed_concept_if_missing(store, cfg, concepts,
"ms.ui.state.registry.v1",
&registry_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(&registry_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, &registry_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(&registry_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, &registry_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,
&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],
&registry_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,
&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(&registry_concept_ref);
amduat_reference_free(&identity_program_ref);
amduat_reference_free(&schema_input_ref);
amduat_reference_free(&registry_input_ref);
amduat_reference_free(&schema_output_ref);
amduat_reference_free(&registry_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_seed_ui_html(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_ui_html,
strlen(k_amduatd_ui_html)));
(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;
}
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_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_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;
char *receipt_evaluator_id = NULL;
uint8_t *receipt_parity_digest = NULL;
size_t receipt_parity_digest_len = 0;
uint64_t receipt_started_at = 0;
uint64_t receipt_completed_at = 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 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(&params_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_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, 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, sv, sv_len,
&params_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, 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, 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, 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, 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, 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("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("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;
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 (!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(&params_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);
}
free(receipt_evaluator_id);
free(receipt_parity_digest);
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_get_ui(int fd,
amduat_asl_store_t *store,
amduat_reference_t ui_ref) {
amduat_artifact_t artifact;
amduat_asl_store_error_t err;
if (store == NULL || ui_ref.hash_id == 0 || ui_ref.digest.data == NULL ||
ui_ref.digest.len == 0) {
return amduatd_http_send_text(fd, 500, "Internal Server Error",
"ui not available\n", false);
}
memset(&artifact, 0, sizeof(artifact));
err = amduat_asl_store_get(store, ui_ref, &artifact);
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) {
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);
}
{
bool ok = amduatd_http_send_status(fd,
200,
"OK",
"text/html; charset=utf-8",
artifact.bytes.data,
artifact.bytes.len,
false);
amduat_asl_artifact_free(&artifact);
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_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 json[4096];
bool ok = false;
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->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;
}
if (!amduatd_name_valid(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_concepts_put_name_artifact(store, 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, 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,
&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,
&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);
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_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,
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,
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,
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 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;
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 (!amduatd_name_valid(name)) {
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid name");
}
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");
}
if (!amduatd_concepts_lookup_alias(store, cfg, concepts, name, &concept_ref)) {
return amduatd_send_json_error(fd, 404, "Not Found", "unknown concept");
}
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,
&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 char *name) {
amduat_reference_t concept_ref;
amduat_reference_t latest_ref;
char *hex = NULL;
char json[2048];
int n;
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_name_valid(name)) {
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid name");
}
if (!amduatd_concepts_lookup_alias(store, cfg, concepts, name, &concept_ref)) {
return amduatd_send_json_error(fd, 404, "Not Found", "unknown concept");
}
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_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) {
amduatd_http_req_t req;
char no_query[1024];
if (!amduatd_http_parse_request(fd, &req)) {
return false;
}
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, store, ui_ref);
}
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/meta") == 0) {
return amduatd_handle_meta(fd, cfg, api_contract_ref, false);
}
if (strcmp(req.method, "HEAD") == 0 && strcmp(no_query, "/v1/meta") == 0) {
return amduatd_handle_meta(fd, cfg, api_contract_ref, true);
}
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/contract") == 0) {
return amduatd_handle_get_contract(fd, store, &req, api_contract_ref);
}
if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/artifacts") == 0) {
return amduatd_handle_post_artifacts(fd, store, &req);
}
if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/run") == 0) {
return amduatd_handle_post_pel_run(fd, store, cfg, concepts, &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, "POST") == 0 &&
strcmp(no_query, "/v1/context_frames") == 0) {
return amduatd_handle_post_context_frames(fd, store, cfg, concepts, &req);
}
if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/concepts") == 0) {
return amduatd_handle_post_concepts(fd, store, cfg, concepts, &req);
}
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/concepts") == 0) {
return amduatd_handle_get_concepts(fd, store, concepts);
}
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/relations") == 0) {
return amduatd_handle_get_relations(fd, concepts);
}
if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/concepts/", 13) == 0) {
const char *name = no_query + 13;
if (name[0] == '\0') {
return amduatd_send_json_error(fd, 400, "Bad Request", "missing name");
}
return amduatd_handle_get_concept(fd, store, cfg, concepts, name);
}
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) {
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid path");
}
*slash = '\0';
return amduatd_handle_post_concept_publish(fd, store, cfg, concepts, name,
&req);
}
if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/resolve/", 12) == 0) {
const char *name = no_query + 12;
if (name[0] == '\0') {
return amduatd_send_json_error(fd, 400, "Bad Request", "missing name");
}
return amduatd_handle_get_resolve(fd, store, cfg, concepts, name);
}
if (strcmp(req.method, "GET") == 0 &&
strncmp(no_query, "/v1/artifacts/", 14) == 0) {
return amduatd_handle_get_artifact(fd, store, &req, req.path, false);
}
if (strcmp(req.method, "HEAD") == 0 &&
strncmp(no_query, "/v1/artifacts/", 14) == 0) {
return amduatd_handle_head_artifact(fd, store, &req, req.path);
}
return amduatd_http_send_not_found(fd, &req);
}
static void amduatd_print_usage(FILE *stream) {
fprintf(stream,
"usage:\n"
" amduatd [--root PATH] [--sock PATH]\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;
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;
int i;
int sfd = -1;
memset(&api_contract_ref, 0, sizeof(api_contract_ref));
memset(&ui_ref, 0, sizeof(ui_ref));
memset(&concepts, 0, sizeof(concepts));
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], "--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;
}
}
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_seed_api_contract(&store, &cfg, &api_contract_ref)) {
fprintf(stderr, "error: failed to seed api contract\n");
return 8;
}
if (!amduatd_seed_ui_html(&store, &cfg, &ui_ref)) {
fprintf(stderr, "error: failed to seed ui html\n");
return 8;
}
if (!amduatd_concepts_init(&concepts, &store, &cfg, root)) {
fprintf(stderr, "error: failed to init concept edges\n");
return 8;
}
if (!amduatd_seed_ms_ui_state(&store, &cfg, &concepts)) {
fprintf(stderr, "error: failed to seed ms ui state\n");
return 8;
}
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) {
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);
(void)close(cfd);
}
amduat_reference_free(&api_contract_ref);
amduat_reference_free(&ui_ref);
amduatd_concepts_free(&concepts);
(void)unlink(sock_path);
(void)close(sfd);
return 0;
}