6213 lines
216 KiB
C
6213 lines
216 KiB
C
#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 didn’t 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/<ref></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(®istry_concept_ref, 0, sizeof(registry_concept_ref));
|
||
memset(&identity_program_ref, 0, sizeof(identity_program_ref));
|
||
memset(&schema_input_ref, 0, sizeof(schema_input_ref));
|
||
memset(®istry_input_ref, 0, sizeof(registry_input_ref));
|
||
memset(&schema_output_ref, 0, sizeof(schema_output_ref));
|
||
memset(®istry_output_ref, 0, sizeof(registry_output_ref));
|
||
memset(&seed_manifest_ref, 0, sizeof(seed_manifest_ref));
|
||
memset(&seed_environment_ref, 0, sizeof(seed_environment_ref));
|
||
memset(&seed_executor_ref, 0, sizeof(seed_executor_ref));
|
||
memset(&receipt_ref, 0, sizeof(receipt_ref));
|
||
memset(&run_result, 0, sizeof(run_result));
|
||
memset(fields, 0, sizeof(fields));
|
||
memset(entries, 0, sizeof(entries));
|
||
memset(&b, 0, sizeof(b));
|
||
memset(&artifact, 0, sizeof(artifact));
|
||
|
||
if (!amduatd_seed_concept_if_missing(store, cfg, concepts, "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",
|
||
®istry_concept_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
|
||
if (!amduatd_seed_pel_identity_program(store, &identity_program_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
|
||
{
|
||
amduat_reference_t latest_schema;
|
||
amduat_reference_t latest_registry;
|
||
bool have_schema = false;
|
||
bool have_registry = false;
|
||
bool schema_ok = false;
|
||
bool registry_ok = false;
|
||
|
||
memset(&latest_schema, 0, sizeof(latest_schema));
|
||
memset(&latest_registry, 0, sizeof(latest_registry));
|
||
have_schema = amduatd_concepts_resolve_latest(store,
|
||
concepts,
|
||
schema_concept_ref,
|
||
&latest_schema);
|
||
have_registry = amduatd_concepts_resolve_latest(store,
|
||
concepts,
|
||
registry_concept_ref,
|
||
&latest_registry);
|
||
if (have_schema && have_registry) {
|
||
amduat_artifact_t schema_artifact;
|
||
amduat_artifact_t registry_artifact;
|
||
amduat_asl_store_error_t err;
|
||
|
||
memset(&schema_artifact, 0, sizeof(schema_artifact));
|
||
memset(®istry_artifact, 0, sizeof(registry_artifact));
|
||
err = amduat_asl_store_get(store, latest_schema, &schema_artifact);
|
||
if (err == AMDUAT_ASL_STORE_OK &&
|
||
schema_artifact.bytes.len != 0 &&
|
||
schema_artifact.bytes.data != NULL &&
|
||
amduatd_bytes_contains(schema_artifact.bytes.data,
|
||
schema_artifact.bytes.len,
|
||
"\"key_text\"")) {
|
||
schema_ok = true;
|
||
}
|
||
err = amduat_asl_store_get(store, latest_registry, ®istry_artifact);
|
||
if (err == AMDUAT_ASL_STORE_OK &&
|
||
registry_artifact.bytes.len != 0 &&
|
||
registry_artifact.bytes.data != NULL &&
|
||
amduatd_bytes_contains(registry_artifact.bytes.data,
|
||
registry_artifact.bytes.len,
|
||
"\"value_text\"")) {
|
||
registry_ok = true;
|
||
}
|
||
amduat_asl_artifact_free(&schema_artifact);
|
||
amduat_asl_artifact_free(®istry_artifact);
|
||
}
|
||
amduat_reference_free(&latest_schema);
|
||
amduat_reference_free(&latest_registry);
|
||
if (have_schema && have_registry && schema_ok && registry_ok) {
|
||
ok = true;
|
||
goto seed_cleanup;
|
||
}
|
||
}
|
||
|
||
{
|
||
const char *payload = "{\"seed\":\"manifest\"}";
|
||
artifact = amduat_artifact(amduat_octets(payload, strlen(payload)));
|
||
if (!amduatd_seed_store_artifact(store, artifact, &seed_manifest_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
}
|
||
{
|
||
const char *payload = "{\"seed\":\"environment\"}";
|
||
artifact = amduat_artifact(amduat_octets(payload, strlen(payload)));
|
||
if (!amduatd_seed_store_artifact(store, artifact, &seed_environment_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
}
|
||
{
|
||
const char *payload = "{\"seed\":\"executor\"}";
|
||
artifact = amduat_artifact(amduat_octets(payload, strlen(payload)));
|
||
if (!amduatd_seed_store_artifact(store, artifact, &seed_executor_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
}
|
||
|
||
{
|
||
amduat_reference_t hello_ref;
|
||
amduat_reference_t title_ref;
|
||
amduat_reference_t status_ref;
|
||
char *hello_hex = NULL;
|
||
const char *payload = "hello";
|
||
|
||
memset(&hello_ref, 0, sizeof(hello_ref));
|
||
memset(&title_ref, 0, sizeof(title_ref));
|
||
memset(&status_ref, 0, sizeof(status_ref));
|
||
|
||
artifact = amduat_artifact(amduat_octets(payload, strlen(payload)));
|
||
if (!amduatd_seed_store_artifact(store, artifact, &hello_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
if (!amduat_asl_ref_encode_hex(hello_ref, &hello_hex)) {
|
||
goto seed_cleanup;
|
||
}
|
||
seed_latest_hex = hello_hex;
|
||
|
||
artifact = amduat_artifact(amduat_octets("Amduat UI",
|
||
strlen("Amduat UI")));
|
||
if (!amduatd_seed_store_artifact(store, artifact, &title_ref)) {
|
||
free(hello_hex);
|
||
goto seed_cleanup;
|
||
}
|
||
|
||
artifact = amduat_artifact(amduat_octets("ready", strlen("ready")));
|
||
if (!amduatd_seed_store_artifact(store, artifact, &status_ref)) {
|
||
free(hello_hex);
|
||
goto seed_cleanup;
|
||
}
|
||
|
||
fields[0].key_hex = NULL;
|
||
fields[1].key_hex = NULL;
|
||
fields[2].key_hex = NULL;
|
||
if (!amduat_asl_ref_encode_hex(key_title_ref, &fields[0].key_hex) ||
|
||
!amduat_asl_ref_encode_hex(key_status_ref, &fields[1].key_hex) ||
|
||
!amduat_asl_ref_encode_hex(key_latest_ref, &fields[2].key_hex)) {
|
||
goto seed_cleanup;
|
||
}
|
||
fields[0].type_hex = strdup("ms.type.string");
|
||
fields[1].type_hex = strdup("ms.type.string");
|
||
fields[2].type_hex = strdup("ms.type.ref");
|
||
if (fields[0].type_hex == NULL || fields[1].type_hex == NULL ||
|
||
fields[2].type_hex == NULL) {
|
||
goto seed_cleanup;
|
||
}
|
||
fields[0].key_text = strdup("title");
|
||
fields[1].key_text = strdup("status");
|
||
fields[2].key_text = strdup("latest_ref");
|
||
if (fields[0].key_text == NULL || fields[1].key_text == NULL ||
|
||
fields[2].key_text == NULL) {
|
||
goto seed_cleanup;
|
||
}
|
||
|
||
entries[0].key_hex = fields[0].key_hex;
|
||
entries[1].key_hex = fields[1].key_hex;
|
||
entries[2].key_hex = fields[2].key_hex;
|
||
|
||
entries[0].value_hex = strdup("Amduat UI");
|
||
entries[1].value_hex = strdup("ready");
|
||
entries[2].value_hex = hello_hex;
|
||
if (entries[0].value_hex == NULL || entries[1].value_hex == NULL ||
|
||
entries[2].value_hex == NULL) {
|
||
goto seed_cleanup;
|
||
}
|
||
hello_hex = NULL;
|
||
seed_latest_hex = NULL;
|
||
|
||
qsort(fields, 3, sizeof(fields[0]), amduatd_seed_entry_cmp);
|
||
qsort(entries, 3, sizeof(entries[0]), amduatd_seed_entry_cmp);
|
||
|
||
if (!amduatd_strbuf_append_cstr(&b,
|
||
"{\"schema_version\":1,\"fields\":[")) {
|
||
goto seed_cleanup;
|
||
}
|
||
for (i = 0; i < 3; ++i) {
|
||
if (i != 0 && !amduatd_strbuf_append_char(&b, ',')) {
|
||
goto seed_cleanup;
|
||
}
|
||
if (!amduatd_strbuf_append_cstr(&b, "{\"key_ref\":\"") ||
|
||
!amduatd_strbuf_append_cstr(&b, fields[i].key_hex) ||
|
||
!amduatd_strbuf_append_cstr(&b, "\",\"key_text\":\"") ||
|
||
!amduatd_strbuf_append_cstr(&b, fields[i].key_text) ||
|
||
!amduatd_strbuf_append_cstr(&b, "\",\"type_ref\":\"") ||
|
||
!amduatd_strbuf_append_cstr(&b, fields[i].type_hex) ||
|
||
!amduatd_strbuf_append_cstr(&b, "\"}")) {
|
||
goto seed_cleanup;
|
||
}
|
||
}
|
||
if (!amduatd_strbuf_append_cstr(&b, "]}")) {
|
||
goto seed_cleanup;
|
||
}
|
||
artifact = amduat_artifact(amduat_octets(b.data, b.len));
|
||
if (!amduatd_seed_store_artifact(store, artifact, &schema_input_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
amduatd_strbuf_free(&b);
|
||
|
||
memset(&b, 0, sizeof(b));
|
||
if (!amduatd_strbuf_append_cstr(&b,
|
||
"{\"registry_version\":1,\"entries\":[")) {
|
||
goto seed_cleanup;
|
||
}
|
||
for (i = 0; i < 3; ++i) {
|
||
if (i != 0 && !amduatd_strbuf_append_char(&b, ',')) {
|
||
goto seed_cleanup;
|
||
}
|
||
if (!amduatd_strbuf_append_cstr(&b, "{\"key_ref\":\"") ||
|
||
!amduatd_strbuf_append_cstr(&b, entries[i].key_hex) ||
|
||
!amduatd_strbuf_append_cstr(&b,
|
||
i == 2 ? "\",\"value_ref\":\""
|
||
: "\",\"value_text\":\"") ||
|
||
!amduatd_strbuf_append_cstr(&b, entries[i].value_hex) ||
|
||
!amduatd_strbuf_append_cstr(&b, "\"}")) {
|
||
goto seed_cleanup;
|
||
}
|
||
}
|
||
if (!amduatd_strbuf_append_cstr(&b, "]}")) {
|
||
goto seed_cleanup;
|
||
}
|
||
artifact = amduat_artifact(amduat_octets(b.data, b.len));
|
||
if (!amduatd_seed_store_artifact(store, artifact, ®istry_input_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
}
|
||
|
||
{
|
||
amduat_reference_t inputs[1];
|
||
amduat_reference_t scheme_ref = amduat_pel_program_dag_scheme_ref();
|
||
amduat_artifact_t receipt_artifact;
|
||
amduat_reference_t edge_ref;
|
||
amduat_octets_t evaluator_id = amduat_octets("amduatd-seed",
|
||
strlen("amduatd-seed"));
|
||
|
||
inputs[0] = schema_input_ref;
|
||
if (!amduat_pel_surf_run_with_result(store,
|
||
scheme_ref,
|
||
identity_program_ref,
|
||
inputs,
|
||
1,
|
||
false,
|
||
amduat_reference(0u,
|
||
amduat_octets(NULL, 0u)),
|
||
&run_result)) {
|
||
goto seed_cleanup;
|
||
}
|
||
if (!run_result.has_result_value || run_result.output_refs_len != 1) {
|
||
goto seed_cleanup;
|
||
}
|
||
if (!amduat_reference_clone(run_result.output_refs[0],
|
||
&schema_output_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
|
||
if (!amduat_fer1_receipt_from_pel_result(&run_result.result_value,
|
||
seed_manifest_ref,
|
||
seed_environment_ref,
|
||
evaluator_id,
|
||
seed_executor_ref,
|
||
false,
|
||
amduat_reference(0u,
|
||
amduat_octets(NULL, 0u)),
|
||
amduat_octets(NULL, 0u),
|
||
0,
|
||
0,
|
||
&receipt_artifact)) {
|
||
goto seed_cleanup;
|
||
}
|
||
if (!amduatd_seed_store_artifact(store, receipt_artifact, &receipt_ref)) {
|
||
amduat_asl_artifact_free(&receipt_artifact);
|
||
goto seed_cleanup;
|
||
}
|
||
amduat_asl_artifact_free(&receipt_artifact);
|
||
if (!amduatd_concepts_put_edge(store,
|
||
concepts,
|
||
schema_output_ref,
|
||
receipt_ref,
|
||
concepts->rel_has_provenance_ref,
|
||
&edge_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
amduat_reference_free(&edge_ref);
|
||
amduat_reference_free(&receipt_ref);
|
||
amduat_enc_pel1_result_free(&run_result.result_value);
|
||
amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len);
|
||
amduat_pel_surf_free_ref(&run_result.result_ref);
|
||
memset(&run_result, 0, sizeof(run_result));
|
||
}
|
||
|
||
{
|
||
amduat_reference_t inputs[1];
|
||
amduat_reference_t scheme_ref = amduat_pel_program_dag_scheme_ref();
|
||
amduat_artifact_t receipt_artifact;
|
||
amduat_reference_t edge_ref;
|
||
amduat_octets_t evaluator_id = amduat_octets("amduatd-seed",
|
||
strlen("amduatd-seed"));
|
||
|
||
inputs[0] = registry_input_ref;
|
||
if (!amduat_pel_surf_run_with_result(store,
|
||
scheme_ref,
|
||
identity_program_ref,
|
||
inputs,
|
||
1,
|
||
false,
|
||
amduat_reference(0u,
|
||
amduat_octets(NULL, 0u)),
|
||
&run_result)) {
|
||
goto seed_cleanup;
|
||
}
|
||
if (!run_result.has_result_value || run_result.output_refs_len != 1) {
|
||
goto seed_cleanup;
|
||
}
|
||
if (!amduat_reference_clone(run_result.output_refs[0],
|
||
®istry_output_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
|
||
if (!amduat_fer1_receipt_from_pel_result(&run_result.result_value,
|
||
seed_manifest_ref,
|
||
seed_environment_ref,
|
||
evaluator_id,
|
||
seed_executor_ref,
|
||
false,
|
||
amduat_reference(0u,
|
||
amduat_octets(NULL, 0u)),
|
||
amduat_octets(NULL, 0u),
|
||
0,
|
||
0,
|
||
&receipt_artifact)) {
|
||
goto seed_cleanup;
|
||
}
|
||
if (!amduatd_seed_store_artifact(store, receipt_artifact, &receipt_ref)) {
|
||
amduat_asl_artifact_free(&receipt_artifact);
|
||
goto seed_cleanup;
|
||
}
|
||
amduat_asl_artifact_free(&receipt_artifact);
|
||
if (!amduatd_concepts_put_edge(store,
|
||
concepts,
|
||
registry_output_ref,
|
||
receipt_ref,
|
||
concepts->rel_has_provenance_ref,
|
||
&edge_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
amduat_reference_free(&edge_ref);
|
||
amduat_reference_free(&receipt_ref);
|
||
amduat_enc_pel1_result_free(&run_result.result_value);
|
||
amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len);
|
||
amduat_pel_surf_free_ref(&run_result.result_ref);
|
||
memset(&run_result, 0, sizeof(run_result));
|
||
}
|
||
|
||
if (!amduatd_seed_materializes_if_missing(store, concepts,
|
||
schema_concept_ref,
|
||
schema_output_ref) ||
|
||
!amduatd_seed_materializes_if_missing(store, concepts,
|
||
registry_concept_ref,
|
||
registry_output_ref)) {
|
||
goto seed_cleanup;
|
||
}
|
||
|
||
ok = true;
|
||
|
||
seed_cleanup:
|
||
if (run_result.has_result_value) {
|
||
amduat_enc_pel1_result_free(&run_result.result_value);
|
||
}
|
||
if (run_result.output_refs != NULL) {
|
||
amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len);
|
||
}
|
||
amduat_pel_surf_free_ref(&run_result.result_ref);
|
||
|
||
amduat_reference_free(&type_string_ref);
|
||
amduat_reference_free(&type_ref_ref);
|
||
amduat_reference_free(&key_title_ref);
|
||
amduat_reference_free(&key_status_ref);
|
||
amduat_reference_free(&key_latest_ref);
|
||
amduat_reference_free(&schema_concept_ref);
|
||
amduat_reference_free(®istry_concept_ref);
|
||
amduat_reference_free(&identity_program_ref);
|
||
amduat_reference_free(&schema_input_ref);
|
||
amduat_reference_free(®istry_input_ref);
|
||
amduat_reference_free(&schema_output_ref);
|
||
amduat_reference_free(®istry_output_ref);
|
||
amduat_reference_free(&seed_manifest_ref);
|
||
amduat_reference_free(&seed_environment_ref);
|
||
amduat_reference_free(&seed_executor_ref);
|
||
|
||
for (i = 0; i < 3; ++i) {
|
||
free(fields[i].key_hex);
|
||
free(fields[i].type_hex);
|
||
free(fields[i].key_text);
|
||
free(entries[i].value_hex);
|
||
}
|
||
amduatd_strbuf_free(&b);
|
||
free(seed_latest_hex);
|
||
return ok;
|
||
}
|
||
|
||
static bool amduatd_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(¶ms_ref, 0, sizeof(params_ref));
|
||
memset(&run_result, 0, sizeof(run_result));
|
||
memset(&receipt_input_manifest_ref, 0, sizeof(receipt_input_manifest_ref));
|
||
memset(&receipt_environment_ref, 0, sizeof(receipt_environment_ref));
|
||
memset(&receipt_executor_ref, 0, sizeof(receipt_executor_ref));
|
||
memset(&receipt_sbom_ref, 0, sizeof(receipt_sbom_ref));
|
||
memset(&receipt_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,
|
||
¶ms_ref);
|
||
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
||
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
||
"params_ref not found");
|
||
goto pel_run_cleanup;
|
||
}
|
||
if (st != AMDUATD_REF_OK) {
|
||
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
||
"invalid params_ref");
|
||
goto pel_run_cleanup;
|
||
}
|
||
has_params_ref = true;
|
||
} else if (key_len == strlen("input_refs") &&
|
||
memcmp(key, "input_refs", key_len) == 0) {
|
||
size_t cap = 0;
|
||
if (have_input_refs) {
|
||
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
||
"duplicate input_refs");
|
||
goto pel_run_cleanup;
|
||
}
|
||
if (!amduatd_json_expect(&p, end, '[')) {
|
||
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
||
"invalid input_refs");
|
||
goto pel_run_cleanup;
|
||
}
|
||
cur = amduatd_json_skip_ws(p, end);
|
||
if (cur < end && *cur == ']') {
|
||
p = cur + 1;
|
||
have_input_refs = true;
|
||
} else {
|
||
for (;;) {
|
||
amduat_reference_t ref;
|
||
memset(&ref, 0, sizeof(ref));
|
||
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
||
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
||
"invalid input_ref");
|
||
goto pel_run_cleanup;
|
||
}
|
||
{
|
||
amduatd_ref_status_t st = amduatd_decode_ref_or_name_latest(
|
||
store, cfg, concepts, 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(¶ms_ref);
|
||
}
|
||
if (have_program_ref) {
|
||
amduat_reference_free(&program_ref);
|
||
}
|
||
if (have_scheme_ref && !scheme_is_dag) {
|
||
amduat_reference_free(&scheme_ref);
|
||
}
|
||
if (receipt_emitted) {
|
||
amduat_reference_free(&receipt_ref);
|
||
}
|
||
if (receipt_have_input_manifest) {
|
||
amduat_reference_free(&receipt_input_manifest_ref);
|
||
}
|
||
if (receipt_have_environment) {
|
||
amduat_reference_free(&receipt_environment_ref);
|
||
}
|
||
if (receipt_have_executor) {
|
||
amduat_reference_free(&receipt_executor_ref);
|
||
}
|
||
if (receipt_has_sbom) {
|
||
amduat_reference_free(&receipt_sbom_ref);
|
||
}
|
||
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;
|
||
}
|