amduat-api/src/amduatd.c
2025-12-22 21:03:00 +01:00

3791 lines
123 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

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

#define _POSIX_C_SOURCE 200809L
#include "amduat/asl/artifact_io.h"
#include "amduat/asl/asl_store_fs.h"
#include "amduat/asl/asl_store_fs_meta.h"
#include "amduat/asl/ref_text.h"
#include "amduat/asl/store.h"
#include "amduat/asl/ref_derive.h"
#include "amduat/enc/asl1_core_codec.h"
#include "amduat/enc/pel1_result.h"
#include "amduat/enc/pel_program_dag.h"
#include "amduat/enc/tgk1_edge.h"
#include "amduat/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>
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;
amduatd_ref_list_t edge_refs;
} amduatd_concepts_t;
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);
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\":\"POST\",\"path\":\"/v1/concepts/{name}/publish\"},"
"{\"method\":\"GET\",\"path\":\"/v1/resolve/{name}\"},"
"{\"method\":\"POST\",\"path\":\"/v1/artifacts\"},"
"{\"method\":\"GET\",\"path\":\"/v1/artifacts/{ref}\"},"
"{\"method\":\"HEAD\",\"path\":\"/v1/artifacts/{ref}\"},"
"{\"method\":\"POST\",\"path\":\"/v1/pel/run\"},"
"{\"method\":\"POST\",\"path\":\"/v1/pel/programs\"}"
"],"
"\"schemas\":{"
"\"pel_run_request\":{"
"\"type\":\"object\","
"\"required\":[\"program_ref\",\"input_refs\"],"
"\"properties\":{"
"\"program_ref\":{\"type\":\"string\",\"description\":\"hex ref 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'\"}"
"}"
"},"
"\"pel_run_response\":{"
"\"type\":\"object\","
"\"required\":[\"result_ref\",\"output_refs\",\"status\"],"
"\"properties\":{"
"\"result_ref\":{\"type\":\"string\",\"description\":\"hex ref\"},"
"\"trace_ref\":{\"type\":\"string\",\"description\":\"hex ref\"},"
"\"output_refs\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"description\":\"hex ref\"}},"
"\"status\":{\"type\":\"string\"}"
"}"
"},"
"\"pel_program_author_request\":{"
"\"type\":\"object\","
"\"required\":[\"nodes\",\"roots\"],"
"\"properties\":{"
"\"nodes\":{\"type\":\"array\"},"
"\"roots\":{\"type\":\"array\"}"
"}"
"}"
"}"
"}\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 — PEL Program Builder</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.35;}\n"
" header{padding:18px 18px 8px;max-width:1100px;margin:0 auto;}\n"
" main{padding:0 18px 24px;max-width:1100px;margin:0 auto;}\n"
" h1{margin:0 0 4px;font-size:18px;}\n"
" .muted{opacity:.75;font-size:13px;}\n"
" .grid{display:grid;grid-template-columns:1fr;gap:14px;}\n"
" @media (min-width: 980px){.grid{grid-template-columns: 1.2fr .8fr;}}\n"
" .card{border:1px solid rgba(127,127,127,.35);border-radius:14px;padding:14px;"
" background:rgba(127,127,127,.06);}\n"
" textarea{width:100%;min-height:380px;resize:vertical;box-sizing:border-box;"
" font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
" font-size:12.5px;line-height:1.35;border-radius:10px;padding:10px;"
" border:1px solid rgba(127,127,127,.35);background:transparent;}\n"
" input{width:100%;box-sizing:border-box;border-radius:10px;padding:10px;"
" border:1px solid rgba(127,127,127,.35);background:transparent;"
" font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
" font-size:12.5px;}\n"
" button{border-radius:10px;padding:10px 12px;border:1px solid rgba(127,127,127,.35);"
" background:rgba(127,127,127,.12);cursor:pointer;}\n"
" button:hover{background:rgba(127,127,127,.18);}\n"
" .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;}\n"
" .row button{flex:0 0 auto;}\n"
" pre{white-space:pre-wrap;word-break:break-word;margin:0;padding:10px;border-radius:10px;"
" border:1px solid rgba(127,127,127,.35);background:rgba(0,0,0,.08);"
" font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
" font-size:12.5px;min-height:120px;}\n"
" a{color:inherit;}\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <header>\n"
" <h1>amduatd — PEL/PROGRAM-DAG/1 builder</h1>\n"
" <div class=\"muted\">POSTs to <code>/v1/pel/programs</code> and <code>/v1/pel/run</code> on this daemon.</div>\n"
" </header>\n"
" <main>\n"
" <div class=\"grid\">\n"
" <div class=\"card\">\n"
" <div class=\"row\" style=\"justify-content:space-between;\">\n"
" <div class=\"muted\">Program authoring JSON</div>\n"
" <div class=\"row\">\n"
" <button id=\"btnTemplate\" type=\"button\">Insert template</button>\n"
" <button id=\"btnCreate\" type=\"button\">Create program</button>\n"
" </div>\n"
" </div>\n"
" <textarea id=\"program\" spellcheck=\"false\"></textarea>\n"
" </div>\n"
"\n"
" <div class=\"card\">\n"
" <div class=\"muted\" style=\"margin-bottom:8px;\">Run</div>\n"
" <div style=\"margin-bottom:8px;\" class=\"muted\">program_ref</div>\n"
" <input id=\"programRef\" placeholder=\"<program_ref>\" />\n"
" <div style=\"margin:10px 0 8px;\" class=\"muted\">input_refs (comma-separated hex refs)</div>\n"
" <input id=\"inputRefs\" placeholder=\"<ref0>,<ref1>,...\" />\n"
" <div style=\"margin:10px 0 8px;\" class=\"muted\">params_ref (optional)</div>\n"
" <input id=\"paramsRef\" placeholder=\"<params_ref>\" />\n"
" <div style=\"margin:10px 0 8px;\" class=\"muted\">scheme_ref (optional, default dag)</div>\n"
" <input id=\"schemeRef\" placeholder=\"dag\" />\n"
" <div class=\"muted\" style=\"margin:14px 0 8px;\">Concept</div>\n"
" <div style=\"margin-bottom:8px;\" class=\"muted\">name (lowercase, [a-z0-9_./-])</div>\n"
" <input id=\"conceptName\" placeholder=\"hello\" />\n"
" <div class=\"row\" style=\"margin-top:10px;\">\n"
" <button id=\"btnConceptCreate\" type=\"button\">Create concept</button>\n"
" <button id=\"btnConceptPublish\" type=\"button\">Publish program_ref</button>\n"
" </div>\n"
" <div class=\"row\" style=\"margin-top:12px;\">\n"
" <button id=\"btnRun\" type=\"button\">Run program</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>\n"
" </main>\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 template = {\n"
" nodes: [\n"
" {\n"
" id: 1,\n"
" op: { name: 'pel.bytes.concat', version: 1 },\n"
" inputs: [ { external: { input_index: 0 } }, { external: { input_index: 1 } } ],\n"
" params_hex: ''\n"
" }\n"
" ],\n"
" roots: [ { node_id: 1, output_index: 0 } ]\n"
" };\n"
"\n"
" el('btnTemplate').addEventListener('click', () => {\n"
" el('program').value = JSON.stringify(template, null, 2);\n"
" });\n"
"\n"
" el('btnCreate').addEventListener('click', async () => {\n"
" try {\n"
" const body = JSON.parse(el('program').value || '{}');\n"
" const resp = await fetch('/v1/pel/programs', {\n"
" method: 'POST',\n"
" headers: { 'Content-Type': 'application/json' },\n"
" body: JSON.stringify(body)\n"
" });\n"
" const text = await resp.text();\n"
" out(text);\n"
" if (resp.ok) {\n"
" const j = JSON.parse(text);\n"
" if (j && j.program_ref) el('programRef').value = j.program_ref;\n"
" }\n"
" } catch (e) {\n"
" out(String(e));\n"
" }\n"
" });\n"
"\n"
" el('btnConceptCreate').addEventListener('click', async () => {\n"
" try {\n"
" const name = el('conceptName').value.trim();\n"
" const resp = await fetch('/v1/concepts', {\n"
" method: 'POST',\n"
" headers: { 'Content-Type': 'application/json' },\n"
" body: JSON.stringify({ name })\n"
" });\n"
" out(await resp.text());\n"
" } catch (e) {\n"
" out(String(e));\n"
" }\n"
" });\n"
"\n"
" el('btnConceptPublish').addEventListener('click', async () => {\n"
" try {\n"
" const name = el('conceptName').value.trim();\n"
" const ref = el('programRef').value.trim();\n"
" const resp = await fetch(`/v1/concepts/${encodeURIComponent(name)}/publish`, {\n"
" method: 'POST',\n"
" headers: { 'Content-Type': 'application/json' },\n"
" body: JSON.stringify({ ref })\n"
" });\n"
" out(await resp.text());\n"
" } catch (e) {\n"
" out(String(e));\n"
" }\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', {\n"
" method: 'POST',\n"
" headers: { 'Content-Type': 'application/json' },\n"
" body: JSON.stringify(body)\n"
" });\n"
" out(await resp.text());\n"
" } catch (e) {\n"
" out(String(e));\n"
" }\n"
" });\n"
"\n"
" el('btnTemplate').click();\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_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_seed_relation(store, cfg, "materializesAs",
&c->rel_materializes_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_read_exact(int fd, uint8_t *buf, size_t len) {
size_t off = 0;
while (off < len) {
ssize_t n = read(fd, buf + off, len - off);
if (n < 0) {
if (errno == EINTR) {
continue;
}
return false;
}
if (n == 0) {
return false;
}
off += (size_t)n;
}
return true;
}
static bool amduatd_read_line(int fd, char *buf, size_t cap, size_t *out_len) {
size_t len = 0;
while (len + 1 < cap) {
char c = 0;
ssize_t n = read(fd, &c, 1);
if (n < 0) {
if (errno == EINTR) {
continue;
}
return false;
}
if (n == 0) {
return false;
}
if (c == '\n') {
break;
}
buf[len++] = c;
}
if (len == 0) {
return false;
}
if (len > 0 && buf[len - 1] == '\r') {
len--;
}
buf[len] = '\0';
if (out_len != NULL) {
*out_len = len;
}
return true;
}
typedef struct {
char method[8];
char path[1024];
char content_type[128];
char accept[128];
char x_type_tag[64];
size_t content_length;
} amduatd_http_req_t;
static void amduatd_http_req_init(amduatd_http_req_t *req) {
memset(req, 0, sizeof(*req));
}
static bool amduatd_http_parse_request(int fd, amduatd_http_req_t *out_req) {
char line[2048];
size_t line_len = 0;
char *sp1 = NULL;
char *sp2 = NULL;
if (out_req == NULL) {
return false;
}
amduatd_http_req_init(out_req);
if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) {
return false;
}
sp1 = strchr(line, ' ');
if (sp1 == NULL) {
return false;
}
*sp1++ = '\0';
sp2 = strchr(sp1, ' ');
if (sp2 == NULL) {
return false;
}
*sp2++ = '\0';
if (strlen(line) >= sizeof(out_req->method)) {
return false;
}
strncpy(out_req->method, line, sizeof(out_req->method) - 1);
if (strlen(sp1) >= sizeof(out_req->path)) {
return false;
}
strncpy(out_req->path, sp1, sizeof(out_req->path) - 1);
for (;;) {
if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) {
return false;
}
if (line_len == 0) {
break;
}
if (strncasecmp(line, "Content-Length:", 15) == 0) {
const char *v = line + 15;
while (*v == ' ' || *v == '\t') {
v++;
}
out_req->content_length = (size_t)strtoull(v, NULL, 10);
} else if (strncasecmp(line, "Content-Type:", 13) == 0) {
const char *v = line + 13;
while (*v == ' ' || *v == '\t') {
v++;
}
strncpy(out_req->content_type, v, sizeof(out_req->content_type) - 1);
} else if (strncasecmp(line, "Accept:", 7) == 0) {
const char *v = line + 7;
while (*v == ' ' || *v == '\t') {
v++;
}
strncpy(out_req->accept, v, sizeof(out_req->accept) - 1);
} else if (strncasecmp(line, "X-Amduat-Type-Tag:", 18) == 0) {
const char *v = line + 18;
while (*v == ' ' || *v == '\t') {
v++;
}
strncpy(out_req->x_type_tag, v, sizeof(out_req->x_type_tag) - 1);
}
}
return true;
}
static bool amduatd_http_send_status(int fd,
int code,
const char *reason,
const char *content_type,
const uint8_t *body,
size_t body_len,
bool head_only) {
char hdr[512];
int n = snprintf(hdr,
sizeof(hdr),
"HTTP/1.1 %d %s\r\n"
"Content-Length: %zu\r\n"
"Content-Type: %s\r\n"
"Connection: close\r\n"
"\r\n",
code,
reason != NULL ? reason : "",
body_len,
content_type != NULL ? content_type : "text/plain");
if (n <= 0 || (size_t)n >= sizeof(hdr)) {
return false;
}
if (!amduatd_write_all(fd, (const uint8_t *)hdr, (size_t)n)) {
return false;
}
if (!head_only && body != NULL && body_len != 0) {
return amduatd_write_all(fd, body, body_len);
}
return true;
}
static bool amduatd_http_send_text(int fd,
int code,
const char *reason,
const char *text,
bool head_only) {
const uint8_t *body = (const uint8_t *)(text != NULL ? text : "");
size_t len = text != NULL ? strlen(text) : 0;
return amduatd_http_send_status(
fd, code, reason, "text/plain; charset=utf-8", body, len, head_only);
}
static bool amduatd_http_send_json(int fd,
int code,
const char *reason,
const char *json,
bool head_only) {
const uint8_t *body = (const uint8_t *)(json != NULL ? json : "{}");
size_t len = json != NULL ? strlen(json) : 2;
return amduatd_http_send_status(
fd, code, reason, "application/json", body, len, head_only);
}
static bool amduatd_http_req_wants_html(const amduatd_http_req_t *req) {
if (req == NULL) {
return false;
}
if (req->accept[0] == '\0') {
return false;
}
return strstr(req->accept, "text/html") != NULL;
}
static bool amduatd_http_send_not_found(int fd, const amduatd_http_req_t *req) {
if (amduatd_http_req_wants_html(req)) {
const char *path = (req != NULL && req->path[0] != '\0') ? req->path : "/";
const char *method =
(req != NULL && req->method[0] != '\0') ? req->method : "GET";
char html[4096];
int n = snprintf(
html,
sizeof(html),
"<!doctype html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"utf-8\" />\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
" <title>amduatd — Not Found</title>\n"
" <style>\n"
" :root{color-scheme:light dark;}\n"
" body{font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,Arial,sans-serif;"
" margin:0;line-height:1.4;}\n"
" .wrap{max-width:860px;margin:0 auto;padding:40px 20px;}\n"
" .card{border:1px solid rgba(127,127,127,.35);border-radius:14px;padding:22px;"
" background:rgba(127,127,127,.06);}\n"
" h1{margin:0 0 6px;font-size:22px;}\n"
" p{margin:8px 0;opacity:.9;}\n"
" code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
" font-size:13px;}\n"
" ul{margin:10px 0 0;padding-left:18px;}\n"
" a{color:inherit;}\n"
" .muted{opacity:.75;}\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <div class=\"wrap\">\n"
" <div class=\"card\">\n"
" <h1>404 — Not Found</h1>\n"
" <p>amduatd didnt recognize <code>%s %s</code>.</p>\n"
" <p class=\"muted\">Try one of these:</p>\n"
" <ul>\n"
" <li><a href=\"/v1/meta\">/v1/meta</a></li>\n"
" <li><a href=\"/v1/contract\">/v1/contract</a></li>\n"
" </ul>\n"
" <p class=\"muted\">Artifact bytes: <code>/v1/artifacts/&lt;ref&gt;</code></p>\n"
" </div>\n"
" </div>\n"
"</body>\n"
"</html>\n",
method,
path);
if (n <= 0 || (size_t)n >= sizeof(html)) {
return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false);
}
return amduatd_http_send_status(fd,
404,
"Not Found",
"text/html; charset=utf-8",
(const uint8_t *)html,
(size_t)n,
false);
}
return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false);
}
static const char *amduatd_query_param(const char *path,
const char *key,
char *out_value,
size_t out_cap) {
const char *q = strchr(path, '?');
size_t key_len = 0;
if (out_value != NULL && out_cap != 0) {
out_value[0] = '\0';
}
if (q == NULL || key == NULL || out_value == NULL || out_cap == 0) {
return NULL;
}
q++;
key_len = strlen(key);
while (*q != '\0') {
const char *k = q;
const char *eq = strchr(k, '=');
const char *amp = strchr(k, '&');
size_t klen = 0;
const char *v = NULL;
size_t vlen = 0;
if (amp == NULL) {
amp = q + strlen(q);
}
if (eq == NULL || eq > amp) {
q = (*amp == '&') ? (amp + 1) : amp;
continue;
}
klen = (size_t)(eq - k);
if (klen == key_len && strncmp(k, key, key_len) == 0) {
v = eq + 1;
vlen = (size_t)(amp - v);
if (vlen >= out_cap) {
vlen = out_cap - 1;
}
memcpy(out_value, v, vlen);
out_value[vlen] = '\0';
return out_value;
}
q = (*amp == '&') ? (amp + 1) : amp;
}
return NULL;
}
typedef struct {
char *data;
size_t len;
size_t cap;
} amduatd_strbuf_t;
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_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;
}
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;
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;
} else if (strcmp(format, "raw") == 0 || format[0] == '\0') {
want_artifact = 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) {
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;
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;
int status_code = 200;
const char *status_reason = "OK";
bool ok = false;
memset(&scheme_ref, 0, sizeof(scheme_ref));
memset(&program_ref, 0, sizeof(program_ref));
memset(&params_ref, 0, sizeof(params_ref));
memset(&run_result, 0, sizeof(run_result));
if (store == NULL || req == NULL) {
return amduatd_send_json_error(fd, 500, "Internal Server Error",
"internal error");
}
if (cfg == NULL || concepts == NULL) {
return amduatd_send_json_error(fd, 500, "Internal Server Error",
"internal error");
}
if (req->content_length > (1u * 1024u * 1024u)) {
return amduatd_send_json_error(fd, 413, "Payload Too Large",
"payload too large");
}
if (req->content_length == 0) {
return amduatd_send_json_error(fd, 400, "Bad Request", "missing body");
}
body = NULL;
if (req->content_length != 0) {
body = (uint8_t *)malloc(req->content_length);
if (body == NULL) {
return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
}
if (!amduatd_read_exact(fd, body, req->content_length)) {
free(body);
return false;
}
}
p = (const char *)body;
end = (const char *)body + req->content_length;
if (!amduatd_json_expect(&p, end, '{')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
goto pel_run_cleanup;
}
for (;;) {
const char *key = NULL;
size_t key_len = 0;
const char *sv = NULL;
size_t sv_len = 0;
const char *cur = NULL;
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid json key");
goto pel_run_cleanup;
}
if (!amduatd_json_expect(&p, end, ':')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
goto pel_run_cleanup;
}
if (key_len == strlen("program_ref") &&
memcmp(key, "program_ref", key_len) == 0) {
amduatd_ref_status_t st;
if (have_program_ref) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"duplicate program_ref");
goto pel_run_cleanup;
}
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid program_ref");
goto pel_run_cleanup;
}
st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, sv, sv_len,
&program_ref);
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
ok = amduatd_send_json_error(fd, 404, "Not Found",
"program_ref not found");
goto pel_run_cleanup;
}
if (st != AMDUATD_REF_OK) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid program_ref");
goto pel_run_cleanup;
}
have_program_ref = true;
} else if (key_len == strlen("scheme_ref") &&
memcmp(key, "scheme_ref", key_len) == 0) {
if (have_scheme_ref) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"duplicate scheme_ref");
goto pel_run_cleanup;
}
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid scheme_ref");
goto pel_run_cleanup;
}
if (sv_len == 3 && memcmp(sv, "dag", 3) == 0) {
scheme_ref = amduat_pel_program_dag_scheme_ref();
scheme_is_dag = true;
} else if (!amduatd_decode_ref_hex_str(sv, sv_len, &scheme_ref)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid scheme_ref");
goto pel_run_cleanup;
}
have_scheme_ref = true;
} else if (key_len == strlen("params_ref") &&
memcmp(key, "params_ref", key_len) == 0) {
amduatd_ref_status_t st;
if (has_params_ref) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"duplicate params_ref");
goto pel_run_cleanup;
}
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid params_ref");
goto pel_run_cleanup;
}
st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, sv, sv_len,
&params_ref);
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
ok = amduatd_send_json_error(fd, 404, "Not Found",
"params_ref not found");
goto pel_run_cleanup;
}
if (st != AMDUATD_REF_OK) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid params_ref");
goto pel_run_cleanup;
}
has_params_ref = true;
} else if (key_len == strlen("input_refs") &&
memcmp(key, "input_refs", key_len) == 0) {
size_t cap = 0;
if (have_input_refs) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"duplicate input_refs");
goto pel_run_cleanup;
}
if (!amduatd_json_expect(&p, end, '[')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid input_refs");
goto pel_run_cleanup;
}
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ']') {
p = cur + 1;
have_input_refs = true;
} else {
for (;;) {
amduat_reference_t ref;
memset(&ref, 0, sizeof(ref));
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid input_ref");
goto pel_run_cleanup;
}
{
amduatd_ref_status_t st = amduatd_decode_ref_or_name_latest(
store, cfg, concepts, sv, sv_len, &ref);
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
ok = amduatd_send_json_error(fd, 404, "Not Found",
"input_ref not found");
goto pel_run_cleanup;
}
if (st != AMDUATD_REF_OK) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid input_ref");
goto pel_run_cleanup;
}
}
if (input_refs_len == cap) {
size_t next_cap = cap != 0 ? cap * 2u : 4u;
amduat_reference_t *next;
if (next_cap > (SIZE_MAX / sizeof(*input_refs))) {
amduat_reference_free(&ref);
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"too many input_refs");
goto pel_run_cleanup;
}
next = (amduat_reference_t *)realloc(
input_refs, next_cap * sizeof(*input_refs));
if (next == NULL) {
amduat_reference_free(&ref);
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
"oom");
goto pel_run_cleanup;
}
input_refs = next;
cap = next_cap;
}
input_refs[input_refs_len++] = ref;
cur = amduatd_json_skip_ws(p, end);
if (cur >= end) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid input_refs");
goto pel_run_cleanup;
}
if (*cur == ',') {
p = cur + 1;
continue;
}
if (*cur == ']') {
p = cur + 1;
have_input_refs = true;
break;
}
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid input_refs");
goto pel_run_cleanup;
}
}
} else {
if (!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 (!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";
}
}
{
amduatd_strbuf_t resp;
char *result_hex = NULL;
char *trace_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 (!amduatd_strbuf_append_cstr(&resp, ",\"output_refs\":[")) {
amduatd_strbuf_free(&resp);
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
goto pel_run_cleanup;
}
for (i = 0; i < run_result.output_refs_len; ++i) {
char *out_hex = NULL;
if (!amduat_asl_ref_encode_hex(run_result.output_refs[i], &out_hex)) {
amduatd_strbuf_free(&resp);
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
"encode error");
goto pel_run_cleanup;
}
if (i != 0) {
if (!amduatd_strbuf_append_char(&resp, ',')) {
free(out_hex);
amduatd_strbuf_free(&resp);
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
goto pel_run_cleanup;
}
}
if (!amduatd_strbuf_append_cstr(&resp, "\"") ||
!amduatd_strbuf_append_cstr(&resp, out_hex) ||
!amduatd_strbuf_append_cstr(&resp, "\"")) {
free(out_hex);
amduatd_strbuf_free(&resp);
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
goto pel_run_cleanup;
}
free(out_hex);
}
if (!amduatd_strbuf_append_cstr(&resp, "],\"status\":\"") ||
!amduatd_strbuf_append_cstr(&resp, status) ||
!amduatd_strbuf_append_cstr(&resp, "\"}\n")) {
amduatd_strbuf_free(&resp);
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
goto pel_run_cleanup;
}
ok = amduatd_http_send_json(fd, status_code, status_reason, resp.data, false);
amduatd_strbuf_free(&resp);
}
pel_run_cleanup:
if (body != NULL) {
free(body);
}
if (run_result.has_result_value) {
amduat_enc_pel1_result_free(&run_result.result_value);
}
if (run_result.output_refs != NULL) {
amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len);
}
amduat_pel_surf_free_ref(&run_result.result_ref);
if (input_refs != NULL) {
size_t i;
for (i = 0; i < input_refs_len; ++i) {
amduat_reference_free(&input_refs[i]);
}
free(input_refs);
}
if (has_params_ref) {
amduat_reference_free(&params_ref);
}
if (have_program_ref) {
amduat_reference_free(&program_ref);
}
if (have_scheme_ref && !scheme_is_dag) {
amduat_reference_free(&scheme_ref);
}
return ok;
}
typedef struct {
amduat_pel_node_t *nodes;
size_t nodes_len;
amduat_pel_root_ref_t *roots;
size_t roots_len;
} amduatd_pel_program_tmp_t;
static void amduatd_pel_program_tmp_free(amduatd_pel_program_tmp_t *tmp) {
size_t i;
if (tmp == NULL) {
return;
}
if (tmp->nodes != NULL) {
for (i = 0; i < tmp->nodes_len; ++i) {
free(tmp->nodes[i].inputs);
tmp->nodes[i].inputs = NULL;
tmp->nodes[i].inputs_len = 0;
free((void *)tmp->nodes[i].params.data);
tmp->nodes[i].params.data = NULL;
tmp->nodes[i].params.len = 0;
}
}
free(tmp->nodes);
tmp->nodes = NULL;
tmp->nodes_len = 0;
free(tmp->roots);
tmp->roots = NULL;
tmp->roots_len = 0;
}
static bool amduatd_parse_params_hex(const char *s,
size_t len,
amduat_octets_t *out) {
char *tmp = NULL;
uint8_t *bytes = NULL;
size_t out_len = 0;
if (out != NULL) {
*out = amduat_octets(NULL, 0);
}
if (s == NULL || out == NULL) {
return false;
}
if (!amduatd_copy_json_str(s, len, &tmp)) {
return false;
}
if (tmp[0] == '\0') {
free(tmp);
*out = amduat_octets(NULL, 0);
return true;
}
if (!amduat_hex_decode_alloc(tmp, &bytes, &out_len)) {
free(tmp);
return false;
}
free(tmp);
*out = amduat_octets(bytes, out_len);
return true;
}
static bool amduatd_handle_post_pel_programs(int fd,
amduat_asl_store_t *store,
const amduatd_http_req_t *req) {
uint8_t *body = NULL;
const char *p = NULL;
const char *end = NULL;
amduatd_pel_program_tmp_t tmp;
amduat_pel_program_t program;
amduat_octets_t program_bytes;
amduat_artifact_t artifact;
amduat_reference_t program_ref;
char *ref_hex = NULL;
char json[2048];
bool ok = false;
memset(&tmp, 0, sizeof(tmp));
memset(&program, 0, sizeof(program));
program_bytes = amduat_octets(NULL, 0);
memset(&artifact, 0, sizeof(artifact));
memset(&program_ref, 0, sizeof(program_ref));
if (store == NULL || req == NULL) {
return amduatd_send_json_error(fd, 500, "Internal Server Error",
"internal error");
}
if (req->content_length == 0) {
return amduatd_send_json_error(fd, 400, "Bad Request", "missing body");
}
if (req->content_length > (2u * 1024u * 1024u)) {
return amduatd_send_json_error(fd, 413, "Payload Too Large",
"payload too large");
}
body = (uint8_t *)malloc(req->content_length);
if (body == NULL) {
return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
}
if (!amduatd_read_exact(fd, body, req->content_length)) {
free(body);
return false;
}
p = (const char *)body;
end = (const char *)body + req->content_length;
if (!amduatd_json_expect(&p, end, '{')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
goto pel_programs_cleanup;
}
{
bool have_nodes = false;
bool have_roots = false;
for (;;) {
const char *key = NULL;
size_t key_len = 0;
const char *cur = NULL;
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) ||
!amduatd_json_expect(&p, end, ':')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
goto pel_programs_cleanup;
}
if (key_len == strlen("nodes") && memcmp(key, "nodes", key_len) == 0) {
size_t cap = 0;
if (have_nodes) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"duplicate nodes");
goto pel_programs_cleanup;
}
if (!amduatd_json_expect(&p, end, '[')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid nodes");
goto pel_programs_cleanup;
}
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ']') {
p = cur + 1;
have_nodes = true;
} else {
for (;;) {
amduat_pel_node_t node;
bool have_id = false;
bool have_op = false;
bool have_inputs = false;
bool have_params_hex = false;
amduat_octets_t op_name = amduat_octets(NULL, 0);
uint32_t op_ver = 0;
memset(&node, 0, sizeof(node));
if (!amduatd_json_expect(&p, end, '{')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid node");
goto pel_programs_cleanup;
}
for (;;) {
const char *nk = NULL;
size_t nk_len = 0;
const char *sv = NULL;
size_t sv_len = 0;
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
if (!amduatd_json_parse_string_noesc(&p, end, &nk, &nk_len) ||
!amduatd_json_expect(&p, end, ':')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid node");
goto pel_programs_cleanup;
}
if (nk_len == strlen("id") && memcmp(nk, "id", nk_len) == 0) {
uint32_t id = 0;
if (have_id || !amduatd_json_parse_u32(&p, end, &id)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid node id");
goto pel_programs_cleanup;
}
node.id = (amduat_pel_node_id_t)id;
have_id = true;
} else if (nk_len == strlen("op") &&
memcmp(nk, "op", nk_len) == 0) {
bool have_name = false;
bool have_version = false;
if (have_op || !amduatd_json_expect(&p, end, '{')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid op");
goto pel_programs_cleanup;
}
for (;;) {
const char *okey = NULL;
size_t okey_len = 0;
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
if (!amduatd_json_parse_string_noesc(&p, end, &okey,
&okey_len) ||
!amduatd_json_expect(&p, end, ':')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid op");
goto pel_programs_cleanup;
}
if (okey_len == strlen("name") &&
memcmp(okey, "name", okey_len) == 0) {
if (have_name ||
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid op name");
goto pel_programs_cleanup;
}
op_name = amduat_octets(sv, sv_len);
have_name = true;
} else if (okey_len == strlen("version") &&
memcmp(okey, "version", okey_len) == 0) {
if (have_version ||
!amduatd_json_parse_u32(&p, end, &op_ver)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid op version");
goto pel_programs_cleanup;
}
have_version = true;
} else {
if (!amduatd_json_skip_value(&p, end, 0)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid op");
goto pel_programs_cleanup;
}
}
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ',') {
p = cur + 1;
continue;
}
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid op");
goto pel_programs_cleanup;
}
if (!have_name || !have_version) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"missing op fields");
goto pel_programs_cleanup;
}
node.op.name = op_name;
node.op.version = op_ver;
have_op = true;
} else if (nk_len == strlen("inputs") &&
memcmp(nk, "inputs", nk_len) == 0) {
size_t icap = 0;
if (have_inputs || !amduatd_json_expect(&p, end, '[')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid inputs");
goto pel_programs_cleanup;
}
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ']') {
p = cur + 1;
have_inputs = true;
} else {
for (;;) {
amduat_pel_dag_input_t in;
memset(&in, 0, sizeof(in));
if (!amduatd_json_expect(&p, end, '{')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid input");
goto pel_programs_cleanup;
}
{
const char *ik = NULL;
size_t ik_len = 0;
if (!amduatd_json_parse_string_noesc(&p, end, &ik,
&ik_len) ||
!amduatd_json_expect(&p, end, ':') ||
!amduatd_json_expect(&p, end, '{')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid input");
goto pel_programs_cleanup;
}
if (ik_len == strlen("external") &&
memcmp(ik, "external", ik_len) == 0) {
uint32_t idx = 0;
const char *k2 = NULL;
size_t k2_len = 0;
if (!amduatd_json_parse_string_noesc(&p, end, &k2,
&k2_len) ||
k2_len != strlen("input_index") ||
memcmp(k2, "input_index", k2_len) != 0 ||
!amduatd_json_expect(&p, end, ':') ||
!amduatd_json_parse_u32(&p, end, &idx) ||
!amduatd_json_expect(&p, end, '}')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid external input");
goto pel_programs_cleanup;
}
in.kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL;
in.value.external.input_index = idx;
} else if (ik_len == strlen("node") &&
memcmp(ik, "node", ik_len) == 0) {
bool have_node_id = false;
bool have_output_index = false;
uint32_t nid = 0;
uint32_t oidx = 0;
for (;;) {
const char *k2 = NULL;
size_t k2_len = 0;
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
if (!amduatd_json_parse_string_noesc(&p, end, &k2,
&k2_len) ||
!amduatd_json_expect(&p, end, ':')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid node input");
goto pel_programs_cleanup;
}
if (k2_len == strlen("node_id") &&
memcmp(k2, "node_id", k2_len) == 0) {
if (have_node_id ||
!amduatd_json_parse_u32(&p, end, &nid)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid node_id");
goto pel_programs_cleanup;
}
have_node_id = true;
} else if (k2_len == strlen("output_index") &&
memcmp(k2, "output_index", k2_len) == 0) {
if (have_output_index ||
!amduatd_json_parse_u32(&p, end, &oidx)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid output_index");
goto pel_programs_cleanup;
}
have_output_index = true;
} else {
if (!amduatd_json_skip_value(&p, end, 0)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid node input");
goto pel_programs_cleanup;
}
}
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ',') {
p = cur + 1;
continue;
}
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid node input");
goto pel_programs_cleanup;
}
if (!have_node_id || !have_output_index) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"missing node input fields");
goto pel_programs_cleanup;
}
in.kind = AMDUAT_PEL_DAG_INPUT_NODE;
in.value.node.node_id = (amduat_pel_node_id_t)nid;
in.value.node.output_index = oidx;
} else {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid input kind");
goto pel_programs_cleanup;
}
if (!amduatd_json_expect(&p, end, '}')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid input");
goto pel_programs_cleanup;
}
}
if (node.inputs_len == icap) {
size_t next_cap = icap != 0 ? icap * 2u : 4u;
amduat_pel_dag_input_t *next =
(amduat_pel_dag_input_t *)realloc(
node.inputs, next_cap * sizeof(*node.inputs));
if (next == NULL) {
ok = amduatd_send_json_error(fd, 500,
"Internal Server Error",
"oom");
goto pel_programs_cleanup;
}
node.inputs = next;
icap = next_cap;
}
node.inputs[node.inputs_len++] = in;
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ',') {
p = cur + 1;
continue;
}
if (cur < end && *cur == ']') {
p = cur + 1;
have_inputs = true;
break;
}
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid inputs");
goto pel_programs_cleanup;
}
}
} else if (nk_len == strlen("params_hex") &&
memcmp(nk, "params_hex", nk_len) == 0) {
if (have_params_hex ||
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
!amduatd_parse_params_hex(sv, sv_len, &node.params)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid params_hex");
goto pel_programs_cleanup;
}
have_params_hex = true;
} else {
if (!amduatd_json_skip_value(&p, end, 0)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid node");
goto pel_programs_cleanup;
}
}
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ',') {
p = cur + 1;
continue;
}
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
}
if (!have_id || !have_op || !have_inputs || !have_params_hex) {
free(node.inputs);
free((void *)node.params.data);
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"missing node fields");
goto pel_programs_cleanup;
}
if (amduat_pel_kernel_op_lookup(node.op.name, node.op.version) ==
NULL) {
free(node.inputs);
free((void *)node.params.data);
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"unknown op");
goto pel_programs_cleanup;
}
if (tmp.nodes_len == cap) {
size_t next_cap = cap != 0 ? cap * 2u : 4u;
amduat_pel_node_t *next =
(amduat_pel_node_t *)realloc(tmp.nodes,
next_cap * sizeof(*tmp.nodes));
if (next == NULL) {
free(node.inputs);
free((void *)node.params.data);
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
"oom");
goto pel_programs_cleanup;
}
tmp.nodes = next;
cap = next_cap;
}
tmp.nodes[tmp.nodes_len++] = node;
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ',') {
p = cur + 1;
continue;
}
if (cur < end && *cur == ']') {
p = cur + 1;
have_nodes = true;
break;
}
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid nodes");
goto pel_programs_cleanup;
}
}
} else if (key_len == strlen("roots") &&
memcmp(key, "roots", key_len) == 0) {
size_t cap = 0;
if (have_roots) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"duplicate roots");
goto pel_programs_cleanup;
}
if (!amduatd_json_expect(&p, end, '[')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid roots");
goto pel_programs_cleanup;
}
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ']') {
p = cur + 1;
have_roots = true;
} else {
for (;;) {
amduat_pel_root_ref_t root;
bool have_node_id = false;
bool have_output_index = false;
memset(&root, 0, sizeof(root));
if (!amduatd_json_expect(&p, end, '{')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid root");
goto pel_programs_cleanup;
}
for (;;) {
const char *rk = NULL;
size_t rk_len = 0;
uint32_t v = 0;
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
if (!amduatd_json_parse_string_noesc(&p, end, &rk, &rk_len) ||
!amduatd_json_expect(&p, end, ':')) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid root");
goto pel_programs_cleanup;
}
if (rk_len == strlen("node_id") &&
memcmp(rk, "node_id", rk_len) == 0) {
if (have_node_id || !amduatd_json_parse_u32(&p, end, &v)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid root node_id");
goto pel_programs_cleanup;
}
root.node_id = (amduat_pel_node_id_t)v;
have_node_id = true;
} else if (rk_len == strlen("output_index") &&
memcmp(rk, "output_index", rk_len) == 0) {
if (have_output_index || !amduatd_json_parse_u32(&p, end, &v)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid root output_index");
goto pel_programs_cleanup;
}
root.output_index = v;
have_output_index = true;
} else {
if (!amduatd_json_skip_value(&p, end, 0)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid root");
goto pel_programs_cleanup;
}
}
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ',') {
p = cur + 1;
continue;
}
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid root");
goto pel_programs_cleanup;
}
if (!have_node_id || !have_output_index) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"missing root fields");
goto pel_programs_cleanup;
}
if (tmp.roots_len == cap) {
size_t next_cap = cap != 0 ? cap * 2u : 2u;
amduat_pel_root_ref_t *next =
(amduat_pel_root_ref_t *)realloc(
tmp.roots, next_cap * sizeof(*tmp.roots));
if (next == NULL) {
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
"oom");
goto pel_programs_cleanup;
}
tmp.roots = next;
cap = next_cap;
}
tmp.roots[tmp.roots_len++] = root;
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ',') {
p = cur + 1;
continue;
}
if (cur < end && *cur == ']') {
p = cur + 1;
have_roots = true;
break;
}
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"invalid roots");
goto pel_programs_cleanup;
}
}
} else {
if (!amduatd_json_skip_value(&p, end, 0)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
goto pel_programs_cleanup;
}
}
cur = amduatd_json_skip_ws(p, end);
if (cur < end && *cur == ',') {
p = cur + 1;
continue;
}
if (cur < end && *cur == '}') {
p = cur + 1;
break;
}
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
goto pel_programs_cleanup;
}
p = amduatd_json_skip_ws(p, end);
if (p != end) {
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
goto pel_programs_cleanup;
}
if (!have_nodes || !have_roots) {
ok = amduatd_send_json_error(fd, 400, "Bad Request",
"missing nodes/roots");
goto pel_programs_cleanup;
}
}
program.nodes = tmp.nodes;
program.nodes_len = tmp.nodes_len;
program.roots = tmp.roots;
program.roots_len = tmp.roots_len;
if (!amduat_pel_program_dag_validate(&program)) {
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid program");
goto pel_programs_cleanup;
}
if (!amduat_enc_pel_program_dag_encode_v1(&program, &program_bytes)) {
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
"encode error");
goto pel_programs_cleanup;
}
artifact = amduat_artifact_with_type(program_bytes,
amduat_type_tag(AMDUAT_PEL_TYPE_TAG_PROGRAM_DAG_1));
if (amduat_asl_store_put(store, artifact, &program_ref) != AMDUAT_ASL_STORE_OK) {
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
"store error");
goto pel_programs_cleanup;
}
if (!amduat_asl_ref_encode_hex(program_ref, &ref_hex)) {
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
"encode ref error");
goto pel_programs_cleanup;
}
{
int n = snprintf(json, sizeof(json), "{\"program_ref\":\"%s\"}\n", ref_hex);
free(ref_hex);
if (n <= 0 || (size_t)n >= sizeof(json)) {
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
goto pel_programs_cleanup;
}
ok = amduatd_http_send_json(fd, 200, "OK", json, false);
goto pel_programs_cleanup;
}
pel_programs_cleanup:
free(body);
free((void *)program_bytes.data);
amduat_reference_free(&program_ref);
amduatd_pel_program_tmp_free(&tmp);
return ok;
}
static bool amduatd_handle_get_ui(int fd) {
return amduatd_http_send_status(fd,
200,
"OK",
"text/html; charset=utf-8",
(const uint8_t *)k_amduatd_ui_html,
strlen(k_amduatd_ui_html),
false);
}
static bool amduatd_handle_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;
}
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,
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);
}
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/concepts") == 0) {
return amduatd_handle_post_concepts(fd, store, cfg, concepts, &req);
}
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;
amduatd_concepts_t concepts;
int i;
int sfd = -1;
memset(&api_contract_ref, 0, sizeof(api_contract_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_concepts_init(&concepts, &store, &cfg, root)) {
fprintf(stderr, "error: failed to init concept edges\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, &concepts);
(void)close(cfd);
}
amduat_reference_free(&api_contract_ref);
amduatd_concepts_free(&concepts);
(void)unlink(sock_path);
(void)close(sfd);
return 0;
}