10478 lines
383 KiB
C
10478 lines
383 KiB
C
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "amduat/asl/artifact_io.h"
|
|
#include "amduat/asl/collection.h"
|
|
#include "amduat/asl/collection_view.h"
|
|
#include "amduat/asl/none.h"
|
|
#include "amduat/asl/record.h"
|
|
#include "amduat/asl/asl_pointer_fs.h"
|
|
#include "amduat/asl/asl_store_fs.h"
|
|
#include "amduat/asl/asl_store_fs_meta.h"
|
|
#include "amduat/asl/log_store.h"
|
|
#include "amduat/asl/ref_text.h"
|
|
#include "amduat/asl/store.h"
|
|
#include "amduat/asl/ref_derive.h"
|
|
#include "amduat/enc/asl1_core_codec.h"
|
|
#include "amduat/enc/asl_log.h"
|
|
#include "amduat/enc/fer1_receipt.h"
|
|
#include "amduat/fed/replay.h"
|
|
#include "amduat/enc/pel1_result.h"
|
|
#include "amduat/enc/pel_program_dag.h"
|
|
#include "amduat/enc/tgk1_edge.h"
|
|
#include "amduat/fer/receipt.h"
|
|
#include "amduat/format/pel.h"
|
|
#include "amduat/tgk/core.h"
|
|
#include "federation/coord.h"
|
|
#include "federation/transport_stub.h"
|
|
#include "federation/transport_unix.h"
|
|
#include "amduat/pel/program_dag.h"
|
|
#include "amduat/pel/program_dag_desc.h"
|
|
#include "amduat/pel/opreg_kernel.h"
|
|
#include "amduat/pel/run.h"
|
|
#include "amduat/util/hex.h"
|
|
#include "amduat/util/log.h"
|
|
#include "amduat/hash/asl1.h"
|
|
#include "amduatd_concepts.h"
|
|
#include "amduatd_http.h"
|
|
#include "amduatd_ui.h"
|
|
#include "amduatd_caps.h"
|
|
#include "amduatd_space.h"
|
|
#include "amduatd_fed.h"
|
|
#include "amduatd_fed_cursor.h"
|
|
#include "amduatd_fed_pull_plan.h"
|
|
#include "amduatd_fed_push_plan.h"
|
|
#include "amduatd_fed_push_apply.h"
|
|
#include "amduatd_fed_pull_apply.h"
|
|
#include "amduatd_fed_until.h"
|
|
#include "amduatd_store.h"
|
|
#include "amduatd_derivation_index.h"
|
|
#include "amduatd_space_doctor.h"
|
|
#include "amduatd_space_roots.h"
|
|
#include "amduatd_space_manifest.h"
|
|
#include "amduatd_space_mounts.h"
|
|
#include "amduatd_space_mounts_sync.h"
|
|
#include "amduatd_space_workspace.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 <time.h>
|
|
#include <pwd.h>
|
|
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#include <unistd.h>
|
|
|
|
typedef struct amduatd_strbuf {
|
|
char *data;
|
|
size_t len;
|
|
size_t cap;
|
|
} amduatd_strbuf_t;
|
|
|
|
amduatd_ref_status_t amduatd_decode_ref_or_name_latest(
|
|
amduat_asl_store_t *store,
|
|
const amduat_asl_store_fs_config_t *cfg,
|
|
const amduatd_concepts_t *concepts,
|
|
const amduatd_cfg_t *dcfg,
|
|
const char *s,
|
|
size_t len,
|
|
amduat_reference_t *out_ref);
|
|
|
|
#if AMDUATD_ENABLE_UI
|
|
bool amduatd_seed_ui_html(amduat_asl_store_t *store,
|
|
const amduat_asl_store_fs_config_t *cfg,
|
|
amduat_reference_t *out_ref);
|
|
#endif
|
|
|
|
bool amduatd_seed_ms_ui_state(amduat_asl_store_t *store,
|
|
amduatd_concepts_t *concepts,
|
|
const amduatd_cfg_t *dcfg);
|
|
|
|
static uint64_t amduatd_now_ms(void) {
|
|
struct timespec ts;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
|
|
return 0;
|
|
}
|
|
return ((uint64_t)ts.tv_sec * 1000u) + ((uint64_t)ts.tv_nsec / 1000000u);
|
|
}
|
|
|
|
static void amduatd_strbuf_free(amduatd_strbuf_t *b);
|
|
static bool amduatd_strbuf_append_cstr(amduatd_strbuf_t *b, const char *s);
|
|
static bool amduatd_strbuf_append_char(amduatd_strbuf_t *b, char c);
|
|
static bool amduatd_parse_u64_str(const char *s, uint64_t *out);
|
|
static bool amduatd_fed_pull_unix_get_records(void *ctx,
|
|
uint32_t domain_id,
|
|
uint64_t from_logseq,
|
|
uint64_t limit,
|
|
int *out_status,
|
|
amduat_fed_record_t **out_records,
|
|
size_t *out_len,
|
|
char **out_body);
|
|
static bool amduatd_fed_pull_unix_get_artifact(void *ctx,
|
|
amduat_reference_t ref,
|
|
int *out_status,
|
|
amduat_octets_t *out_bytes,
|
|
char **out_body);
|
|
|
|
static const char *const AMDUATD_DEFAULT_ROOT = ".amduat-asl";
|
|
static const char *const AMDUATD_DEFAULT_SOCK = "amduatd.sock";
|
|
static const uint64_t AMDUATD_FED_TICK_MS = 1000u;
|
|
static const size_t AMDUATD_FED_INGEST_MAX_BYTES = 8u * 1024u * 1024u;
|
|
static const size_t AMDUATD_REF_TEXT_MAX = 256u;
|
|
static const size_t AMDUATD_SPACE_MANIFEST_MAX_BYTES = 64u * 1024u;
|
|
static const char k_amduatd_workspace_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 workspace</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;background:#0f1216;color:#e7edf4;}\n"
|
|
" .wrap{max-width:980px;margin:0 auto;padding:28px 20px 60px;}\n"
|
|
" h1{margin:0 0 12px;font-size:22px;letter-spacing:.02em;}\n"
|
|
" h2{margin:18px 0 10px;font-size:16px;}\n"
|
|
" p{margin:8px 0;color:#c9d2dc;}\n"
|
|
" .bar{display:flex;gap:12px;flex-wrap:wrap;align-items:center;"
|
|
" padding:12px 14px;border:1px solid rgba(120,130,140,.4);"
|
|
" border-radius:12px;background:rgba(120,130,140,.08);}\n"
|
|
" label{font-size:13px;color:#c9d2dc;}\n"
|
|
" input{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;"
|
|
" font-size:13px;padding:6px 8px;border-radius:8px;border:1px solid rgba(120,130,140,.4);"
|
|
" background:#141922;color:#e7edf4;min-width:200px;}\n"
|
|
" button{font-size:13px;padding:7px 12px;border-radius:8px;border:1px solid #5b6b7c;"
|
|
" background:#1b232f;color:#e7edf4;cursor:pointer;}\n"
|
|
" button:disabled{opacity:.6;cursor:default;}\n"
|
|
" .grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;}\n"
|
|
" .card{padding:14px;border-radius:12px;border:1px solid rgba(120,130,140,.4);"
|
|
" background:rgba(120,130,140,.06);}\n"
|
|
" .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:13px;}\n"
|
|
" table{width:100%;border-collapse:collapse;font-size:13px;}\n"
|
|
" th,td{border-bottom:1px solid rgba(120,130,140,.3);padding:8px 6px;text-align:left;}\n"
|
|
" th{color:#c9d2dc;font-weight:600;}\n"
|
|
" .muted{color:#9aa6b2;font-size:12px;}\n"
|
|
" pre{margin:8px 0 0;padding:10px;border-radius:8px;background:#12161e;color:#e7edf4;overflow:auto;}\n"
|
|
" details{margin-top:8px;}\n"
|
|
" summary{cursor:pointer;color:#c9d2dc;font-size:13px;}\n"
|
|
" .status-ok{color:#7dd56f;}\n"
|
|
" .status-bad{color:#f09a8d;}\n"
|
|
" @media (max-width:760px){.grid{grid-template-columns:1fr;}}\n"
|
|
" </style>\n"
|
|
"</head>\n"
|
|
"<body>\n"
|
|
" <div class=\"wrap\">\n"
|
|
" <h1>Workspace snapshot</h1>\n"
|
|
" <p class=\"muted\">Local, read-only view of the effective space.</p>\n"
|
|
" <div class=\"bar\">\n"
|
|
" <label for=\"space\">Space (optional)</label>\n"
|
|
" <input id=\"space\" placeholder=\"space-id\" />\n"
|
|
" <button id=\"refresh\">Refresh</button>\n"
|
|
" <button id=\"sync\">Sync mounts</button>\n"
|
|
" <span id=\"syncing\" class=\"muted\" hidden>syncing...</span>\n"
|
|
" <span id=\"space_error\" class=\"muted\" hidden>invalid space id (no \"/\" or \"..\")</span>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <div id=\"message\" class=\"card\" style=\"margin-top:16px;\" hidden></div>\n"
|
|
"\n"
|
|
" <div class=\"grid\" style=\"margin-top:16px;\">\n"
|
|
" <div class=\"card\">\n"
|
|
" <h2>Effective space</h2>\n"
|
|
" <div id=\"effective\" class=\"mono\">-</div>\n"
|
|
" <h2>Store backend</h2>\n"
|
|
" <div id=\"backend\" class=\"mono\">-</div>\n"
|
|
" <h2>Federation</h2>\n"
|
|
" <div id=\"federation\" class=\"mono\">-</div>\n"
|
|
" <h2>Manifest ref</h2>\n"
|
|
" <div id=\"manifest_ref\" class=\"mono\">-</div>\n"
|
|
" </div>\n"
|
|
" <div class=\"card\">\n"
|
|
" <h2>Store capabilities</h2>\n"
|
|
" <div id=\"capabilities\" class=\"mono\">-</div>\n"
|
|
" <h2>Sync result</h2>\n"
|
|
" <div id=\"sync_summary\" class=\"mono\">-</div>\n"
|
|
" <details id=\"sync_details\" hidden>\n"
|
|
" <summary>Full sync response</summary>\n"
|
|
" <pre id=\"sync_raw\"></pre>\n"
|
|
" </details>\n"
|
|
" </div>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <div class=\"card\" style=\"margin-top:16px;\">\n"
|
|
" <h2>Mounts</h2>\n"
|
|
" <table id=\"mounts_table\">\n"
|
|
" <thead>\n"
|
|
" <tr>\n"
|
|
" <th>Name</th>\n"
|
|
" <th>Peer</th>\n"
|
|
" <th>Remote space</th>\n"
|
|
" <th>Mode</th>\n"
|
|
" <th>Cursor</th>\n"
|
|
" <th>Status</th>\n"
|
|
" </tr>\n"
|
|
" </thead>\n"
|
|
" <tbody id=\"mounts_body\"></tbody>\n"
|
|
" </table>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <div class=\"card\" style=\"margin-top:16px;\">\n"
|
|
" <h2>Raw workspace JSON</h2>\n"
|
|
" <pre id=\"workspace_raw\"></pre>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <div class=\"card\" style=\"margin-top:16px;\">\n"
|
|
" <h2>Health / Debug</h2>\n"
|
|
" <p class=\"muted\">Fetch read-only diagnostics for the current space.</p>\n"
|
|
"\n"
|
|
" <div class=\"card\" style=\"margin-top:12px;\">\n"
|
|
" <h2>Doctor</h2>\n"
|
|
" <div class=\"bar\">\n"
|
|
" <button id=\"doctor_fetch\">Fetch</button>\n"
|
|
" <span id=\"doctor_status\" class=\"muted\">idle</span>\n"
|
|
" <span id=\"doctor_summary\" class=\"mono\"></span>\n"
|
|
" </div>\n"
|
|
" <details id=\"doctor_details\" hidden>\n"
|
|
" <summary>Raw JSON</summary>\n"
|
|
" <pre id=\"doctor_raw\"></pre>\n"
|
|
" </details>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <div class=\"card\" style=\"margin-top:12px;\">\n"
|
|
" <h2>Roots</h2>\n"
|
|
" <div class=\"bar\">\n"
|
|
" <button id=\"roots_fetch\">Fetch</button>\n"
|
|
" <span id=\"roots_status\" class=\"muted\">idle</span>\n"
|
|
" <span id=\"roots_summary\" class=\"mono\"></span>\n"
|
|
" </div>\n"
|
|
" <div id=\"roots_list\" class=\"mono\" style=\"margin-top:8px;\"></div>\n"
|
|
" <details id=\"roots_details\" hidden>\n"
|
|
" <summary>Raw JSON</summary>\n"
|
|
" <pre id=\"roots_raw\"></pre>\n"
|
|
" </details>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <div class=\"card\" style=\"margin-top:12px;\">\n"
|
|
" <h2>Sync status</h2>\n"
|
|
" <div class=\"bar\">\n"
|
|
" <button id=\"sync_status_fetch\">Fetch</button>\n"
|
|
" <span id=\"sync_status\" class=\"muted\">idle</span>\n"
|
|
" <span id=\"sync_status_summary\" class=\"mono\"></span>\n"
|
|
" </div>\n"
|
|
" <div id=\"sync_status_table\" style=\"margin-top:8px;\"></div>\n"
|
|
" <details id=\"sync_status_details\" hidden>\n"
|
|
" <summary>Raw JSON</summary>\n"
|
|
" <pre id=\"sync_status_raw\"></pre>\n"
|
|
" </details>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <div class=\"card\" style=\"margin-top:12px;\">\n"
|
|
" <h2>Mounts resolve</h2>\n"
|
|
" <div class=\"bar\">\n"
|
|
" <button id=\"mounts_resolve_fetch\">Fetch</button>\n"
|
|
" <span id=\"mounts_resolve_status\" class=\"muted\">idle</span>\n"
|
|
" <span id=\"mounts_resolve_summary\" class=\"mono\"></span>\n"
|
|
" </div>\n"
|
|
" <details id=\"mounts_resolve_details\" hidden>\n"
|
|
" <summary>Raw JSON</summary>\n"
|
|
" <pre id=\"mounts_resolve_raw\"></pre>\n"
|
|
" </details>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <div class=\"card\" style=\"margin-top:12px;\">\n"
|
|
" <h2>Manifest</h2>\n"
|
|
" <div class=\"bar\">\n"
|
|
" <button id=\"manifest_fetch\">Fetch</button>\n"
|
|
" <span id=\"manifest_status\" class=\"muted\">idle</span>\n"
|
|
" <span id=\"manifest_summary\" class=\"mono\"></span>\n"
|
|
" </div>\n"
|
|
" <details id=\"manifest_details\" hidden>\n"
|
|
" <summary>Raw JSON</summary>\n"
|
|
" <pre id=\"manifest_raw\"></pre>\n"
|
|
" </details>\n"
|
|
" </div>\n"
|
|
" </div>\n"
|
|
" </div>\n"
|
|
"\n"
|
|
" <script>\n"
|
|
" const spaceInput = document.getElementById('space');\n"
|
|
" const spaceError = document.getElementById('space_error');\n"
|
|
" const message = document.getElementById('message');\n"
|
|
" const refreshBtn = document.getElementById('refresh');\n"
|
|
" const syncBtn = document.getElementById('sync');\n"
|
|
" const syncing = document.getElementById('syncing');\n"
|
|
" const effectiveEl = document.getElementById('effective');\n"
|
|
" const backendEl = document.getElementById('backend');\n"
|
|
" const federationEl = document.getElementById('federation');\n"
|
|
" const manifestRefEl = document.getElementById('manifest_ref');\n"
|
|
" const capabilitiesEl = document.getElementById('capabilities');\n"
|
|
" const mountsBody = document.getElementById('mounts_body');\n"
|
|
" const workspaceRaw = document.getElementById('workspace_raw');\n"
|
|
" const syncSummary = document.getElementById('sync_summary');\n"
|
|
" const syncDetails = document.getElementById('sync_details');\n"
|
|
" const syncRaw = document.getElementById('sync_raw');\n"
|
|
" const doctorFetch = document.getElementById('doctor_fetch');\n"
|
|
" const doctorStatus = document.getElementById('doctor_status');\n"
|
|
" const doctorSummary = document.getElementById('doctor_summary');\n"
|
|
" const doctorDetails = document.getElementById('doctor_details');\n"
|
|
" const doctorRaw = document.getElementById('doctor_raw');\n"
|
|
" const rootsFetch = document.getElementById('roots_fetch');\n"
|
|
" const rootsStatus = document.getElementById('roots_status');\n"
|
|
" const rootsSummary = document.getElementById('roots_summary');\n"
|
|
" const rootsList = document.getElementById('roots_list');\n"
|
|
" const rootsDetails = document.getElementById('roots_details');\n"
|
|
" const rootsRaw = document.getElementById('roots_raw');\n"
|
|
" const syncStatusFetch = document.getElementById('sync_status_fetch');\n"
|
|
" const syncStatus = document.getElementById('sync_status');\n"
|
|
" const syncStatusSummary = document.getElementById('sync_status_summary');\n"
|
|
" const syncStatusTable = document.getElementById('sync_status_table');\n"
|
|
" const syncStatusDetails = document.getElementById('sync_status_details');\n"
|
|
" const syncStatusRaw = document.getElementById('sync_status_raw');\n"
|
|
" const mountsResolveFetch = document.getElementById('mounts_resolve_fetch');\n"
|
|
" const mountsResolveStatus = document.getElementById('mounts_resolve_status');\n"
|
|
" const mountsResolveSummary = document.getElementById('mounts_resolve_summary');\n"
|
|
" const mountsResolveDetails = document.getElementById('mounts_resolve_details');\n"
|
|
" const mountsResolveRaw = document.getElementById('mounts_resolve_raw');\n"
|
|
" const manifestFetch = document.getElementById('manifest_fetch');\n"
|
|
" const manifestStatus = document.getElementById('manifest_status');\n"
|
|
" const manifestSummary = document.getElementById('manifest_summary');\n"
|
|
" const manifestDetails = document.getElementById('manifest_details');\n"
|
|
" const manifestRaw = document.getElementById('manifest_raw');\n"
|
|
"\n"
|
|
" function getSpaceHeader() {\n"
|
|
" const raw = spaceInput.value.trim();\n"
|
|
" if (!raw) {\n"
|
|
" spaceError.hidden = true;\n"
|
|
" return null;\n"
|
|
" }\n"
|
|
" if (raw.includes('/') || raw.includes('..')) {\n"
|
|
" spaceError.hidden = false;\n"
|
|
" return null;\n"
|
|
" }\n"
|
|
" spaceError.hidden = true;\n"
|
|
" return raw;\n"
|
|
" }\n"
|
|
"\n"
|
|
" function clearMessage() {\n"
|
|
" message.hidden = true;\n"
|
|
" message.textContent = '';\n"
|
|
" }\n"
|
|
"\n"
|
|
" function showMessage(text) {\n"
|
|
" message.hidden = false;\n"
|
|
" message.textContent = text;\n"
|
|
" }\n"
|
|
"\n"
|
|
" function setMonospaceText(el, text) {\n"
|
|
" el.textContent = text || '-';\n"
|
|
" }\n"
|
|
"\n"
|
|
" function renderCapabilities(caps) {\n"
|
|
" if (!caps || !caps.supported_ops) {\n"
|
|
" setMonospaceText(capabilitiesEl, '-');\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" const keys = ['put','get','put_indexed','get_indexed','tombstone','tombstone_lift','log_scan','current_state','validate_config'];\n"
|
|
" const lines = keys.map((key) => `${key}: ${caps.supported_ops[key] === true ? 'true' : 'false'}`);\n"
|
|
" capabilitiesEl.textContent = lines.join('\\n');\n"
|
|
" if (caps.implemented_ops) {\n"
|
|
" const impl = keys.map((key) => `${key}: ${caps.implemented_ops[key] === true ? 'true' : 'false'}`);\n"
|
|
" const details = document.createElement('details');\n"
|
|
" const summary = document.createElement('summary');\n"
|
|
" const pre = document.createElement('pre');\n"
|
|
" summary.textContent = 'Implemented ops (debug)';\n"
|
|
" pre.textContent = impl.join('\\n');\n"
|
|
" details.appendChild(summary);\n"
|
|
" details.appendChild(pre);\n"
|
|
" capabilitiesEl.appendChild(document.createElement('div'));\n"
|
|
" capabilitiesEl.appendChild(details);\n"
|
|
" }\n"
|
|
" }\n"
|
|
"\n"
|
|
" function renderMounts(mounts) {\n"
|
|
" mountsBody.innerHTML = '';\n"
|
|
" if (!Array.isArray(mounts)) {\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" mounts.forEach((mount) => {\n"
|
|
" const row = document.createElement('tr');\n"
|
|
" const cursor = mount.mode === 'track' ? mount.tracking : null;\n"
|
|
" const cursorText = cursor && cursor.pull_cursor\n"
|
|
" ? `present=${cursor.pull_cursor.present}` +\n"
|
|
" (cursor.pull_cursor.last_logseq !== undefined ? ` last_logseq=${cursor.pull_cursor.last_logseq}` : '')\n"
|
|
" : 'present=false';\n"
|
|
" const notes = mount.status && Array.isArray(mount.status.notes)\n"
|
|
" ? mount.status.notes.join(', ')\n"
|
|
" : '';\n"
|
|
" const statusText = mount.status && mount.status.ok === true ? 'ok' : 'not-ok';\n"
|
|
" row.innerHTML = `\n"
|
|
" <td class=\"mono\">${mount.name || ''}</td>\n"
|
|
" <td class=\"mono\">${mount.peer_key || ''}</td>\n"
|
|
" <td class=\"mono\">${mount.remote_space_id || ''}</td>\n"
|
|
" <td>${mount.mode || ''}</td>\n"
|
|
" <td class=\"mono\">${cursorText}</td>\n"
|
|
" <td class=\"mono\">${statusText}${notes ? ` (${notes})` : ''}</td>\n"
|
|
" `;\n"
|
|
" mountsBody.appendChild(row);\n"
|
|
" });\n"
|
|
" }\n"
|
|
"\n"
|
|
" function truncateBody(text, limit) {\n"
|
|
" if (!text) {\n"
|
|
" return '';\n"
|
|
" }\n"
|
|
" if (text.length <= limit) {\n"
|
|
" return text;\n"
|
|
" }\n"
|
|
" return text.slice(0, limit) + '...';\n"
|
|
" }\n"
|
|
"\n"
|
|
" async function fetchPanel(endpoint, statusEl, summaryEl, rawEl, detailsEl, onData) {\n"
|
|
" clearMessage();\n"
|
|
" statusEl.textContent = 'loading';\n"
|
|
" summaryEl.textContent = '';\n"
|
|
" if (detailsEl) {\n"
|
|
" detailsEl.hidden = true;\n"
|
|
" }\n"
|
|
" if (rawEl) {\n"
|
|
" rawEl.textContent = '';\n"
|
|
" }\n"
|
|
" const space = getSpaceHeader();\n"
|
|
" if (spaceError.hidden === false) {\n"
|
|
" statusEl.textContent = 'error';\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" const headers = {};\n"
|
|
" if (space) {\n"
|
|
" headers['X-Amduat-Space'] = space;\n"
|
|
" }\n"
|
|
" let res;\n"
|
|
" try {\n"
|
|
" res = await fetch(endpoint, { headers });\n"
|
|
" } catch (err) {\n"
|
|
" statusEl.textContent = 'error';\n"
|
|
" summaryEl.textContent = 'network error';\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" const body = await res.text();\n"
|
|
" if (rawEl) {\n"
|
|
" rawEl.textContent = body;\n"
|
|
" }\n"
|
|
" if (detailsEl) {\n"
|
|
" detailsEl.hidden = false;\n"
|
|
" }\n"
|
|
" if (!res.ok) {\n"
|
|
" statusEl.textContent = 'error';\n"
|
|
" summaryEl.textContent = `http ${res.status}: ${truncateBody(body, 240)}`;\n"
|
|
" if (onData) {\n"
|
|
" onData(null, res.status);\n"
|
|
" }\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" let data;\n"
|
|
" try {\n"
|
|
" data = JSON.parse(body);\n"
|
|
" } catch (err) {\n"
|
|
" statusEl.textContent = 'error';\n"
|
|
" summaryEl.textContent = 'invalid json';\n"
|
|
" if (onData) {\n"
|
|
" onData(null, res.status);\n"
|
|
" }\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" statusEl.textContent = 'ok';\n"
|
|
" if (onData) {\n"
|
|
" onData(data, res.status);\n"
|
|
" }\n"
|
|
" }\n"
|
|
"\n"
|
|
" async function loadWorkspace() {\n"
|
|
" clearMessage();\n"
|
|
" const space = getSpaceHeader();\n"
|
|
" if (spaceError.hidden === false) {\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" const headers = {};\n"
|
|
" if (space) {\n"
|
|
" headers['X-Amduat-Space'] = space;\n"
|
|
" }\n"
|
|
" let res;\n"
|
|
" try {\n"
|
|
" res = await fetch('/v1/space/workspace', { headers });\n"
|
|
" } catch (err) {\n"
|
|
" showMessage('Failed to reach /v1/space/workspace.');\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" if (res.status === 404) {\n"
|
|
" showMessage('No manifest configured for this space. Use PUT /v1/space/manifest to create one.');\n"
|
|
" workspaceRaw.textContent = '';\n"
|
|
" renderMounts([]);\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" const raw = await res.text();\n"
|
|
" if (!res.ok) {\n"
|
|
" showMessage(`Request failed (${res.status}).`);\n"
|
|
" workspaceRaw.textContent = raw;\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" workspaceRaw.textContent = raw;\n"
|
|
" let data;\n"
|
|
" try {\n"
|
|
" data = JSON.parse(raw);\n"
|
|
" } catch (err) {\n"
|
|
" showMessage('Invalid JSON response.');\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" if (data.effective_space) {\n"
|
|
" const mode = data.effective_space.mode || 'unknown';\n"
|
|
" const sid = data.effective_space.space_id || 'null';\n"
|
|
" setMonospaceText(effectiveEl, `${mode} (${sid})`);\n"
|
|
" } else {\n"
|
|
" setMonospaceText(effectiveEl, '-');\n"
|
|
" }\n"
|
|
" setMonospaceText(backendEl, data.store_backend || '-');\n"
|
|
" if (data.federation) {\n"
|
|
" const enabled = data.federation.enabled ? 'enabled' : 'disabled';\n"
|
|
" const transport = data.federation.transport || 'unknown';\n"
|
|
" setMonospaceText(federationEl, `${enabled} (${transport})`);\n"
|
|
" } else {\n"
|
|
" setMonospaceText(federationEl, '-');\n"
|
|
" }\n"
|
|
" setMonospaceText(manifestRefEl, data.manifest_ref || '-');\n"
|
|
" renderCapabilities(data.capabilities);\n"
|
|
" renderMounts(data.mounts);\n"
|
|
" }\n"
|
|
"\n"
|
|
" async function syncMounts() {\n"
|
|
" clearMessage();\n"
|
|
" const space = getSpaceHeader();\n"
|
|
" if (spaceError.hidden === false) {\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" syncBtn.disabled = true;\n"
|
|
" syncing.hidden = false;\n"
|
|
" syncSummary.textContent = 'syncing...';\n"
|
|
" const headers = {};\n"
|
|
" if (space) {\n"
|
|
" headers['X-Amduat-Space'] = space;\n"
|
|
" }\n"
|
|
" let res;\n"
|
|
" try {\n"
|
|
" res = await fetch('/v1/space/mounts/sync/until?limit=128&max_rounds=10&max_mounts=32', {\n"
|
|
" method: 'POST',\n"
|
|
" headers\n"
|
|
" });\n"
|
|
" } catch (err) {\n"
|
|
" showMessage('Failed to reach /v1/space/mounts/sync/until.');\n"
|
|
" syncBtn.disabled = false;\n"
|
|
" syncing.hidden = true;\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" const raw = await res.text();\n"
|
|
" syncRaw.textContent = raw;\n"
|
|
" syncDetails.hidden = false;\n"
|
|
" let data;\n"
|
|
" try {\n"
|
|
" data = JSON.parse(raw);\n"
|
|
" } catch (err) {\n"
|
|
" syncSummary.textContent = `Request failed (${res.status}).`;\n"
|
|
" syncBtn.disabled = false;\n"
|
|
" syncing.hidden = true;\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" const ok = data.ok === true;\n"
|
|
" const synced = data.mounts_synced !== undefined ? data.mounts_synced : '-';\n"
|
|
" let failures = 0;\n"
|
|
" if (Array.isArray(data.results)) {\n"
|
|
" failures = data.results.filter((r) => r && r.ok === false).length;\n"
|
|
" }\n"
|
|
" const statusClass = ok ? 'status-ok' : 'status-bad';\n"
|
|
" syncSummary.innerHTML = `<span class=\"${statusClass}\">ok=${ok}</span> mounts_synced=${synced} failures=${failures}`;\n"
|
|
" syncBtn.disabled = false;\n"
|
|
" syncing.hidden = true;\n"
|
|
" }\n"
|
|
"\n"
|
|
" function renderDoctor(data, status) {\n"
|
|
" if (!data) {\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" if (data.summary && (data.summary.ok_count !== undefined || data.summary.warn_count !== undefined)) {\n"
|
|
" const okCount = data.summary.ok_count !== undefined ? data.summary.ok_count : 0;\n"
|
|
" const warnCount = data.summary.warn_count !== undefined ? data.summary.warn_count : 0;\n"
|
|
" const failCount = data.summary.fail_count !== undefined ? data.summary.fail_count : 0;\n"
|
|
" const skippedCount = data.summary.skipped_count !== undefined ? data.summary.skipped_count : 0;\n"
|
|
" doctorSummary.textContent = `ok=${okCount} warn=${warnCount} fail=${failCount} skipped=${skippedCount}`;\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" let warnCount = 0;\n"
|
|
" let okCount = 0;\n"
|
|
" let skipCount = 0;\n"
|
|
" if (Array.isArray(data.checks)) {\n"
|
|
" data.checks.forEach((item) => {\n"
|
|
" if (!item) {\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" if (item.status === 'ok') {\n"
|
|
" okCount += 1;\n"
|
|
" } else if (item.status === 'skipped') {\n"
|
|
" skipCount += 1;\n"
|
|
" } else {\n"
|
|
" warnCount += 1;\n"
|
|
" }\n"
|
|
" });\n"
|
|
" }\n"
|
|
" if (okCount + warnCount + skipCount > 0) {\n"
|
|
" doctorSummary.textContent = `ok=${okCount} warn=${warnCount} skipped=${skipCount}`;\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" doctorSummary.textContent = data.status !== undefined ? `status=${data.status}` : `http ${status}`;\n"
|
|
" }\n"
|
|
"\n"
|
|
" function renderRoots(data) {\n"
|
|
" rootsList.textContent = '';\n"
|
|
" if (!data || !Array.isArray(data.roots)) {\n"
|
|
" rootsSummary.textContent = 'roots=0';\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" rootsSummary.textContent = `roots=${data.roots.length}`;\n"
|
|
" const shown = data.roots.slice(0, 10);\n"
|
|
" rootsList.textContent = shown.join('\\n');\n"
|
|
" }\n"
|
|
"\n"
|
|
" function renderSyncStatus(data) {\n"
|
|
" syncStatusTable.innerHTML = '';\n"
|
|
" if (!data || !Array.isArray(data.peers)) {\n"
|
|
" syncStatusSummary.textContent = 'peers=0 remotes=0';\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" let remotes = 0;\n"
|
|
" data.peers.forEach((peer) => {\n"
|
|
" if (peer && Array.isArray(peer.remotes)) {\n"
|
|
" remotes += peer.remotes.length;\n"
|
|
" }\n"
|
|
" });\n"
|
|
" syncStatusSummary.textContent = `peers=${data.peers.length} remotes=${remotes}`;\n"
|
|
" let html = '<table><thead><tr><th>Peer</th><th>Remote</th><th>Pull</th><th>Push</th></tr></thead><tbody>';\n"
|
|
" data.peers.forEach((peer) => {\n"
|
|
" if (!peer || !Array.isArray(peer.remotes)) {\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" peer.remotes.forEach((remote) => {\n"
|
|
" const pull = remote && remote.pull_cursor ? remote.pull_cursor : null;\n"
|
|
" const push = remote && remote.push_cursor ? remote.push_cursor : null;\n"
|
|
" const pullText = pull ? `present=${pull.present}` +\n"
|
|
" (pull.last_logseq !== undefined ? ` last_logseq=${pull.last_logseq}` : '') : '';\n"
|
|
" const pushText = push ? `present=${push.present}` +\n"
|
|
" (push.last_logseq !== undefined ? ` last_logseq=${push.last_logseq}` : '') : '';\n"
|
|
" html += `<tr><td class=\"mono\">${peer.peer_key || ''}</td>` +\n"
|
|
" `<td class=\"mono\">${remote.remote_space_id || 'null'}</td>` +\n"
|
|
" `<td class=\"mono\">${pullText}</td>` +\n"
|
|
" `<td class=\"mono\">${pushText}</td></tr>`;\n"
|
|
" });\n"
|
|
" });\n"
|
|
" html += '</tbody></table>';\n"
|
|
" syncStatusTable.innerHTML = html;\n"
|
|
" }\n"
|
|
"\n"
|
|
" function renderMountsResolve(data) {\n"
|
|
" if (!data || !Array.isArray(data.mounts)) {\n"
|
|
" mountsResolveSummary.textContent = 'mounts=0 pinned=0 track=0';\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" let pinned = 0;\n"
|
|
" let track = 0;\n"
|
|
" data.mounts.forEach((mount) => {\n"
|
|
" if (mount && mount.mode === 'pinned') {\n"
|
|
" pinned += 1;\n"
|
|
" } else if (mount && mount.mode === 'track') {\n"
|
|
" track += 1;\n"
|
|
" }\n"
|
|
" });\n"
|
|
" mountsResolveSummary.textContent = `mounts=${data.mounts.length} pinned=${pinned} track=${track}`;\n"
|
|
" }\n"
|
|
"\n"
|
|
" function renderManifest(data, status) {\n"
|
|
" if (!data) {\n"
|
|
" if (status === 404) {\n"
|
|
" manifestSummary.textContent = 'no manifest configured';\n"
|
|
" }\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" const count = data.manifest && Array.isArray(data.manifest.mounts)\n"
|
|
" ? data.manifest.mounts.length : 0;\n"
|
|
" manifestSummary.textContent = `ref=${data.manifest_ref || '-'} mounts=${count}`;\n"
|
|
" }\n"
|
|
"\n"
|
|
" refreshBtn.addEventListener('click', () => {\n"
|
|
" loadWorkspace();\n"
|
|
" });\n"
|
|
" syncBtn.addEventListener('click', syncMounts);\n"
|
|
" doctorFetch.addEventListener('click', () => {\n"
|
|
" fetchPanel('/v1/space/doctor', doctorStatus, doctorSummary, doctorRaw, doctorDetails, renderDoctor);\n"
|
|
" });\n"
|
|
" rootsFetch.addEventListener('click', () => {\n"
|
|
" fetchPanel('/v1/space/roots', rootsStatus, rootsSummary, rootsRaw, rootsDetails, renderRoots);\n"
|
|
" });\n"
|
|
" syncStatusFetch.addEventListener('click', () => {\n"
|
|
" fetchPanel('/v1/space/sync/status', syncStatus, syncStatusSummary, syncStatusRaw, syncStatusDetails, renderSyncStatus);\n"
|
|
" });\n"
|
|
" mountsResolveFetch.addEventListener('click', () => {\n"
|
|
" fetchPanel('/v1/space/mounts/resolve', mountsResolveStatus, mountsResolveSummary, mountsResolveRaw, mountsResolveDetails, renderMountsResolve);\n"
|
|
" });\n"
|
|
" manifestFetch.addEventListener('click', () => {\n"
|
|
" fetchPanel('/v1/space/manifest', manifestStatus, manifestSummary, manifestRaw, manifestDetails, renderManifest);\n"
|
|
" });\n"
|
|
" window.addEventListener('load', loadWorkspace);\n"
|
|
" </script>\n"
|
|
"</body>\n"
|
|
"</html>\n";
|
|
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\":\"GET\",\"path\":\"/v1/space/doctor\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/space/roots\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/space/manifest\"},"
|
|
"{\"method\":\"PUT\",\"path\":\"/v1/space/manifest\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/space/mounts/resolve\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/space/workspace\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/space/mounts/sync/until\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/space/sync/status\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/capabilities\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/cap/resolve\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/fed/records\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/fed/cursor\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/fed/cursor\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/fed/pull/plan\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/fed/push/plan\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/fed/pull\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/fed/artifacts/{ref}\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/fed/status\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/fed/ingest\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/fed/pull/until\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/fed/push\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/fed/push/until\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/concepts\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/concepts\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/concepts/{name}\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/concepts/{name}/publish\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/resolve/{name}\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/artifacts\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/relations\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/artifacts/{ref}\"},"
|
|
"{\"method\":\"HEAD\",\"path\":\"/v1/artifacts/{ref}\"},"
|
|
"{\"method\":\"GET\",\"path\":\"/v1/artifacts/{ref}?format=info\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/pel/run\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/pel/programs\"},"
|
|
"{\"method\":\"POST\",\"path\":\"/v1/context_frames\"}"
|
|
"],"
|
|
"\"schemas\":{"
|
|
"\"capability_mint_request\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"kind\",\"target\",\"expiry_seconds\"],"
|
|
"\"properties\":{"
|
|
"\"kind\":{\"type\":\"string\"},"
|
|
"\"target\":{\"type\":\"object\"},"
|
|
"\"expiry_seconds\":{\"type\":\"integer\"}"
|
|
"}"
|
|
"},"
|
|
"\"capability_mint_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"token\"],"
|
|
"\"properties\":{"
|
|
"\"token\":{\"type\":\"string\"}"
|
|
"}"
|
|
"},"
|
|
"\"pel_run_request\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"program_ref\",\"input_refs\"],"
|
|
"\"properties\":{"
|
|
"\"program_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"input_refs\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}},"
|
|
"\"params_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"scheme_ref\":{\"type\":\"string\",\"description\":\"hex ref or 'dag'\"},"
|
|
"\"receipt\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"input_manifest_ref\",\"environment_ref\",\"evaluator_id\",\"executor_ref\",\"started_at\",\"completed_at\"],"
|
|
"\"properties\":{"
|
|
"\"input_manifest_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"environment_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"evaluator_id\":{\"type\":\"string\",\"description\":\"opaque evaluator bytes (utf-8)\"},"
|
|
"\"executor_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"sbom_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"parity_digest_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"},"
|
|
"\"executor_fingerprint_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"run_id_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"},"
|
|
"\"limits\":{\"type\":\"object\",\"required\":[\"cpu_ms\",\"wall_ms\",\"max_rss_kib\",\"io_reads\",\"io_writes\"],\"properties\":{"
|
|
"\"cpu_ms\":{\"type\":\"integer\"},"
|
|
"\"wall_ms\":{\"type\":\"integer\"},"
|
|
"\"max_rss_kib\":{\"type\":\"integer\"},"
|
|
"\"io_reads\":{\"type\":\"integer\"},"
|
|
"\"io_writes\":{\"type\":\"integer\"}"
|
|
"}},"
|
|
"\"logs\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"kind\",\"log_ref\",\"sha256_hex\"],\"properties\":{"
|
|
"\"kind\":{\"type\":\"integer\"},"
|
|
"\"log_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"sha256_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"}"
|
|
"}}},"
|
|
"\"determinism_level\":{\"type\":\"integer\",\"description\":\"0-255\"},"
|
|
"\"rng_seed_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"},"
|
|
"\"signature_hex\":{\"type\":\"string\",\"description\":\"hex bytes\"},"
|
|
"\"started_at\":{\"type\":\"integer\"},"
|
|
"\"completed_at\":{\"type\":\"integer\"}"
|
|
"}"
|
|
"}"
|
|
"}"
|
|
"},"
|
|
"\"pel_run_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"result_ref\",\"output_refs\",\"status\"],"
|
|
"\"properties\":{"
|
|
"\"result_ref\":{\"type\":\"string\",\"description\":\"hex ref\"},"
|
|
"\"trace_ref\":{\"type\":\"string\",\"description\":\"hex ref\"},"
|
|
"\"receipt_ref\":{\"type\":\"string\",\"description\":\"hex ref\"},"
|
|
"\"output_refs\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"description\":\"hex ref\"}},"
|
|
"\"status\":{\"type\":\"string\"}"
|
|
"}"
|
|
"},"
|
|
"\"fed_records_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"domain_id\",\"snapshot_id\",\"log_prefix\",\"next_logseq\",\"records\"],"
|
|
"\"properties\":{"
|
|
"\"domain_id\":{\"type\":\"integer\"},"
|
|
"\"snapshot_id\":{\"type\":\"integer\"},"
|
|
"\"log_prefix\":{\"type\":\"integer\"},"
|
|
"\"next_logseq\":{\"type\":\"integer\",\"description\":\"Paging cursor; last emitted logseq + 1, or from_logseq if no records emitted.\"},"
|
|
"\"records\":{\"type\":\"array\",\"items\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"domain_id\",\"type\",\"ref\",\"logseq\",\"snapshot_id\",\"log_prefix\"],"
|
|
"\"properties\":{"
|
|
"\"domain_id\":{\"type\":\"integer\"},"
|
|
"\"type\":{\"type\":\"integer\"},"
|
|
"\"ref\":{\"type\":\"string\"},"
|
|
"\"logseq\":{\"type\":\"integer\"},"
|
|
"\"snapshot_id\":{\"type\":\"integer\"},"
|
|
"\"log_prefix\":{\"type\":\"integer\"},"
|
|
"\"visibility\":{\"type\":\"integer\"},"
|
|
"\"has_source\":{\"type\":\"boolean\"},"
|
|
"\"source_domain\":{\"type\":\"integer\"},"
|
|
"\"notes\":{\"type\":\"string\",\"description\":\"Type mapping: ARTIFACT_PUBLISH -> ARTIFACT, PER when type_tag=FER1_RECEIPT_1, TGK_EDGE when type_tag=TGK1_EDGE_V1; ARTIFACT_UNPUBLISH -> TOMBSTONE.\"}"
|
|
"}"
|
|
"}}"
|
|
"}"
|
|
"},"
|
|
"\"fed_status_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"status\",\"domain_id\",\"registry_ref\",\"last_tick_ms\"],"
|
|
"\"properties\":{"
|
|
"\"status\":{\"type\":\"string\"},"
|
|
"\"domain_id\":{\"type\":\"integer\"},"
|
|
"\"registry_ref\":{\"type\":[\"string\",\"null\"]},"
|
|
"\"last_tick_ms\":{\"type\":\"integer\"}"
|
|
"}"
|
|
"},"
|
|
"\"context_frame_request\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"bindings\"],"
|
|
"\"properties\":{"
|
|
"\"bindings\":{"
|
|
"\"type\":\"array\","
|
|
"\"items\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"key\"],"
|
|
"\"properties\":{"
|
|
"\"key\":{\"type\":\"string\",\"description\":\"concept name or hex ref\"},"
|
|
"\"value\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"value_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"},"
|
|
"\"value_scalar\":{\"type\":\"object\",\"properties\":{"
|
|
"\"int\":{\"type\":\"integer\"},"
|
|
"\"enum\":{\"type\":\"string\",\"description\":\"concept name or hex ref\"}"
|
|
"}}"
|
|
"}"
|
|
"}"
|
|
"}"
|
|
"}"
|
|
"},"
|
|
"\"pel_program_author_request\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"nodes\",\"roots\"],"
|
|
"\"properties\":{"
|
|
"\"nodes\":{\"type\":\"array\"},"
|
|
"\"roots\":{\"type\":\"array\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_manifest_mount\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"name\",\"peer_key\",\"space_id\",\"mode\"],"
|
|
"\"properties\":{"
|
|
"\"name\":{\"type\":\"string\"},"
|
|
"\"peer_key\":{\"type\":\"string\"},"
|
|
"\"space_id\":{\"type\":\"string\"},"
|
|
"\"mode\":{\"type\":\"string\"},"
|
|
"\"pinned_root_ref\":{\"type\":\"string\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_manifest\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"version\",\"mounts\"],"
|
|
"\"properties\":{"
|
|
"\"version\":{\"type\":\"integer\"},"
|
|
"\"mounts\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/schemas/space_manifest_mount\"}}"
|
|
"}"
|
|
"},"
|
|
"\"space_manifest_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"effective_space\",\"manifest_ref\",\"manifest\"],"
|
|
"\"properties\":{"
|
|
"\"effective_space\":{\"type\":\"object\"},"
|
|
"\"manifest_ref\":{\"type\":\"string\"},"
|
|
"\"manifest\":{\"$ref\":\"#/schemas/space_manifest\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_manifest_put_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"effective_space\",\"manifest_ref\",\"updated\",\"manifest\"],"
|
|
"\"properties\":{"
|
|
"\"effective_space\":{\"type\":\"object\"},"
|
|
"\"manifest_ref\":{\"type\":\"string\"},"
|
|
"\"updated\":{\"type\":\"boolean\"},"
|
|
"\"previous_ref\":{\"type\":\"string\"},"
|
|
"\"manifest\":{\"$ref\":\"#/schemas/space_manifest\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_mounts_pull_cursor\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"present\"],"
|
|
"\"properties\":{"
|
|
"\"present\":{\"type\":\"boolean\"},"
|
|
"\"last_logseq\":{\"type\":\"integer\"},"
|
|
"\"ref\":{\"type\":\"string\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_mounts_local_tracking\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"cursor_namespace\",\"cursor_scope\",\"remote_space_id\",\"pull_cursor\"],"
|
|
"\"properties\":{"
|
|
"\"cursor_namespace\":{\"type\":\"string\"},"
|
|
"\"cursor_scope\":{\"type\":\"string\"},"
|
|
"\"remote_space_id\":{\"type\":\"string\"},"
|
|
"\"pull_cursor\":{\"$ref\":\"#/schemas/space_mounts_pull_cursor\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_mounts_resolved_mount\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"name\",\"peer_key\",\"space_id\",\"mode\",\"local_tracking\"],"
|
|
"\"properties\":{"
|
|
"\"name\":{\"type\":\"string\"},"
|
|
"\"peer_key\":{\"type\":\"string\"},"
|
|
"\"space_id\":{\"type\":\"string\"},"
|
|
"\"mode\":{\"type\":\"string\"},"
|
|
"\"pinned_root_ref\":{\"type\":\"string\"},"
|
|
"\"local_tracking\":{\"$ref\":\"#/schemas/space_mounts_local_tracking\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_mounts_resolve_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"effective_space\",\"manifest_ref\",\"mounts\"],"
|
|
"\"properties\":{"
|
|
"\"effective_space\":{\"type\":\"object\"},"
|
|
"\"manifest_ref\":{\"type\":\"string\"},"
|
|
"\"mounts\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/schemas/space_mounts_resolved_mount\"}}"
|
|
"}"
|
|
"},"
|
|
"\"space_mounts_sync_error\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"code\",\"message\"],"
|
|
"\"properties\":{"
|
|
"\"code\":{\"type\":\"string\"},"
|
|
"\"message\":{\"type\":\"string\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_mounts_sync_cursor\":{"
|
|
"\"type\":\"object\","
|
|
"\"properties\":{"
|
|
"\"last_logseq\":{\"type\":\"integer\"},"
|
|
"\"ref\":{\"type\":\"string\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_mounts_sync_result\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"name\",\"peer_key\",\"remote_space_id\",\"status\"],"
|
|
"\"properties\":{"
|
|
"\"name\":{\"type\":\"string\"},"
|
|
"\"peer_key\":{\"type\":\"string\"},"
|
|
"\"remote_space_id\":{\"type\":\"string\"},"
|
|
"\"status\":{\"type\":\"string\"},"
|
|
"\"caught_up\":{\"type\":\"boolean\"},"
|
|
"\"rounds_executed\":{\"type\":\"integer\"},"
|
|
"\"applied\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"records\",\"artifacts\"],"
|
|
"\"properties\":{"
|
|
"\"records\":{\"type\":\"integer\"},"
|
|
"\"artifacts\":{\"type\":\"integer\"}"
|
|
"}"
|
|
"},"
|
|
"\"cursor\":{\"$ref\":\"#/schemas/space_mounts_sync_cursor\"},"
|
|
"\"error\":{\"$ref\":\"#/schemas/space_mounts_sync_error\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_mounts_sync_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"effective_space\",\"manifest_ref\",\"limit\",\"max_rounds\","
|
|
"\"max_mounts\",\"mounts_total\",\"mounts_synced\",\"ok\",\"results\"],"
|
|
"\"properties\":{"
|
|
"\"effective_space\":{\"type\":\"object\"},"
|
|
"\"manifest_ref\":{\"type\":\"string\"},"
|
|
"\"limit\":{\"type\":\"integer\"},"
|
|
"\"max_rounds\":{\"type\":\"integer\"},"
|
|
"\"max_mounts\":{\"type\":\"integer\"},"
|
|
"\"mounts_total\":{\"type\":\"integer\"},"
|
|
"\"mounts_synced\":{\"type\":\"integer\"},"
|
|
"\"ok\":{\"type\":\"boolean\"},"
|
|
"\"results\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/schemas/space_mounts_sync_result\"}}"
|
|
"}"
|
|
"},"
|
|
"\"space_sync_status_cursor\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"present\"],"
|
|
"\"properties\":{"
|
|
"\"present\":{\"type\":\"boolean\"},"
|
|
"\"last_logseq\":{\"type\":\"integer\"},"
|
|
"\"ref\":{\"type\":\"string\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_sync_status_remote\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"remote_space_id\",\"pull_cursor\",\"push_cursor\"],"
|
|
"\"properties\":{"
|
|
"\"remote_space_id\":{\"type\":[\"string\",\"null\"]},"
|
|
"\"pull_cursor\":{\"$ref\":\"#/schemas/space_sync_status_cursor\"},"
|
|
"\"push_cursor\":{\"$ref\":\"#/schemas/space_sync_status_cursor\"}"
|
|
"}"
|
|
"},"
|
|
"\"space_sync_status_peer\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"peer_key\",\"remotes\"],"
|
|
"\"properties\":{"
|
|
"\"peer_key\":{\"type\":\"string\"},"
|
|
"\"remotes\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/schemas/space_sync_status_remote\"}}"
|
|
"}"
|
|
"},"
|
|
"\"space_sync_status_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"effective_space\",\"store_backend\",\"federation\",\"peers\"],"
|
|
"\"properties\":{"
|
|
"\"effective_space\":{\"type\":\"object\"},"
|
|
"\"store_backend\":{\"type\":\"string\"},"
|
|
"\"federation\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"enabled\",\"transport\"],"
|
|
"\"properties\":{"
|
|
"\"enabled\":{\"type\":\"boolean\"},"
|
|
"\"transport\":{\"type\":\"string\"}"
|
|
"}"
|
|
"},"
|
|
"\"peers\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/schemas/space_sync_status_peer\"}}"
|
|
"}"
|
|
"},"
|
|
"\"space_workspace_response\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"effective_space\",\"store_backend\",\"federation\",\"capabilities\",\"manifest_ref\",\"manifest\",\"mounts\"],"
|
|
"\"properties\":{"
|
|
"\"effective_space\":{\"type\":\"object\"},"
|
|
"\"store_backend\":{\"type\":\"string\"},"
|
|
"\"federation\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"enabled\",\"transport\"],"
|
|
"\"properties\":{"
|
|
"\"enabled\":{\"type\":\"boolean\"},"
|
|
"\"transport\":{\"type\":\"string\"}"
|
|
"}"
|
|
"},"
|
|
"\"capabilities\":{"
|
|
"\"type\":\"object\","
|
|
"\"required\":[\"supported_ops\"],"
|
|
"\"properties\":{"
|
|
"\"supported_ops\":{"
|
|
"\"type\":\"object\","
|
|
"\"properties\":{"
|
|
"\"put\":{\"type\":\"boolean\"},"
|
|
"\"get\":{\"type\":\"boolean\"},"
|
|
"\"put_indexed\":{\"type\":\"boolean\"},"
|
|
"\"get_indexed\":{\"type\":\"boolean\"},"
|
|
"\"tombstone\":{\"type\":\"boolean\"},"
|
|
"\"tombstone_lift\":{\"type\":\"boolean\"},"
|
|
"\"log_scan\":{\"type\":\"boolean\"},"
|
|
"\"current_state\":{\"type\":\"boolean\"},"
|
|
"\"validate_config\":{\"type\":\"boolean\"}"
|
|
"}"
|
|
"},"
|
|
"\"implemented_ops\":{"
|
|
"\"type\":\"object\","
|
|
"\"properties\":{"
|
|
"\"put\":{\"type\":\"boolean\"},"
|
|
"\"get\":{\"type\":\"boolean\"},"
|
|
"\"put_indexed\":{\"type\":\"boolean\"},"
|
|
"\"get_indexed\":{\"type\":\"boolean\"},"
|
|
"\"tombstone\":{\"type\":\"boolean\"},"
|
|
"\"tombstone_lift\":{\"type\":\"boolean\"},"
|
|
"\"log_scan\":{\"type\":\"boolean\"},"
|
|
"\"current_state\":{\"type\":\"boolean\"},"
|
|
"\"validate_config\":{\"type\":\"boolean\"}"
|
|
"}"
|
|
"}"
|
|
"}"
|
|
"},"
|
|
"\"manifest_ref\":{\"type\":\"string\"},"
|
|
"\"manifest\":{\"type\":\"object\"},"
|
|
"\"mounts\":{\"type\":\"array\"}"
|
|
"}"
|
|
"}"
|
|
"}"
|
|
"}\n";
|
|
|
|
static volatile sig_atomic_t amduatd_should_exit = 0;
|
|
|
|
static void amduatd_on_signal(int signo) {
|
|
(void)signo;
|
|
amduatd_should_exit = 1;
|
|
}
|
|
|
|
typedef struct {
|
|
uid_t *uids;
|
|
size_t len;
|
|
size_t cap;
|
|
} amduatd_allowlist_t;
|
|
|
|
static void amduatd_allowlist_free(amduatd_allowlist_t *list) {
|
|
if (list == NULL) {
|
|
return;
|
|
}
|
|
free(list->uids);
|
|
list->uids = NULL;
|
|
list->len = 0;
|
|
list->cap = 0;
|
|
}
|
|
|
|
static bool amduatd_allowlist_add(amduatd_allowlist_t *list, uid_t uid) {
|
|
if (list == NULL) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0u; i < list->len; ++i) {
|
|
if (list->uids[i] == uid) {
|
|
return true;
|
|
}
|
|
}
|
|
if (list->len == list->cap) {
|
|
size_t next_cap = list->cap != 0 ? list->cap * 2u : 8u;
|
|
uid_t *next = (uid_t *)realloc(list->uids, next_cap * sizeof(*next));
|
|
if (next == NULL) {
|
|
return false;
|
|
}
|
|
list->uids = next;
|
|
list->cap = next_cap;
|
|
}
|
|
list->uids[list->len++] = uid;
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_actor_allowed(const amduatd_allowlist_t *list,
|
|
bool has_uid,
|
|
uid_t uid) {
|
|
if (list == NULL || list->len == 0u) {
|
|
return true;
|
|
}
|
|
if (!has_uid) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0u; i < list->len; ++i) {
|
|
if (list->uids[i] == uid) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool amduatd_get_peer_actor(int client_fd,
|
|
amduat_octets_t *out_actor,
|
|
bool *out_has_actor,
|
|
bool *out_has_uid,
|
|
uid_t *out_uid) {
|
|
char *actor = NULL;
|
|
if (out_actor == NULL || out_has_actor == NULL ||
|
|
out_has_uid == NULL || out_uid == NULL) {
|
|
return false;
|
|
}
|
|
*out_actor = amduat_octets(NULL, 0u);
|
|
*out_has_actor = false;
|
|
*out_has_uid = false;
|
|
*out_uid = 0;
|
|
|
|
#ifdef SO_PEERCRED
|
|
{
|
|
struct ucred cred;
|
|
socklen_t len = (socklen_t)sizeof(cred);
|
|
if (getsockopt(client_fd,
|
|
SOL_SOCKET,
|
|
SO_PEERCRED,
|
|
&cred,
|
|
&len) == 0 &&
|
|
len == sizeof(cred)) {
|
|
int needed = snprintf(NULL, 0,
|
|
"uid:%u gid:%u pid:%u",
|
|
(unsigned int)cred.uid,
|
|
(unsigned int)cred.gid,
|
|
(unsigned int)cred.pid);
|
|
if (needed < 0) {
|
|
return false;
|
|
}
|
|
actor = (char *)malloc((size_t)needed + 1u);
|
|
if (actor == NULL) {
|
|
return false;
|
|
}
|
|
snprintf(actor,
|
|
(size_t)needed + 1u,
|
|
"uid:%u gid:%u pid:%u",
|
|
(unsigned int)cred.uid,
|
|
(unsigned int)cred.gid,
|
|
(unsigned int)cred.pid);
|
|
*out_actor = amduat_octets((uint8_t *)actor, (size_t)needed);
|
|
*out_has_actor = true;
|
|
*out_has_uid = true;
|
|
*out_uid = cred.uid;
|
|
amduat_log(AMDUAT_LOG_DEBUG, "request actor=%s", actor);
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
actor = strdup("unknown");
|
|
if (actor == NULL) {
|
|
return false;
|
|
}
|
|
*out_actor = amduat_octets((uint8_t *)actor, strlen(actor));
|
|
*out_has_actor = true;
|
|
*out_has_uid = false;
|
|
*out_uid = 0;
|
|
amduat_log(AMDUAT_LOG_DEBUG, "request actor=unknown");
|
|
return true;
|
|
}
|
|
|
|
static 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 bool amduatd_json_parse_u32(const char **p,
|
|
const char *end,
|
|
uint32_t *out) {
|
|
const char *cur;
|
|
const char *start;
|
|
unsigned long long v;
|
|
char *tmp = NULL;
|
|
char *endp = NULL;
|
|
size_t n;
|
|
|
|
if (out != NULL) {
|
|
*out = 0;
|
|
}
|
|
if (p == NULL || *p == NULL || out == NULL) {
|
|
return false;
|
|
}
|
|
|
|
cur = amduatd_json_skip_ws(*p, end);
|
|
start = cur;
|
|
if (cur >= end) {
|
|
return false;
|
|
}
|
|
if (*cur < '0' || *cur > '9') {
|
|
return false;
|
|
}
|
|
cur++;
|
|
while (cur < end && *cur >= '0' && *cur <= '9') {
|
|
cur++;
|
|
}
|
|
n = (size_t)(cur - start);
|
|
tmp = (char *)malloc(n + 1u);
|
|
if (tmp == NULL) {
|
|
return false;
|
|
}
|
|
memcpy(tmp, start, n);
|
|
tmp[n] = '\0';
|
|
errno = 0;
|
|
v = strtoull(tmp, &endp, 10);
|
|
free(tmp);
|
|
if (errno != 0 || endp == NULL || *endp != '\0' || v > 0xffffffffULL) {
|
|
return false;
|
|
}
|
|
*out = (uint32_t)v;
|
|
*p = cur;
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_json_parse_u32_loose(const char **p,
|
|
const char *end,
|
|
uint32_t *out) {
|
|
const char *cur;
|
|
const char *sv = NULL;
|
|
size_t sv_len = 0;
|
|
uint64_t v = 0;
|
|
size_t i;
|
|
|
|
if (out != NULL) {
|
|
*out = 0;
|
|
}
|
|
if (p == NULL || *p == NULL || out == NULL) {
|
|
return false;
|
|
}
|
|
|
|
cur = amduatd_json_skip_ws(*p, end);
|
|
if (cur >= end) {
|
|
return false;
|
|
}
|
|
if (*cur == '"') {
|
|
if (!amduatd_json_parse_string_noesc(p, end, &sv, &sv_len)) {
|
|
return false;
|
|
}
|
|
if (sv_len == 0) {
|
|
return false;
|
|
}
|
|
for (i = 0; i < sv_len; ++i) {
|
|
unsigned char c = (unsigned char)sv[i];
|
|
if (c < '0' || c > '9') {
|
|
return false;
|
|
}
|
|
v = (v * 10u) + (uint64_t)(c - '0');
|
|
if (v > 0xffffffffULL) {
|
|
return false;
|
|
}
|
|
}
|
|
*out = (uint32_t)v;
|
|
return true;
|
|
}
|
|
if (*cur < '0' || *cur > '9') {
|
|
return false;
|
|
}
|
|
for (;;) {
|
|
unsigned char c = (unsigned char)*cur;
|
|
if (c < '0' || c > '9') {
|
|
break;
|
|
}
|
|
v = (v * 10u) + (uint64_t)(c - '0');
|
|
if (v > 0xffffffffULL) {
|
|
return false;
|
|
}
|
|
cur++;
|
|
if (cur >= end) {
|
|
break;
|
|
}
|
|
}
|
|
*out = (uint32_t)v;
|
|
*p = cur;
|
|
return true;
|
|
}
|
|
|
|
static void amduatd_json_peek_token(const char *p,
|
|
const char *end,
|
|
char *out,
|
|
size_t cap) {
|
|
const char *cur;
|
|
size_t n = 0;
|
|
if (out != NULL && cap != 0) {
|
|
out[0] = '\0';
|
|
}
|
|
if (out == NULL || cap == 0) {
|
|
return;
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
while (cur < end && n + 1 < cap) {
|
|
unsigned char c = (unsigned char)*cur;
|
|
if (c == ',' || c == '}' || c == ']' || c == '\n' || c == '\r') {
|
|
break;
|
|
}
|
|
if (c < 0x20u || c > 0x7eu) {
|
|
out[n++] = '?';
|
|
} else {
|
|
out[n++] = (char)c;
|
|
}
|
|
cur++;
|
|
if (n >= 24u) {
|
|
break;
|
|
}
|
|
}
|
|
out[n] = '\0';
|
|
}
|
|
|
|
static bool amduatd_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;
|
|
}
|
|
|
|
static bool amduatd_trim_header_value(const char *value,
|
|
char *out,
|
|
size_t cap) {
|
|
const char *start;
|
|
const char *end;
|
|
size_t len;
|
|
|
|
if (out != NULL && cap != 0u) {
|
|
out[0] = '\0';
|
|
}
|
|
if (value == NULL || out == NULL || cap == 0u) {
|
|
return false;
|
|
}
|
|
|
|
start = value;
|
|
while (*start == ' ' || *start == '\t') {
|
|
start++;
|
|
}
|
|
end = start + strlen(start);
|
|
while (end > start && (end[-1] == ' ' || end[-1] == '\t')) {
|
|
end--;
|
|
}
|
|
if (end <= start) {
|
|
return false;
|
|
}
|
|
if (*start == '"' && end > start + 1 && end[-1] == '"') {
|
|
start++;
|
|
end--;
|
|
}
|
|
if (end <= start) {
|
|
return false;
|
|
}
|
|
len = (size_t)(end - start);
|
|
if (len >= cap) {
|
|
len = cap - 1u;
|
|
}
|
|
memcpy(out, start, len);
|
|
out[len] = '\0';
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_get_remote_space_id(const amduatd_http_req_t *req,
|
|
char *buf,
|
|
size_t cap,
|
|
const char **out_remote,
|
|
const char **out_error) {
|
|
const char *remote = NULL;
|
|
if (out_remote != NULL) {
|
|
*out_remote = NULL;
|
|
}
|
|
if (out_error != NULL) {
|
|
*out_error = NULL;
|
|
}
|
|
if (req == NULL || buf == NULL || cap == 0u || out_remote == NULL) {
|
|
if (out_error != NULL) {
|
|
*out_error = "invalid remote_space_id";
|
|
}
|
|
return false;
|
|
}
|
|
if (amduatd_query_param(req->path, "remote_space_id", buf, cap) == NULL ||
|
|
buf[0] == '\0') {
|
|
return true;
|
|
}
|
|
if (!amduatd_space_space_id_is_valid(buf)) {
|
|
if (out_error != NULL) {
|
|
*out_error = "invalid remote_space_id";
|
|
}
|
|
return false;
|
|
}
|
|
remote = buf;
|
|
*out_remote = remote;
|
|
return true;
|
|
}
|
|
|
|
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_handle_get_space_doctor(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const char *root_path) {
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduatd_space_doctor_report_t report;
|
|
char *json = NULL;
|
|
bool ok = false;
|
|
int status = 200;
|
|
|
|
if (store == NULL || req == NULL || dcfg == NULL || root_path == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
if (!amduatd_space_doctor_run(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
dcfg,
|
|
fed_cfg,
|
|
&report)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"doctor failed");
|
|
}
|
|
if (!amduatd_space_doctor_report_json(&report, &json)) {
|
|
amduatd_space_doctor_report_free(&report);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
if (report.fail_count != 0u) {
|
|
status = 500;
|
|
}
|
|
ok = amduatd_http_send_json(fd,
|
|
status,
|
|
status == 200 ? "OK" : "Internal Server Error",
|
|
json,
|
|
false);
|
|
free(json);
|
|
amduatd_space_doctor_report_free(&report);
|
|
return ok;
|
|
}
|
|
|
|
static bool amduatd_handle_get_space_roots(int fd,
|
|
const amduatd_http_req_t *req,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_caps_t *caps,
|
|
const char *root_path) {
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduatd_space_roots_list_t roots;
|
|
amduatd_strbuf_t b;
|
|
bool ok = false;
|
|
|
|
memset(&roots, 0, sizeof(roots));
|
|
memset(&b, 0, sizeof(b));
|
|
|
|
if (req == NULL || dcfg == NULL || root_path == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
if (!amduatd_space_roots_list(root_path,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
&roots)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"root enumeration failed");
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"effective_space\":{")) {
|
|
goto roots_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto roots_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto roots_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},\"roots\":[")) {
|
|
goto roots_cleanup;
|
|
}
|
|
|
|
for (size_t i = 0u; i < roots.len; ++i) {
|
|
const char *pointer_name = roots.names[i];
|
|
bool exists = false;
|
|
amduat_reference_t ref;
|
|
amduat_asl_pointer_error_t perr;
|
|
char *ref_hex = NULL;
|
|
|
|
memset(&ref, 0, sizeof(ref));
|
|
if (i != 0u) {
|
|
if (!amduatd_strbuf_append_char(&b, ',')) {
|
|
goto roots_cleanup;
|
|
}
|
|
}
|
|
|
|
perr = amduat_asl_pointer_get(&pointer_store,
|
|
pointer_name,
|
|
&exists,
|
|
&ref);
|
|
if (perr != AMDUAT_ASL_POINTER_OK) {
|
|
amduat_reference_free(&ref);
|
|
goto roots_cleanup;
|
|
}
|
|
if (exists) {
|
|
if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) {
|
|
amduat_reference_free(&ref);
|
|
goto roots_cleanup;
|
|
}
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"pointer_name\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, pointer_name) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"head_ref_present\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, exists ? "true" : "false")) {
|
|
free(ref_hex);
|
|
amduat_reference_free(&ref);
|
|
goto roots_cleanup;
|
|
}
|
|
if (exists) {
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"head_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
free(ref_hex);
|
|
amduat_reference_free(&ref);
|
|
goto roots_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}")) {
|
|
free(ref_hex);
|
|
amduat_reference_free(&ref);
|
|
goto roots_cleanup;
|
|
}
|
|
|
|
free(ref_hex);
|
|
amduat_reference_free(&ref);
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "]}\n")) {
|
|
goto roots_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
roots_cleanup:
|
|
if (!ok) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
}
|
|
amduatd_space_roots_list_free(&roots);
|
|
amduatd_strbuf_free(&b);
|
|
return ok;
|
|
}
|
|
|
|
static bool amduatd_send_manifest_conflict(
|
|
int fd,
|
|
const amduat_reference_t *expected_ref,
|
|
const amduat_reference_t *current_ref,
|
|
bool current_present) {
|
|
amduatd_strbuf_t b;
|
|
char *expected_hex = NULL;
|
|
char *current_hex = NULL;
|
|
bool ok = false;
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
|
|
if (expected_ref != NULL) {
|
|
if (!amduat_asl_ref_encode_hex(*expected_ref, &expected_hex)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
}
|
|
if (current_present && current_ref != NULL) {
|
|
if (!amduat_asl_ref_encode_hex(*current_ref, ¤t_hex)) {
|
|
free(expected_hex);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"error\":\"manifest conflict\"")) {
|
|
goto manifest_conflict_cleanup;
|
|
}
|
|
if (expected_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"expected_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, expected_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto manifest_conflict_cleanup;
|
|
}
|
|
}
|
|
if (current_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"current_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, current_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto manifest_conflict_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}\n")) {
|
|
goto manifest_conflict_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 409, "Conflict", b.data, false);
|
|
|
|
manifest_conflict_cleanup:
|
|
amduatd_strbuf_free(&b);
|
|
free(expected_hex);
|
|
free(current_hex);
|
|
if (!ok) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static bool amduatd_handle_get_space_manifest(
|
|
int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_caps_t *caps,
|
|
const char *root_path) {
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_reference_t manifest_ref;
|
|
amduatd_space_manifest_t manifest;
|
|
amduatd_space_manifest_status_t status;
|
|
amduatd_strbuf_t b;
|
|
char *manifest_ref_hex = NULL;
|
|
bool ok = false;
|
|
|
|
memset(&manifest_ref, 0, sizeof(manifest_ref));
|
|
memset(&manifest, 0, sizeof(manifest));
|
|
memset(&b, 0, sizeof(b));
|
|
|
|
if (store == NULL || req == NULL || dcfg == NULL || root_path == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
status = amduatd_space_manifest_get(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
&manifest_ref,
|
|
&manifest);
|
|
if (status == AMDUATD_SPACE_MANIFEST_ERR_NOT_FOUND) {
|
|
return amduatd_send_json_error(fd, 404, "Not Found",
|
|
"manifest not found");
|
|
}
|
|
if (status == AMDUATD_SPACE_MANIFEST_ERR_STORE) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"store error");
|
|
}
|
|
if (status != AMDUATD_SPACE_MANIFEST_OK) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"manifest decode failed");
|
|
}
|
|
|
|
if (!amduat_asl_ref_encode_hex(manifest_ref, &manifest_ref_hex)) {
|
|
amduatd_space_manifest_free(&manifest);
|
|
amduat_reference_free(&manifest_ref);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"effective_space\":{")) {
|
|
goto manifest_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto manifest_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto manifest_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},\"manifest_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, manifest_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"manifest\":{")) {
|
|
goto manifest_cleanup;
|
|
}
|
|
{
|
|
char tmp[32];
|
|
int n = snprintf(tmp, sizeof(tmp), "%u", manifest.version);
|
|
if (n <= 0 || (size_t)n >= sizeof(tmp)) {
|
|
goto manifest_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"version\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",\"mounts\":[")) {
|
|
goto manifest_cleanup;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0u; i < manifest.mounts_len; ++i) {
|
|
const amduatd_space_manifest_mount_t *mount = &manifest.mounts[i];
|
|
char *root_ref_hex = NULL;
|
|
if (i != 0u) {
|
|
if (!amduatd_strbuf_append_char(&b, ',')) {
|
|
goto manifest_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"name\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, mount->name) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"peer_key\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, mount->peer_key) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, mount->space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"mode\":\"") ||
|
|
!amduatd_strbuf_append_cstr(
|
|
&b,
|
|
mount->mode == AMDUATD_SPACE_MANIFEST_MOUNT_PINNED ? "pinned"
|
|
: "track") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto manifest_cleanup;
|
|
}
|
|
if (mount->mode == AMDUATD_SPACE_MANIFEST_MOUNT_PINNED) {
|
|
if (!amduat_asl_ref_encode_hex(mount->pinned_root_ref, &root_ref_hex)) {
|
|
goto manifest_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"pinned_root_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, root_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
free(root_ref_hex);
|
|
goto manifest_cleanup;
|
|
}
|
|
free(root_ref_hex);
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}")) {
|
|
goto manifest_cleanup;
|
|
}
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "]}}\n")) {
|
|
goto manifest_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
manifest_cleanup:
|
|
if (!ok) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
}
|
|
amduatd_space_manifest_free(&manifest);
|
|
amduat_reference_free(&manifest_ref);
|
|
free(manifest_ref_hex);
|
|
amduatd_strbuf_free(&b);
|
|
return ok;
|
|
}
|
|
|
|
static bool amduatd_handle_get_space_mounts_resolve(
|
|
int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_caps_t *caps,
|
|
const char *root_path) {
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_reference_t manifest_ref;
|
|
amduatd_space_mounts_status_t status;
|
|
amduatd_strbuf_t b;
|
|
char *manifest_ref_hex = NULL;
|
|
char *mounts_json = NULL;
|
|
size_t mounts_len = 0u;
|
|
bool ok = false;
|
|
|
|
memset(&manifest_ref, 0, sizeof(manifest_ref));
|
|
memset(&b, 0, sizeof(b));
|
|
|
|
if (store == NULL || req == NULL || dcfg == NULL || root_path == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
status = amduatd_space_mounts_resolve(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
&manifest_ref,
|
|
&mounts_json,
|
|
&mounts_len);
|
|
if (status == AMDUATD_SPACE_MOUNTS_ERR_NOT_FOUND) {
|
|
return amduatd_send_json_error(fd, 404, "Not Found",
|
|
"manifest not found");
|
|
}
|
|
if (status == AMDUATD_SPACE_MOUNTS_ERR_STORE) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"store error");
|
|
}
|
|
if (status != AMDUATD_SPACE_MOUNTS_OK) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"manifest decode failed");
|
|
}
|
|
|
|
if (!amduat_asl_ref_encode_hex(manifest_ref, &manifest_ref_hex)) {
|
|
free(mounts_json);
|
|
amduat_reference_free(&manifest_ref);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"effective_space\":{")) {
|
|
goto mounts_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto mounts_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto mounts_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},\"manifest_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, manifest_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"mounts\":") ||
|
|
!amduatd_strbuf_append(&b, mounts_json, mounts_len) ||
|
|
!amduatd_strbuf_append_cstr(&b, "}\n")) {
|
|
goto mounts_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
mounts_cleanup:
|
|
if (!ok) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
}
|
|
amduat_reference_free(&manifest_ref);
|
|
free(manifest_ref_hex);
|
|
free(mounts_json);
|
|
amduatd_strbuf_free(&b);
|
|
return ok;
|
|
}
|
|
|
|
static bool amduatd_handle_post_space_mounts_sync_until(
|
|
int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char limit_buf[32];
|
|
char rounds_buf[32];
|
|
char mounts_buf[32];
|
|
uint64_t limit = 128u;
|
|
uint64_t max_rounds = 10u;
|
|
uint64_t max_mounts = 32u;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_fed_transport_unix_t transport;
|
|
amduat_fed_transport_t ops;
|
|
amduatd_fed_pull_transport_t pull_transport;
|
|
amduatd_space_mounts_sync_report_t report;
|
|
amduatd_space_mounts_sync_status_t status;
|
|
amduatd_strbuf_t b;
|
|
char *manifest_ref_hex = NULL;
|
|
bool ok = false;
|
|
|
|
memset(&report, 0, sizeof(report));
|
|
memset(&b, 0, sizeof(b));
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || root_path == NULL ||
|
|
dcfg == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
|
|
{
|
|
amduatd_fed_pull_plan_status_t check =
|
|
amduatd_fed_pull_plan_check(fed_cfg, store);
|
|
if (check == AMDUATD_FED_PULL_PLAN_ERR_DISABLED) {
|
|
return amduatd_send_json_error(fd, 503, "Service Unavailable",
|
|
"federation disabled");
|
|
}
|
|
if (check == AMDUATD_FED_PULL_PLAN_ERR_UNSUPPORTED) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"requires index backend");
|
|
}
|
|
if (check != AMDUATD_FED_PULL_PLAN_OK) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"plan unavailable");
|
|
}
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "limit",
|
|
limit_buf, sizeof(limit_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(limit_buf, &limit) || limit == 0u ||
|
|
limit > 10000u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid limit");
|
|
}
|
|
}
|
|
if (amduatd_query_param(req->path, "max_rounds",
|
|
rounds_buf, sizeof(rounds_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(rounds_buf, &max_rounds) ||
|
|
max_rounds == 0u || max_rounds > 100u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid max_rounds");
|
|
}
|
|
}
|
|
if (amduatd_query_param(req->path, "max_mounts",
|
|
mounts_buf, sizeof(mounts_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(mounts_buf, &max_mounts) ||
|
|
max_mounts == 0u || max_mounts > 256u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid max_mounts");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
if (fed_cfg->transport_kind != AMDUATD_FED_TRANSPORT_UNIX ||
|
|
!fed_cfg->unix_socket_set) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"federation transport unavailable");
|
|
}
|
|
if (!amduat_fed_transport_unix_init(&transport, fed_cfg->unix_socket_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"transport init failed");
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
(void)amduat_fed_transport_unix_set_space(
|
|
&transport, (const char *)req->effective_space->space_id.data);
|
|
}
|
|
|
|
ops = amduat_fed_transport_unix_ops(&transport);
|
|
memset(&pull_transport, 0, sizeof(pull_transport));
|
|
pull_transport.ctx = &transport;
|
|
pull_transport.get_records = amduatd_fed_pull_unix_get_records;
|
|
pull_transport.free_records = ops.free_records;
|
|
pull_transport.get_artifact = amduatd_fed_pull_unix_get_artifact;
|
|
|
|
status = amduatd_space_mounts_sync_until(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
fed_cfg,
|
|
&pull_transport,
|
|
limit,
|
|
max_rounds,
|
|
(size_t)max_mounts,
|
|
&report);
|
|
if (status == AMDUATD_SPACE_MOUNTS_SYNC_ERR_NOT_FOUND) {
|
|
amduatd_space_mounts_sync_report_free(&report);
|
|
return amduatd_send_json_error(fd, 404, "Not Found",
|
|
"manifest not found");
|
|
}
|
|
if (status == AMDUATD_SPACE_MOUNTS_SYNC_ERR_STORE) {
|
|
amduatd_space_mounts_sync_report_free(&report);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"store error");
|
|
}
|
|
if (status == AMDUATD_SPACE_MOUNTS_SYNC_ERR_CODEC) {
|
|
amduatd_space_mounts_sync_report_free(&report);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"manifest decode failed");
|
|
}
|
|
if (status != AMDUATD_SPACE_MOUNTS_SYNC_OK) {
|
|
amduatd_space_mounts_sync_report_free(&report);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"sync failed");
|
|
}
|
|
|
|
if (!amduat_asl_ref_encode_hex(report.manifest_ref, &manifest_ref_hex)) {
|
|
amduatd_space_mounts_sync_report_free(&report);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"effective_space\":{")) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},\"manifest_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, manifest_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"limit\":")) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
{
|
|
char tmp[32];
|
|
int n = snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)limit);
|
|
if (n <= 0 || (size_t)n >= sizeof(tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"max_rounds\":")) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
{
|
|
char tmp[32];
|
|
int n = snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)max_rounds);
|
|
if (n <= 0 || (size_t)n >= sizeof(tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"max_mounts\":")) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
{
|
|
char tmp[32];
|
|
int n = snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)max_mounts);
|
|
if (n <= 0 || (size_t)n >= sizeof(tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"mounts_total\":")) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
{
|
|
char tmp[32];
|
|
int n = snprintf(tmp, sizeof(tmp), "%zu", report.mounts_total);
|
|
if (n <= 0 || (size_t)n >= sizeof(tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"mounts_synced\":")) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
{
|
|
char tmp[32];
|
|
int n = snprintf(tmp, sizeof(tmp), "%zu", report.mounts_synced);
|
|
if (n <= 0 || (size_t)n >= sizeof(tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"ok\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, report.ok ? "true" : "false") ||
|
|
!amduatd_strbuf_append_cstr(&b, ",\"results\":") ||
|
|
!amduatd_strbuf_append(&b, report.results_json, report.results_len) ||
|
|
!amduatd_strbuf_append_cstr(&b, "}\n")) {
|
|
goto mounts_sync_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
mounts_sync_cleanup:
|
|
if (!ok) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
}
|
|
amduatd_space_mounts_sync_report_free(&report);
|
|
free(manifest_ref_hex);
|
|
amduatd_strbuf_free(&b);
|
|
return ok;
|
|
}
|
|
|
|
static bool amduatd_handle_get_workspace_ui(int fd) {
|
|
return amduatd_http_send_status(fd,
|
|
200,
|
|
"OK",
|
|
"text/html; charset=utf-8",
|
|
(const uint8_t *)k_amduatd_workspace_html,
|
|
strlen(k_amduatd_workspace_html),
|
|
false);
|
|
}
|
|
|
|
static bool amduatd_handle_get_space_workspace(
|
|
int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const char *root_path,
|
|
amduatd_store_backend_t store_backend) {
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduatd_space_workspace_status_t status;
|
|
char *workspace_json = NULL;
|
|
size_t workspace_len = 0u;
|
|
bool ok = false;
|
|
|
|
if (store == NULL || req == NULL || dcfg == NULL || fed_cfg == NULL ||
|
|
root_path == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
status = amduatd_space_workspace_get(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
fed_cfg,
|
|
store_backend,
|
|
&workspace_json,
|
|
&workspace_len);
|
|
if (status == AMDUATD_SPACE_WORKSPACE_ERR_NOT_FOUND) {
|
|
return amduatd_send_json_error(fd, 404, "Not Found",
|
|
"manifest not found");
|
|
}
|
|
if (status == AMDUATD_SPACE_WORKSPACE_ERR_STORE) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"store error");
|
|
}
|
|
if (status != AMDUATD_SPACE_WORKSPACE_OK) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"workspace decode failed");
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", workspace_json, false);
|
|
free(workspace_json);
|
|
if (!ok) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static bool amduatd_handle_put_space_manifest(
|
|
int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_caps_t *caps,
|
|
const char *root_path) {
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduatd_space_manifest_t manifest;
|
|
amduat_reference_t new_ref;
|
|
amduat_reference_t expected_ref;
|
|
amduatd_space_manifest_status_t status;
|
|
amduatd_strbuf_t b;
|
|
uint8_t *body = NULL;
|
|
char expected_param[AMDUATD_REF_TEXT_MAX];
|
|
char if_match_value[AMDUATD_REF_TEXT_MAX];
|
|
const char *expected_text = NULL;
|
|
bool have_expected = false;
|
|
char *manifest_ref_hex = NULL;
|
|
char *previous_ref_hex = NULL;
|
|
char *manifest_json = NULL;
|
|
size_t manifest_json_len = 0u;
|
|
bool ok = false;
|
|
|
|
memset(&manifest, 0, sizeof(manifest));
|
|
memset(&new_ref, 0, sizeof(new_ref));
|
|
memset(&expected_ref, 0, sizeof(expected_ref));
|
|
memset(&b, 0, sizeof(b));
|
|
|
|
if (store == NULL || req == NULL || dcfg == NULL || root_path == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (req->content_length == 0u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "missing body");
|
|
}
|
|
if (req->content_length > AMDUATD_SPACE_MANIFEST_MAX_BYTES) {
|
|
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;
|
|
}
|
|
|
|
if (req->if_match[0] != '\0') {
|
|
if (!amduatd_trim_header_value(req->if_match,
|
|
if_match_value,
|
|
sizeof(if_match_value)) ||
|
|
if_match_value[0] == '\0' ||
|
|
strcmp(if_match_value, "*") == 0) {
|
|
free(body);
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid If-Match");
|
|
}
|
|
expected_text = if_match_value;
|
|
}
|
|
if (amduatd_query_param(req->path,
|
|
"expected_ref",
|
|
expected_param,
|
|
sizeof(expected_param)) != NULL &&
|
|
expected_param[0] != '\0') {
|
|
if (expected_text != NULL &&
|
|
strcmp(expected_text, expected_param) != 0) {
|
|
free(body);
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"conflicting expected_ref");
|
|
}
|
|
expected_text = expected_param;
|
|
}
|
|
if (expected_text != NULL) {
|
|
if (!amduat_asl_ref_decode_hex(expected_text, &expected_ref)) {
|
|
free(body);
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid expected_ref");
|
|
}
|
|
have_expected = true;
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
free(body);
|
|
if (have_expected) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
status = amduatd_space_manifest_put(
|
|
store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
amduat_octets(body, req->content_length),
|
|
have_expected ? &expected_ref : NULL,
|
|
&new_ref,
|
|
&manifest);
|
|
free(body);
|
|
body = NULL;
|
|
|
|
if (status == AMDUATD_SPACE_MANIFEST_ERR_CODEC) {
|
|
amduatd_space_manifest_free(&manifest);
|
|
amduat_reference_free(&new_ref);
|
|
if (have_expected) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid manifest");
|
|
}
|
|
if (status == AMDUATD_SPACE_MANIFEST_ERR_CONFLICT) {
|
|
amduat_octets_t pointer_name = amduat_octets(NULL, 0u);
|
|
amduat_reference_t current_ref;
|
|
bool current_present = false;
|
|
amduat_asl_pointer_error_t perr;
|
|
|
|
memset(¤t_ref, 0, sizeof(current_ref));
|
|
if (amduatd_space_scope_name(req->effective_space,
|
|
"manifest/head",
|
|
&pointer_name)) {
|
|
perr = amduat_asl_pointer_get(&pointer_store,
|
|
(const char *)pointer_name.data,
|
|
¤t_present,
|
|
¤t_ref);
|
|
amduat_octets_free(&pointer_name);
|
|
if (perr != AMDUAT_ASL_POINTER_OK) {
|
|
current_present = false;
|
|
}
|
|
}
|
|
amduatd_space_manifest_free(&manifest);
|
|
amduat_reference_free(&new_ref);
|
|
ok = amduatd_send_manifest_conflict(
|
|
fd,
|
|
have_expected ? &expected_ref : NULL,
|
|
current_present ? ¤t_ref : NULL,
|
|
current_present);
|
|
if (current_present) {
|
|
amduat_reference_free(¤t_ref);
|
|
}
|
|
if (have_expected) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
return ok;
|
|
}
|
|
if (status != AMDUATD_SPACE_MANIFEST_OK) {
|
|
amduatd_space_manifest_free(&manifest);
|
|
amduat_reference_free(&new_ref);
|
|
if (have_expected) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"manifest update failed");
|
|
}
|
|
|
|
if (!amduat_asl_ref_encode_hex(new_ref, &manifest_ref_hex)) {
|
|
goto manifest_put_cleanup;
|
|
}
|
|
if (have_expected &&
|
|
!amduat_asl_ref_encode_hex(expected_ref, &previous_ref_hex)) {
|
|
goto manifest_put_cleanup;
|
|
}
|
|
if (!amduatd_space_manifest_encode_json(&manifest,
|
|
&manifest_json,
|
|
&manifest_json_len)) {
|
|
goto manifest_put_cleanup;
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"effective_space\":{")) {
|
|
goto manifest_put_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto manifest_put_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto manifest_put_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},\"manifest_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, manifest_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"updated\":true")) {
|
|
goto manifest_put_cleanup;
|
|
}
|
|
if (previous_ref_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"previous_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, previous_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto manifest_put_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"manifest\":") ||
|
|
!amduatd_strbuf_append(&b, manifest_json, manifest_json_len) ||
|
|
!amduatd_strbuf_append_cstr(&b, "}\n")) {
|
|
goto manifest_put_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
manifest_put_cleanup:
|
|
if (!ok) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
}
|
|
amduatd_strbuf_free(&b);
|
|
amduatd_space_manifest_free(&manifest);
|
|
amduat_reference_free(&new_ref);
|
|
if (have_expected) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
free(manifest_ref_hex);
|
|
free(previous_ref_hex);
|
|
free(manifest_json);
|
|
return ok;
|
|
}
|
|
|
|
typedef struct {
|
|
char *peer_key;
|
|
char *remote_space_id;
|
|
} amduatd_cursor_pair_t;
|
|
|
|
static void amduatd_cursor_pair_free(amduatd_cursor_pair_t *pair) {
|
|
if (pair == NULL) {
|
|
return;
|
|
}
|
|
free(pair->peer_key);
|
|
free(pair->remote_space_id);
|
|
pair->peer_key = NULL;
|
|
pair->remote_space_id = NULL;
|
|
}
|
|
|
|
static int amduatd_cursor_pair_cmp(const void *a, const void *b) {
|
|
const amduatd_cursor_pair_t *lhs = (const amduatd_cursor_pair_t *)a;
|
|
const amduatd_cursor_pair_t *rhs = (const amduatd_cursor_pair_t *)b;
|
|
const char *lhs_peer = lhs != NULL && lhs->peer_key != NULL ? lhs->peer_key
|
|
: "";
|
|
const char *rhs_peer = rhs != NULL && rhs->peer_key != NULL ? rhs->peer_key
|
|
: "";
|
|
const char *lhs_remote =
|
|
lhs != NULL && lhs->remote_space_id != NULL ? lhs->remote_space_id : "";
|
|
const char *rhs_remote =
|
|
rhs != NULL && rhs->remote_space_id != NULL ? rhs->remote_space_id : "";
|
|
int cmp = strcmp(lhs_peer, rhs_peer);
|
|
if (cmp != 0) {
|
|
return cmp;
|
|
}
|
|
return strcmp(lhs_remote, rhs_remote);
|
|
}
|
|
|
|
static bool amduatd_cursor_pair_add(amduatd_cursor_pair_t **pairs,
|
|
size_t *len,
|
|
size_t *cap,
|
|
const char *peer_key,
|
|
const char *remote_space_id) {
|
|
size_t next_len;
|
|
amduatd_cursor_pair_t *next;
|
|
|
|
if (pairs == NULL || len == NULL || cap == NULL ||
|
|
peer_key == NULL || peer_key[0] == '\0') {
|
|
return false;
|
|
}
|
|
for (size_t i = 0u; i < *len; ++i) {
|
|
const char *cur_peer = (*pairs)[i].peer_key;
|
|
const char *cur_remote = (*pairs)[i].remote_space_id;
|
|
const char *remote = remote_space_id != NULL ? remote_space_id : "";
|
|
if (cur_peer != NULL && strcmp(cur_peer, peer_key) == 0) {
|
|
if ((cur_remote == NULL || cur_remote[0] == '\0') && remote[0] == '\0') {
|
|
return true;
|
|
}
|
|
if (cur_remote != NULL && strcmp(cur_remote, remote) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (*len == *cap) {
|
|
size_t next_cap = *cap != 0u ? *cap * 2u : 8u;
|
|
next = (amduatd_cursor_pair_t *)realloc(*pairs,
|
|
next_cap * sizeof(*next));
|
|
if (next == NULL) {
|
|
return false;
|
|
}
|
|
*pairs = next;
|
|
*cap = next_cap;
|
|
}
|
|
next_len = *len + 1u;
|
|
(*pairs)[*len].peer_key = strdup(peer_key);
|
|
if ((*pairs)[*len].peer_key == NULL) {
|
|
return false;
|
|
}
|
|
if (remote_space_id != NULL && remote_space_id[0] != '\0') {
|
|
(*pairs)[*len].remote_space_id = strdup(remote_space_id);
|
|
if ((*pairs)[*len].remote_space_id == NULL) {
|
|
free((*pairs)[*len].peer_key);
|
|
(*pairs)[*len].peer_key = NULL;
|
|
return false;
|
|
}
|
|
} else {
|
|
(*pairs)[*len].remote_space_id = NULL;
|
|
}
|
|
*len = next_len;
|
|
return true;
|
|
}
|
|
|
|
static void amduatd_cursor_pairs_free(amduatd_cursor_pair_t *pairs,
|
|
size_t len) {
|
|
if (pairs == NULL) {
|
|
return;
|
|
}
|
|
for (size_t i = 0u; i < len; ++i) {
|
|
amduatd_cursor_pair_free(&pairs[i]);
|
|
}
|
|
free(pairs);
|
|
}
|
|
|
|
static bool amduatd_sync_status_append_cursor(
|
|
amduatd_strbuf_t *b,
|
|
amduat_asl_store_t *store,
|
|
amduat_asl_pointer_store_t *pointer_store,
|
|
const amduatd_space_t *effective_space,
|
|
const char *peer_key,
|
|
const char *remote_space_id,
|
|
bool push) {
|
|
amduatd_fed_cursor_record_t cursor;
|
|
amduat_reference_t ref;
|
|
amduatd_fed_cursor_status_t status;
|
|
bool present = false;
|
|
const char *error = NULL;
|
|
char *ref_hex = NULL;
|
|
|
|
amduatd_fed_cursor_record_init(&cursor);
|
|
ref = amduat_reference(0u, amduat_octets(NULL, 0u));
|
|
|
|
status = push
|
|
? amduatd_fed_push_cursor_get_remote(store,
|
|
pointer_store,
|
|
effective_space,
|
|
peer_key,
|
|
remote_space_id,
|
|
&cursor,
|
|
&ref)
|
|
: amduatd_fed_cursor_get_remote(store,
|
|
pointer_store,
|
|
effective_space,
|
|
peer_key,
|
|
remote_space_id,
|
|
&cursor,
|
|
&ref);
|
|
|
|
if (status == AMDUATD_FED_CURSOR_OK) {
|
|
present = true;
|
|
} else if (status == AMDUATD_FED_CURSOR_ERR_NOT_FOUND) {
|
|
present = false;
|
|
} else if (status == AMDUATD_FED_CURSOR_ERR_CODEC) {
|
|
present = false;
|
|
error = "codec";
|
|
} else {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&ref);
|
|
return false;
|
|
}
|
|
|
|
if (present && cursor.has_record_ref) {
|
|
if (!amduat_asl_ref_encode_hex(cursor.last_record_ref, &ref_hex)) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&ref);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(b, "{\"present\":") ||
|
|
!amduatd_strbuf_append_cstr(b, present ? "true" : "false")) {
|
|
free(ref_hex);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&ref);
|
|
return false;
|
|
}
|
|
if (error != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(b, ",\"error\":\"") ||
|
|
!amduatd_strbuf_append_cstr(b, error) ||
|
|
!amduatd_strbuf_append_cstr(b, "\"")) {
|
|
free(ref_hex);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&ref);
|
|
return false;
|
|
}
|
|
}
|
|
if (present && cursor.has_logseq) {
|
|
char tmp[32];
|
|
int n = snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)cursor.last_logseq);
|
|
if (n <= 0 || (size_t)n >= sizeof(tmp)) {
|
|
free(ref_hex);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&ref);
|
|
return false;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(b, ",\"last_logseq\":") ||
|
|
!amduatd_strbuf_append_cstr(b, tmp)) {
|
|
free(ref_hex);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&ref);
|
|
return false;
|
|
}
|
|
}
|
|
if (present && ref_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(b, ",\"ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(b, ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(b, "\"")) {
|
|
free(ref_hex);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&ref);
|
|
return false;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(b, "}")) {
|
|
free(ref_hex);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&ref);
|
|
return false;
|
|
}
|
|
|
|
free(ref_hex);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&ref);
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_handle_get_space_sync_status(
|
|
int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const char *root_path,
|
|
amduatd_store_backend_t store_backend) {
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduatd_space_roots_list_t pull_heads;
|
|
amduatd_space_roots_list_t push_heads;
|
|
amduatd_strbuf_t b;
|
|
amduatd_cursor_pair_t *pairs = NULL;
|
|
size_t pair_len = 0u;
|
|
size_t pair_cap = 0u;
|
|
amduat_octets_t pull_prefix = amduat_octets(NULL, 0u);
|
|
amduat_octets_t push_prefix = amduat_octets(NULL, 0u);
|
|
bool ok = false;
|
|
|
|
memset(&pull_heads, 0, sizeof(pull_heads));
|
|
memset(&push_heads, 0, sizeof(push_heads));
|
|
memset(&b, 0, sizeof(b));
|
|
|
|
if (store == NULL || req == NULL || dcfg == NULL || fed_cfg == NULL ||
|
|
root_path == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
if (!amduatd_space_scope_name(req->effective_space,
|
|
"fed/cursor",
|
|
&pull_prefix) ||
|
|
!amduatd_space_scope_name(req->effective_space,
|
|
"fed/push_cursor",
|
|
&push_prefix)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"cursor scan failed");
|
|
}
|
|
if (!amduatd_space_roots_list_cursor_heads(root_path,
|
|
req->effective_space,
|
|
false,
|
|
&pull_heads) ||
|
|
!amduatd_space_roots_list_cursor_heads(root_path,
|
|
req->effective_space,
|
|
true,
|
|
&push_heads)) {
|
|
amduat_octets_free(&pull_prefix);
|
|
amduat_octets_free(&push_prefix);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"cursor scan failed");
|
|
}
|
|
|
|
for (size_t i = 0u; i < pull_heads.len; ++i) {
|
|
char *peer = NULL;
|
|
char *remote = NULL;
|
|
if (amduatd_space_roots_cursor_parse((const char *)pull_prefix.data,
|
|
pull_heads.names[i],
|
|
&peer,
|
|
&remote)) {
|
|
if (!amduatd_cursor_pair_add(&pairs, &pair_len, &pair_cap,
|
|
peer, remote)) {
|
|
free(peer);
|
|
free(remote);
|
|
goto sync_cleanup;
|
|
}
|
|
free(peer);
|
|
free(remote);
|
|
}
|
|
}
|
|
for (size_t i = 0u; i < push_heads.len; ++i) {
|
|
char *peer = NULL;
|
|
char *remote = NULL;
|
|
if (amduatd_space_roots_cursor_parse((const char *)push_prefix.data,
|
|
push_heads.names[i],
|
|
&peer,
|
|
&remote)) {
|
|
if (!amduatd_cursor_pair_add(&pairs, &pair_len, &pair_cap,
|
|
peer, remote)) {
|
|
free(peer);
|
|
free(remote);
|
|
goto sync_cleanup;
|
|
}
|
|
free(peer);
|
|
free(remote);
|
|
}
|
|
}
|
|
if (pair_len > 1u) {
|
|
qsort(pairs, pair_len, sizeof(*pairs), amduatd_cursor_pair_cmp);
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"effective_space\":{")) {
|
|
goto sync_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto sync_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},\"store_backend\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, amduatd_store_backend_name(store_backend)) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"federation\":{")) {
|
|
goto sync_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"enabled\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, fed_cfg->enabled ? "true" : "false") ||
|
|
!amduatd_strbuf_append_cstr(&b, ",\"transport\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, amduatd_fed_transport_name(
|
|
fed_cfg->transport_kind)) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"},\"peers\":[")) {
|
|
goto sync_cleanup;
|
|
}
|
|
|
|
{
|
|
const char *current_peer = NULL;
|
|
bool first_peer = true;
|
|
bool first_remote = true;
|
|
|
|
for (size_t i = 0u; i < pair_len; ++i) {
|
|
const char *peer_key = pairs[i].peer_key;
|
|
const char *remote_space_id = pairs[i].remote_space_id;
|
|
if (peer_key == NULL || peer_key[0] == '\0') {
|
|
continue;
|
|
}
|
|
if (current_peer == NULL || strcmp(current_peer, peer_key) != 0) {
|
|
if (!first_peer) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "]}")) {
|
|
goto sync_cleanup;
|
|
}
|
|
}
|
|
if (!first_peer) {
|
|
if (!amduatd_strbuf_append_char(&b, ',')) {
|
|
goto sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"peer_key\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, peer_key) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"remotes\":[")) {
|
|
goto sync_cleanup;
|
|
}
|
|
current_peer = peer_key;
|
|
first_peer = false;
|
|
first_remote = true;
|
|
}
|
|
|
|
if (!first_remote) {
|
|
if (!amduatd_strbuf_append_char(&b, ',')) {
|
|
goto sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"remote_space_id\":")) {
|
|
goto sync_cleanup;
|
|
}
|
|
if (remote_space_id != NULL && remote_space_id[0] != '\0') {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, remote_space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto sync_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto sync_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"pull_cursor\":")) {
|
|
goto sync_cleanup;
|
|
}
|
|
if (!amduatd_sync_status_append_cursor(&b,
|
|
store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_key,
|
|
remote_space_id,
|
|
false)) {
|
|
goto sync_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"push_cursor\":")) {
|
|
goto sync_cleanup;
|
|
}
|
|
if (!amduatd_sync_status_append_cursor(&b,
|
|
store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_key,
|
|
remote_space_id,
|
|
true)) {
|
|
goto sync_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}")) {
|
|
goto sync_cleanup;
|
|
}
|
|
first_remote = false;
|
|
}
|
|
|
|
if (current_peer != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "]}")) {
|
|
goto sync_cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "]}\n")) {
|
|
goto sync_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
sync_cleanup:
|
|
if (!ok) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
}
|
|
amduatd_space_roots_list_free(&pull_heads);
|
|
amduatd_space_roots_list_free(&push_heads);
|
|
amduatd_cursor_pairs_free(pairs, pair_len);
|
|
amduat_octets_free(&pull_prefix);
|
|
amduat_octets_free(&push_prefix);
|
|
amduatd_strbuf_free(&b);
|
|
return ok;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool amduatd_handle_get_artifact(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req,
|
|
const char *path,
|
|
bool head_only) {
|
|
char no_query[1024];
|
|
char format[32];
|
|
const char *ref_text = NULL;
|
|
amduat_reference_t ref;
|
|
amduat_artifact_t artifact;
|
|
amduat_asl_store_error_t err;
|
|
bool want_artifact = false;
|
|
bool want_info = false;
|
|
|
|
memset(&ref, 0, sizeof(ref));
|
|
memset(&artifact, 0, sizeof(artifact));
|
|
amduatd_path_without_query(path, no_query, sizeof(no_query));
|
|
|
|
if (strncmp(no_query, "/v1/artifacts/", 14) != 0) {
|
|
(void)head_only;
|
|
return amduatd_http_send_not_found(fd, req);
|
|
}
|
|
ref_text = no_query + 14;
|
|
if (ref_text[0] == '\0') {
|
|
return amduatd_http_send_text(fd, 400, "Bad Request", "missing ref\n",
|
|
head_only);
|
|
}
|
|
|
|
if (!amduat_asl_ref_decode_hex(ref_text, &ref)) {
|
|
return amduatd_http_send_text(fd, 400, "Bad Request", "invalid ref\n",
|
|
head_only);
|
|
}
|
|
|
|
memset(format, 0, sizeof(format));
|
|
if (amduatd_query_param(path, "format", format, sizeof(format)) != NULL) {
|
|
if (strcmp(format, "artifact") == 0) {
|
|
want_artifact = true;
|
|
want_info = false;
|
|
} else if (strcmp(format, "info") == 0) {
|
|
want_artifact = false;
|
|
want_info = true;
|
|
} else if (strcmp(format, "raw") == 0 || format[0] == '\0') {
|
|
want_artifact = false;
|
|
want_info = false;
|
|
} else {
|
|
free((void *)ref.digest.data);
|
|
return amduatd_http_send_text(fd, 400, "Bad Request",
|
|
"invalid format\n", head_only);
|
|
}
|
|
}
|
|
|
|
err = amduat_asl_store_get(store, ref, &artifact);
|
|
free((void *)ref.digest.data);
|
|
if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) {
|
|
if (!head_only) {
|
|
return amduatd_http_send_not_found(fd, req);
|
|
}
|
|
return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", true);
|
|
}
|
|
if (err != AMDUAT_ASL_STORE_OK) {
|
|
amduat_asl_artifact_free(&artifact);
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"store error\n", head_only);
|
|
}
|
|
|
|
if (!want_artifact) {
|
|
if (want_info) {
|
|
char json[256];
|
|
int n;
|
|
if (artifact.has_type_tag) {
|
|
n = snprintf(json,
|
|
sizeof(json),
|
|
"{"
|
|
"\"len\":%zu,"
|
|
"\"has_type_tag\":true,"
|
|
"\"type_tag\":\"0x%08x\""
|
|
"}\n",
|
|
artifact.bytes.len,
|
|
(unsigned int)artifact.type_tag.tag_id);
|
|
} else {
|
|
n = snprintf(json,
|
|
sizeof(json),
|
|
"{"
|
|
"\"len\":%zu,"
|
|
"\"has_type_tag\":false,"
|
|
"\"type_tag\":null"
|
|
"}\n",
|
|
artifact.bytes.len);
|
|
}
|
|
amduat_asl_artifact_free(&artifact);
|
|
if (n <= 0 || (size_t)n >= sizeof(json)) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"error\n", head_only);
|
|
}
|
|
return amduatd_http_send_json(fd, 200, "OK", json, head_only);
|
|
}
|
|
bool ok = amduatd_http_send_status(
|
|
fd,
|
|
200,
|
|
"OK",
|
|
"application/octet-stream",
|
|
artifact.bytes.data,
|
|
artifact.bytes.len,
|
|
head_only);
|
|
amduat_asl_artifact_free(&artifact);
|
|
return ok;
|
|
}
|
|
|
|
{
|
|
amduat_octets_t out = amduat_octets(NULL, 0);
|
|
bool ok = amduat_enc_asl1_core_encode_artifact_v1(artifact, &out);
|
|
amduat_asl_artifact_free(&artifact);
|
|
if (!ok) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"encode error\n", head_only);
|
|
}
|
|
ok = amduatd_http_send_status(fd,
|
|
200,
|
|
"OK",
|
|
"application/vnd.amduat.asl.artifact+v1",
|
|
out.data,
|
|
out.len,
|
|
head_only);
|
|
free((void *)out.data);
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
static bool amduatd_handle_head_artifact(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req,
|
|
const char *path) {
|
|
return amduatd_handle_get_artifact(fd, store, req, path, true);
|
|
}
|
|
|
|
static bool amduatd_parse_u64_str(const char *s, uint64_t *out) {
|
|
char *end = NULL;
|
|
unsigned long long v;
|
|
|
|
if (s == NULL || out == NULL || s[0] == '\0') {
|
|
return false;
|
|
}
|
|
errno = 0;
|
|
v = strtoull(s, &end, 10);
|
|
if (errno != 0 || end == s || *end != '\0') {
|
|
return false;
|
|
}
|
|
*out = (uint64_t)v;
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_parse_u32_str(const char *s, uint32_t *out) {
|
|
uint64_t tmp = 0;
|
|
if (!amduatd_parse_u64_str(s, &tmp)) {
|
|
return false;
|
|
}
|
|
if (tmp > UINT32_MAX) {
|
|
return false;
|
|
}
|
|
*out = (uint32_t)tmp;
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_fed_require_space(int fd,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_http_req_t *req) {
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
const char *err = NULL;
|
|
char msg[128];
|
|
|
|
if (fed_cfg == NULL || req == NULL) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"internal error\n", false);
|
|
}
|
|
if (!amduatd_fed_scope_names(fed_cfg,
|
|
req->effective_space,
|
|
"fed",
|
|
&scoped,
|
|
&err)) {
|
|
const char *reason = err != NULL ? err : "invalid X-Amduat-Space";
|
|
int n = snprintf(msg, sizeof(msg), "%s\n", reason);
|
|
if (n <= 0 || (size_t)n >= sizeof(msg)) {
|
|
return amduatd_http_send_text(fd, 400, "Bad Request",
|
|
"invalid X-Amduat-Space\n", false);
|
|
}
|
|
return amduatd_http_send_text(fd, 400, "Bad Request", msg, false);
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_fed_records_log_name(const amduatd_space_t *space,
|
|
amduat_octets_t *out_name) {
|
|
return amduatd_space_scope_name(space, "fed/records", out_name);
|
|
}
|
|
|
|
static bool amduatd_fed_ingest_parse_record_type(const char *s,
|
|
size_t len,
|
|
amduat_fed_record_type_t *out) {
|
|
if (out != NULL) {
|
|
*out = AMDUAT_FED_REC_ARTIFACT;
|
|
}
|
|
if (s == NULL || out == NULL) {
|
|
return false;
|
|
}
|
|
if (len == strlen("artifact") && memcmp(s, "artifact", len) == 0) {
|
|
*out = AMDUAT_FED_REC_ARTIFACT;
|
|
return true;
|
|
}
|
|
if (len == strlen("per") && memcmp(s, "per", len) == 0) {
|
|
*out = AMDUAT_FED_REC_PER;
|
|
return true;
|
|
}
|
|
if (len == strlen("tgk_edge") && memcmp(s, "tgk_edge", len) == 0) {
|
|
*out = AMDUAT_FED_REC_TGK_EDGE;
|
|
return true;
|
|
}
|
|
if (len == strlen("tombstone") && memcmp(s, "tombstone", len) == 0) {
|
|
*out = AMDUAT_FED_REC_TOMBSTONE;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool amduatd_fed_ingest_send_response(
|
|
int fd,
|
|
int code,
|
|
const char *reason,
|
|
const char *status,
|
|
bool applied,
|
|
const amduat_reference_t *ref,
|
|
const amduatd_space_t *effective_space) {
|
|
amduatd_strbuf_t b;
|
|
char *ref_hex = NULL;
|
|
bool ok = false;
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
if (ref != NULL && ref->digest.data != NULL && ref->digest.len != 0u) {
|
|
if (!amduat_asl_ref_encode_hex(*ref, &ref_hex)) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"encode error\n", false);
|
|
}
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"status\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, status != NULL ? status : "error") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"applied\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, applied ? "true" : "false") ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"ref\":")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
if (ref_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"effective_space\":{")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
if (effective_space != NULL && effective_space->enabled &&
|
|
effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}}\n")) {
|
|
goto ingest_response_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd,
|
|
code,
|
|
reason != NULL ? reason : "OK",
|
|
b.data,
|
|
false);
|
|
|
|
ingest_response_cleanup:
|
|
amduatd_strbuf_free(&b);
|
|
free(ref_hex);
|
|
if (!ok) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"error\n", false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void amduatd_fed_ingest_discard_body(int fd,
|
|
const amduatd_http_req_t *req,
|
|
size_t max_len) {
|
|
uint8_t buf[4096];
|
|
size_t remaining;
|
|
if (req == NULL || req->content_length == 0u) {
|
|
return;
|
|
}
|
|
if (max_len != 0u && req->content_length > max_len) {
|
|
return;
|
|
}
|
|
remaining = req->content_length;
|
|
while (remaining > 0u) {
|
|
size_t chunk = remaining < sizeof(buf) ? remaining : sizeof(buf);
|
|
if (!amduatd_read_exact(fd, buf, chunk)) {
|
|
break;
|
|
}
|
|
remaining -= chunk;
|
|
}
|
|
}
|
|
|
|
static bool amduatd_handle_get_fed_records(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char domain_buf[32];
|
|
char from_buf[32];
|
|
char limit_buf[32];
|
|
uint32_t domain_id = 0;
|
|
uint64_t from_logseq = 0;
|
|
uint64_t next_logseq = 0;
|
|
uint64_t limit = 256;
|
|
uint64_t emitted = 0;
|
|
amduat_asl_index_state_t state;
|
|
amduat_asl_log_entry_t *entries = NULL;
|
|
size_t entry_count = 0;
|
|
uint64_t next_offset = 0;
|
|
bool end = false;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_asl_log_store_t log_store;
|
|
amduat_octets_t log_name = amduat_octets(NULL, 0u);
|
|
size_t i;
|
|
amduatd_strbuf_t b;
|
|
bool first = true;
|
|
|
|
if (store == NULL || req == NULL || root_path == NULL) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"internal error\n", false);
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
if (amduatd_query_param(req->path, "domain_id",
|
|
domain_buf, sizeof(domain_buf)) == NULL ||
|
|
!amduatd_parse_u32_str(domain_buf, &domain_id)) {
|
|
return amduatd_http_send_text(fd, 400, "Bad Request",
|
|
"missing domain_id\n", false);
|
|
}
|
|
if (amduatd_query_param(req->path, "from_logseq",
|
|
from_buf, sizeof(from_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(from_buf, &from_logseq)) {
|
|
return amduatd_http_send_text(fd, 400, "Bad Request",
|
|
"invalid from_logseq\n", false);
|
|
}
|
|
}
|
|
if (amduatd_query_param(req->path, "limit",
|
|
limit_buf, sizeof(limit_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(limit_buf, &limit) || limit == 0u ||
|
|
limit > 10000u) {
|
|
return amduatd_http_send_text(fd, 400, "Bad Request",
|
|
"invalid limit\n", false);
|
|
}
|
|
}
|
|
if (store->ops.current_state == NULL) {
|
|
return amduatd_http_send_text(fd, 501, "Not Implemented",
|
|
"requires index backend\n", false);
|
|
}
|
|
if (!amduat_asl_index_current_state(store, &state)) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"store error\n", false);
|
|
}
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path) ||
|
|
!amduat_asl_log_store_init(&log_store, root_path, store,
|
|
&pointer_store)) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"log store error\n", false);
|
|
}
|
|
if (!amduatd_fed_records_log_name(req->effective_space, &log_name)) {
|
|
return amduatd_http_send_text(fd, 400, "Bad Request",
|
|
"invalid X-Amduat-Space\n", false);
|
|
}
|
|
{
|
|
amduat_asl_store_error_t read_err =
|
|
amduat_asl_log_read(&log_store,
|
|
(const char *)log_name.data,
|
|
from_logseq,
|
|
(size_t)limit,
|
|
&entries,
|
|
&entry_count,
|
|
&next_offset,
|
|
&end);
|
|
amduat_octets_free(&log_name);
|
|
if (read_err != AMDUAT_ASL_STORE_OK) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"log read error\n", false);
|
|
}
|
|
}
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
if (!amduatd_strbuf_append_cstr(&b, "{")) {
|
|
goto fed_records_oom;
|
|
}
|
|
{
|
|
char header[256];
|
|
int n = snprintf(header,
|
|
sizeof(header),
|
|
"\"domain_id\":%u,"
|
|
"\"snapshot_id\":%llu,"
|
|
"\"log_prefix\":%llu,"
|
|
"\"records\":[",
|
|
(unsigned int)domain_id,
|
|
(unsigned long long)state.snapshot_id,
|
|
(unsigned long long)state.log_position);
|
|
if (n <= 0 || (size_t)n >= sizeof(header)) {
|
|
goto fed_records_oom;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, header)) {
|
|
goto fed_records_oom;
|
|
}
|
|
}
|
|
|
|
next_logseq = next_offset;
|
|
for (i = 0; i < entry_count; ++i) {
|
|
const amduat_asl_log_entry_t *entry = &entries[i];
|
|
amduat_reference_t ref;
|
|
char *ref_hex = NULL;
|
|
char entry_buf[512];
|
|
int n;
|
|
|
|
if (entry->payload_ref.digest.data == NULL ||
|
|
entry->payload_ref.digest.len == 0u) {
|
|
continue;
|
|
}
|
|
ref = entry->payload_ref;
|
|
if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) {
|
|
goto fed_records_oom;
|
|
}
|
|
|
|
{
|
|
uint32_t rec_type = 0u;
|
|
amduat_artifact_t artifact;
|
|
amduat_asl_store_error_t store_err;
|
|
uint64_t logseq = from_logseq + i;
|
|
if (entry->kind == AMDUATD_FED_LOG_KIND_ARTIFACT) {
|
|
rec_type = AMDUAT_FED_REC_ARTIFACT;
|
|
memset(&artifact, 0, sizeof(artifact));
|
|
store_err = amduat_asl_store_get(store, ref, &artifact);
|
|
if (store_err == AMDUAT_ASL_STORE_OK && artifact.has_type_tag) {
|
|
if (artifact.type_tag.tag_id == AMDUAT_TYPE_TAG_TGK1_EDGE_V1) {
|
|
rec_type = AMDUAT_FED_REC_TGK_EDGE;
|
|
} else if (artifact.type_tag.tag_id ==
|
|
AMDUAT_TYPE_TAG_FER1_RECEIPT_1) {
|
|
rec_type = AMDUAT_FED_REC_PER;
|
|
}
|
|
}
|
|
if (store_err == AMDUAT_ASL_STORE_OK) {
|
|
amduat_asl_artifact_free(&artifact);
|
|
}
|
|
} else if (entry->kind == AMDUATD_FED_LOG_KIND_TOMBSTONE) {
|
|
rec_type = AMDUAT_FED_REC_TOMBSTONE;
|
|
} else {
|
|
free(ref_hex);
|
|
continue;
|
|
}
|
|
n = snprintf(entry_buf,
|
|
sizeof(entry_buf),
|
|
"%s{\"domain_id\":%u,"
|
|
"\"type\":%u,"
|
|
"\"ref\":\"%s\","
|
|
"\"logseq\":%llu,"
|
|
"\"snapshot_id\":%llu,"
|
|
"\"log_prefix\":%llu,"
|
|
"\"visibility\":1,"
|
|
"\"has_source\":false,"
|
|
"\"source_domain\":0}",
|
|
first ? "" : ",",
|
|
(unsigned int)domain_id,
|
|
(unsigned int)rec_type,
|
|
ref_hex,
|
|
(unsigned long long)logseq,
|
|
(unsigned long long)state.snapshot_id,
|
|
(unsigned long long)state.log_position);
|
|
}
|
|
free(ref_hex);
|
|
if (n <= 0 || (size_t)n >= sizeof(entry_buf)) {
|
|
goto fed_records_oom;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, entry_buf)) {
|
|
goto fed_records_oom;
|
|
}
|
|
first = false;
|
|
emitted++;
|
|
}
|
|
|
|
{
|
|
char tail[128];
|
|
int n = snprintf(tail,
|
|
sizeof(tail),
|
|
"],\"next_logseq\":%llu}\n",
|
|
(unsigned long long)next_logseq);
|
|
if (n <= 0 || (size_t)n >= sizeof(tail)) {
|
|
goto fed_records_oom;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, tail)) {
|
|
goto fed_records_oom;
|
|
}
|
|
}
|
|
|
|
{
|
|
bool ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
amduatd_strbuf_free(&b);
|
|
if (entries != NULL) {
|
|
amduat_asl_log_entries_free(entries, entry_count);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
fed_records_oom:
|
|
if (entries != NULL) {
|
|
amduat_asl_log_entries_free(entries, entry_count);
|
|
}
|
|
amduatd_strbuf_free(&b);
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"oom\n", false);
|
|
}
|
|
|
|
static bool amduatd_handle_get_fed_artifact(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *path) {
|
|
char no_query[1024];
|
|
const char *ref_text = NULL;
|
|
amduat_reference_t ref;
|
|
amduat_artifact_t artifact;
|
|
amduat_asl_store_error_t err;
|
|
|
|
if (store == NULL || req == NULL || path == NULL) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"internal error\n", false);
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
amduatd_path_without_query(path, no_query, sizeof(no_query));
|
|
if (strncmp(no_query, "/v1/fed/artifacts/", 18) != 0) {
|
|
return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false);
|
|
}
|
|
ref_text = no_query + 18;
|
|
memset(&ref, 0, sizeof(ref));
|
|
if (!amduat_asl_ref_decode_hex(ref_text, &ref)) {
|
|
return amduatd_http_send_text(fd, 400, "Bad Request",
|
|
"invalid ref\n", false);
|
|
}
|
|
|
|
memset(&artifact, 0, sizeof(artifact));
|
|
err = amduat_asl_store_get(store, ref, &artifact);
|
|
amduat_reference_free(&ref);
|
|
if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) {
|
|
return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false);
|
|
}
|
|
if (err != AMDUAT_ASL_STORE_OK) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"store error\n", false);
|
|
}
|
|
|
|
{
|
|
bool ok = amduatd_http_send_status(fd,
|
|
200,
|
|
"OK",
|
|
"application/octet-stream",
|
|
artifact.bytes.data,
|
|
artifact.bytes.len,
|
|
false);
|
|
amduat_asl_artifact_free(&artifact);
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
static bool amduatd_handle_get_fed_status(int fd,
|
|
const amduat_fed_coord_t *coord,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_http_req_t *req) {
|
|
amduat_fed_coord_status_t status;
|
|
char *ref_hex = NULL;
|
|
char json[512];
|
|
int n;
|
|
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
if (coord == NULL || fed_cfg == NULL || !fed_cfg->enabled) {
|
|
return amduatd_http_send_text(fd, 503, "Service Unavailable",
|
|
"federation disabled\n", false);
|
|
}
|
|
amduat_fed_coord_get_status(coord, &status);
|
|
if (status.registry_ref.hash_id != 0 &&
|
|
status.registry_ref.digest.data != NULL) {
|
|
if (!amduat_asl_ref_encode_hex(status.registry_ref, &ref_hex)) {
|
|
amduat_reference_free(&status.registry_ref);
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"encode error\n", false);
|
|
}
|
|
}
|
|
|
|
if (ref_hex != NULL) {
|
|
n = snprintf(json,
|
|
sizeof(json),
|
|
"{\"status\":\"ok\",\"domain_id\":%u,"
|
|
"\"registry_ref\":\"%s\",\"last_tick_ms\":%llu}\n",
|
|
(unsigned int)status.domain_id,
|
|
ref_hex,
|
|
(unsigned long long)status.last_tick_ms);
|
|
} else {
|
|
n = snprintf(json,
|
|
sizeof(json),
|
|
"{\"status\":\"ok\",\"domain_id\":%u,"
|
|
"\"registry_ref\":null,\"last_tick_ms\":%llu}\n",
|
|
(unsigned int)status.domain_id,
|
|
(unsigned long long)status.last_tick_ms);
|
|
}
|
|
free(ref_hex);
|
|
amduat_reference_free(&status.registry_ref);
|
|
if (n <= 0 || (size_t)n >= sizeof(json)) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"error\n", false);
|
|
}
|
|
return amduatd_http_send_json(fd, 200, "OK", json, false);
|
|
}
|
|
|
|
static bool amduatd_handle_get_fed_cursor(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char peer_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
char remote_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
const char *remote_space_id = NULL;
|
|
const char *remote_error = NULL;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduatd_fed_cursor_record_t cursor;
|
|
amduat_reference_t ref;
|
|
amduatd_fed_cursor_status_t status;
|
|
amduatd_strbuf_t b;
|
|
char *ref_hex = NULL;
|
|
char *record_hex = NULL;
|
|
char tmp[64];
|
|
bool ok = false;
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || root_path == NULL ||
|
|
dcfg == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
if (amduatd_fed_cursor_check_enabled(fed_cfg) ==
|
|
AMDUATD_FED_CURSOR_ERR_DISABLED) {
|
|
return amduatd_send_json_error(fd, 503, "Service Unavailable",
|
|
"federation disabled");
|
|
}
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "peer",
|
|
peer_buf, sizeof(peer_buf)) == NULL ||
|
|
peer_buf[0] == '\0') {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing peer");
|
|
}
|
|
if (!amduatd_get_remote_space_id(req,
|
|
remote_buf,
|
|
sizeof(remote_buf),
|
|
&remote_space_id,
|
|
&remote_error)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
remote_error);
|
|
}
|
|
{
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
if (remote_space_id != NULL) {
|
|
if (!amduatd_fed_cursor_pointer_name_v2(req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
} else if (!amduatd_fed_cursor_pointer_name(req->effective_space,
|
|
peer_buf,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
amduatd_fed_cursor_record_init(&cursor);
|
|
memset(&ref, 0, sizeof(ref));
|
|
status = amduatd_fed_cursor_get_remote(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&cursor,
|
|
&ref);
|
|
if (status == AMDUATD_FED_CURSOR_ERR_NOT_FOUND) {
|
|
return amduatd_send_json_error(fd, 404, "Not Found", "cursor not found");
|
|
}
|
|
if (status == AMDUATD_FED_CURSOR_ERR_INVALID) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
if (status != AMDUATD_FED_CURSOR_OK) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"cursor read failed");
|
|
}
|
|
|
|
if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) {
|
|
amduat_reference_free(&ref);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
if (cursor.has_record_ref) {
|
|
if (!amduat_asl_ref_encode_hex(cursor.last_record_ref, &record_hex)) {
|
|
free(ref_hex);
|
|
amduat_reference_free(&ref);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
}
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"peer_key\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, cursor.peer_key) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",\"space_id\":")) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
if (cursor.space_id != NULL && cursor.space_id[0] != '\0') {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, cursor.space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"last_logseq\":")) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
if (cursor.has_logseq) {
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)cursor.last_logseq);
|
|
if (!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"last_record_hash\":")) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
if (record_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, record_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"}\n")) {
|
|
goto fed_cursor_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
fed_cursor_cleanup:
|
|
amduatd_strbuf_free(&b);
|
|
free(ref_hex);
|
|
free(record_hex);
|
|
amduat_reference_free(&ref);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
if (!ok) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_handle_post_fed_cursor(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char peer_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
char remote_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
const char *remote_space_id = NULL;
|
|
const char *remote_error = NULL;
|
|
uint8_t *body = NULL;
|
|
const char *p = NULL;
|
|
const char *end = NULL;
|
|
bool have_logseq = false;
|
|
bool have_record_hash = false;
|
|
bool have_expected_ref = false;
|
|
uint64_t last_logseq = 0;
|
|
amduat_reference_t last_record_ref;
|
|
amduat_reference_t expected_ref;
|
|
const char *parse_error = NULL;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduatd_fed_cursor_record_t cursor;
|
|
amduat_reference_t new_ref;
|
|
amduatd_fed_cursor_status_t status;
|
|
char *ref_hex = NULL;
|
|
amduatd_strbuf_t b;
|
|
bool ok = false;
|
|
|
|
memset(&last_record_ref, 0, sizeof(last_record_ref));
|
|
memset(&expected_ref, 0, sizeof(expected_ref));
|
|
memset(&new_ref, 0, sizeof(new_ref));
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || root_path == NULL ||
|
|
dcfg == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
if (amduatd_fed_cursor_check_enabled(fed_cfg) ==
|
|
AMDUATD_FED_CURSOR_ERR_DISABLED) {
|
|
return amduatd_send_json_error(fd, 503, "Service Unavailable",
|
|
"federation disabled");
|
|
}
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "peer",
|
|
peer_buf, sizeof(peer_buf)) == NULL ||
|
|
peer_buf[0] == '\0') {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing peer");
|
|
}
|
|
if (!amduatd_get_remote_space_id(req,
|
|
remote_buf,
|
|
sizeof(remote_buf),
|
|
&remote_space_id,
|
|
&remote_error)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
remote_error);
|
|
}
|
|
{
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
if (remote_space_id != NULL) {
|
|
if (!amduatd_fed_cursor_pointer_name_v2(req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
} else if (!amduatd_fed_cursor_pointer_name(req->effective_space,
|
|
peer_buf,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
}
|
|
if (req->content_length == 0) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "missing body");
|
|
}
|
|
if (req->content_length > (32u * 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, '{')) {
|
|
parse_error = "invalid json";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
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, ':')) {
|
|
parse_error = "invalid json";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
if (key_len == strlen("last_logseq") &&
|
|
memcmp(key, "last_logseq", key_len) == 0) {
|
|
if (have_logseq || !amduatd_json_parse_u64(&p, end, &last_logseq)) {
|
|
parse_error = "invalid last_logseq";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
have_logseq = true;
|
|
} else if (key_len == strlen("last_record_hash") &&
|
|
memcmp(key, "last_record_hash", key_len) == 0) {
|
|
if (have_record_hash ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
parse_error = "invalid last_record_hash";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
if (sv_len == 0) {
|
|
parse_error = "invalid last_record_hash";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
if (!amduat_asl_ref_decode_hex(sv, &last_record_ref)) {
|
|
parse_error = "invalid last_record_hash";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
have_record_hash = true;
|
|
} else if (key_len == strlen("expected_ref") &&
|
|
memcmp(key, "expected_ref", key_len) == 0) {
|
|
if (have_expected_ref ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
parse_error = "invalid expected_ref";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
if (sv_len == 0 || !amduat_asl_ref_decode_hex(sv, &expected_ref)) {
|
|
parse_error = "invalid expected_ref";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
have_expected_ref = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
parse_error = "invalid json";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
parse_error = "invalid json";
|
|
goto fed_cursor_parse_fail;
|
|
}
|
|
free(body);
|
|
body = NULL;
|
|
|
|
if (!have_logseq && !have_record_hash) {
|
|
if (have_expected_ref) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing cursor fields");
|
|
}
|
|
|
|
fed_cursor_parse_fail:
|
|
if (parse_error != NULL) {
|
|
free(body);
|
|
body = NULL;
|
|
if (have_record_hash) {
|
|
amduat_reference_free(&last_record_ref);
|
|
}
|
|
if (have_expected_ref) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", parse_error);
|
|
}
|
|
|
|
amduatd_fed_cursor_record_init(&cursor);
|
|
cursor.peer_key = strdup(peer_buf);
|
|
if (cursor.peer_key == NULL) {
|
|
if (have_record_hash) {
|
|
amduat_reference_free(&last_record_ref);
|
|
}
|
|
if (have_expected_ref) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
cursor.space_id = strdup((const char *)req->effective_space->space_id.data);
|
|
if (cursor.space_id == NULL) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
if (have_record_hash) {
|
|
amduat_reference_free(&last_record_ref);
|
|
}
|
|
if (have_expected_ref) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
}
|
|
}
|
|
cursor.has_logseq = have_logseq;
|
|
cursor.last_logseq = last_logseq;
|
|
if (have_record_hash) {
|
|
cursor.has_record_ref = true;
|
|
cursor.last_record_ref = last_record_ref;
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
if (have_expected_ref) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
status = amduatd_fed_cursor_cas_set_remote(
|
|
store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
have_expected_ref ? &expected_ref : NULL,
|
|
&cursor,
|
|
&new_ref);
|
|
|
|
if (have_expected_ref) {
|
|
amduat_reference_free(&expected_ref);
|
|
}
|
|
|
|
if (status == AMDUATD_FED_CURSOR_ERR_CONFLICT) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
return amduatd_send_json_error(fd, 409, "Conflict", "cursor conflict");
|
|
}
|
|
if (status == AMDUATD_FED_CURSOR_ERR_INVALID) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid cursor");
|
|
}
|
|
if (status != AMDUATD_FED_CURSOR_OK) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"cursor update failed");
|
|
}
|
|
|
|
if (!amduat_asl_ref_encode_hex(new_ref, &ref_hex)) {
|
|
amduat_reference_free(&new_ref);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
amduat_reference_free(&new_ref);
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"}\n")) {
|
|
goto fed_cursor_post_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
fed_cursor_post_cleanup:
|
|
amduatd_strbuf_free(&b);
|
|
free(ref_hex);
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
if (!ok) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_handle_post_fed_ingest(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req) {
|
|
char record_type_buf[32];
|
|
char ref_buf[AMDUATD_REF_TEXT_MAX + 1u];
|
|
char target_ref_buf[AMDUATD_REF_TEXT_MAX + 1u];
|
|
uint8_t *body = NULL;
|
|
const char *p = NULL;
|
|
const char *end = NULL;
|
|
bool have_record_type = false;
|
|
bool have_query_record_type = false;
|
|
bool have_ref = false;
|
|
bool have_ref_field = false;
|
|
bool have_target_ref_field = false;
|
|
bool have_body = false;
|
|
bool already_present = false;
|
|
amduat_fed_record_type_t record_type = AMDUAT_FED_REC_ARTIFACT;
|
|
amduat_fed_record_type_t query_record_type = AMDUAT_FED_REC_ARTIFACT;
|
|
amduat_reference_t ref;
|
|
amduat_reference_t stored_ref;
|
|
amduat_asl_store_error_t store_err;
|
|
amduat_asl_index_state_t state;
|
|
amduat_asl_io_format_t input_format = AMDUAT_ASL_IO_RAW;
|
|
amduat_type_tag_t type_tag = amduat_type_tag(0u);
|
|
bool has_type_tag = false;
|
|
|
|
memset(&ref, 0, sizeof(ref));
|
|
memset(&stored_ref, 0, sizeof(stored_ref));
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || dcfg == NULL) {
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"error",
|
|
false,
|
|
NULL,
|
|
req != NULL ? req->effective_space
|
|
: NULL);
|
|
}
|
|
if (!fed_cfg->enabled) {
|
|
amduatd_fed_ingest_discard_body(fd, req, AMDUATD_FED_INGEST_MAX_BYTES);
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
503,
|
|
"Service Unavailable",
|
|
"error",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
{
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
const char *err = NULL;
|
|
if (!amduatd_fed_scope_names(fed_cfg,
|
|
req->effective_space,
|
|
"fed",
|
|
&scoped,
|
|
&err)) {
|
|
amduatd_fed_ingest_discard_body(fd, req, AMDUATD_FED_INGEST_MAX_BYTES);
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
amduat_octets_free(&scoped);
|
|
return ok;
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
}
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
amduatd_fed_ingest_discard_body(fd, req, AMDUATD_FED_INGEST_MAX_BYTES);
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
403,
|
|
"Forbidden",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
403,
|
|
"Forbidden",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path,
|
|
"record_type",
|
|
record_type_buf,
|
|
sizeof(record_type_buf)) != NULL) {
|
|
size_t len = strlen(record_type_buf);
|
|
if (!amduatd_fed_ingest_parse_record_type(record_type_buf,
|
|
len,
|
|
&query_record_type)) {
|
|
amduatd_fed_ingest_discard_body(fd, req, AMDUATD_FED_INGEST_MAX_BYTES);
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
have_record_type = true;
|
|
have_query_record_type = true;
|
|
record_type = query_record_type;
|
|
}
|
|
if (amduatd_query_param(req->path, "ref", ref_buf, sizeof(ref_buf)) != NULL &&
|
|
ref_buf[0] != '\0') {
|
|
if (!amduat_asl_ref_decode_hex(ref_buf, &ref)) {
|
|
amduatd_fed_ingest_discard_body(fd, req, AMDUATD_FED_INGEST_MAX_BYTES);
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
have_ref = true;
|
|
have_ref_field = true;
|
|
}
|
|
if (amduatd_query_param(req->path,
|
|
"target_ref",
|
|
target_ref_buf,
|
|
sizeof(target_ref_buf)) != NULL &&
|
|
target_ref_buf[0] != '\0') {
|
|
if (have_ref_field) {
|
|
if (have_ref) {
|
|
amduatd_fed_ingest_discard_body(fd, req, AMDUATD_FED_INGEST_MAX_BYTES);
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
amduatd_fed_ingest_discard_body(fd, req, AMDUATD_FED_INGEST_MAX_BYTES);
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
if (!amduat_asl_ref_decode_hex(target_ref_buf, &ref)) {
|
|
amduatd_fed_ingest_discard_body(fd, req, AMDUATD_FED_INGEST_MAX_BYTES);
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
have_ref = true;
|
|
have_target_ref_field = true;
|
|
}
|
|
|
|
if (req->content_length > AMDUATD_FED_INGEST_MAX_BYTES) {
|
|
amduatd_fed_ingest_discard_body(fd, req, AMDUATD_FED_INGEST_MAX_BYTES);
|
|
if (have_ref) {
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
413,
|
|
"Payload Too Large",
|
|
"invalid",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
413,
|
|
"Payload Too Large",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
|
|
if (req->content_type[0] != '\0' &&
|
|
strstr(req->content_type, "application/json") != NULL) {
|
|
const char *parse_error = NULL;
|
|
bool json_have_record_type = false;
|
|
bool json_have_ref = false;
|
|
bool json_have_target_ref = false;
|
|
amduat_fed_record_type_t json_record_type = AMDUAT_FED_REC_ARTIFACT;
|
|
|
|
if (req->content_length == 0u) {
|
|
if (have_ref) {
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
body = (uint8_t *)malloc(req->content_length);
|
|
if (body == NULL) {
|
|
if (have_ref) {
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"error",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"error",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
if (!amduatd_read_exact(fd, body, req->content_length)) {
|
|
free(body);
|
|
if (have_ref) {
|
|
amduat_reference_free(&ref);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
p = (const char *)body;
|
|
end = (const char *)body + req->content_length;
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
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, ':')) {
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
|
|
if (key_len == strlen("record_type") &&
|
|
memcmp(key, "record_type", key_len) == 0) {
|
|
if (json_have_record_type ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_fed_ingest_parse_record_type(sv,
|
|
sv_len,
|
|
&json_record_type)) {
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
json_have_record_type = true;
|
|
} else if (key_len == strlen("ref") &&
|
|
memcmp(key, "ref", key_len) == 0) {
|
|
amduat_reference_t json_ref;
|
|
if (json_have_ref ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_decode_ref_hex_str(sv, sv_len, &json_ref)) {
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
if (have_target_ref_field) {
|
|
amduat_reference_free(&json_ref);
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
if (have_ref && have_ref_field) {
|
|
if (!amduat_reference_eq(ref, json_ref)) {
|
|
amduat_reference_free(&json_ref);
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
amduat_reference_free(&json_ref);
|
|
} else {
|
|
if (have_ref) {
|
|
amduat_reference_free(&ref);
|
|
}
|
|
ref = json_ref;
|
|
have_ref = true;
|
|
}
|
|
json_have_ref = true;
|
|
have_ref_field = true;
|
|
} else if (key_len == strlen("target_ref") &&
|
|
memcmp(key, "target_ref", key_len) == 0) {
|
|
amduat_reference_t json_ref;
|
|
if (json_have_target_ref ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_decode_ref_hex_str(sv, sv_len, &json_ref)) {
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
if (have_ref_field) {
|
|
amduat_reference_free(&json_ref);
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
if (have_ref && have_target_ref_field) {
|
|
if (!amduat_reference_eq(ref, json_ref)) {
|
|
amduat_reference_free(&json_ref);
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
amduat_reference_free(&json_ref);
|
|
} else {
|
|
if (have_ref) {
|
|
amduat_reference_free(&ref);
|
|
}
|
|
ref = json_ref;
|
|
have_ref = true;
|
|
}
|
|
json_have_target_ref = true;
|
|
have_target_ref_field = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
}
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
|
|
if (json_have_record_type) {
|
|
if (have_query_record_type && json_record_type != query_record_type) {
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
have_record_type = true;
|
|
record_type = json_record_type;
|
|
}
|
|
if (!have_record_type || !have_ref) {
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
if (record_type != AMDUAT_FED_REC_TOMBSTONE) {
|
|
parse_error = "invalid";
|
|
goto ingest_json_parse_fail;
|
|
}
|
|
free(body);
|
|
body = NULL;
|
|
have_body = false;
|
|
|
|
ingest_json_parse_fail:
|
|
if (parse_error != NULL) {
|
|
free(body);
|
|
body = NULL;
|
|
if (have_ref) {
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
} else {
|
|
if (req->content_length > 0u) {
|
|
body = (uint8_t *)malloc(req->content_length);
|
|
if (body == NULL) {
|
|
if (have_ref) {
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"error",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"error",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
if (!amduatd_read_exact(fd, body, req->content_length)) {
|
|
free(body);
|
|
if (have_ref) {
|
|
amduat_reference_free(&ref);
|
|
}
|
|
return false;
|
|
}
|
|
have_body = true;
|
|
}
|
|
}
|
|
|
|
if (!have_record_type || !have_ref) {
|
|
free(body);
|
|
if (have_ref) {
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
return amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
}
|
|
|
|
if (record_type != AMDUAT_FED_REC_TOMBSTONE) {
|
|
amduat_artifact_t artifact;
|
|
amduat_artifact_t existing_artifact;
|
|
amduat_asl_store_error_t exist_err;
|
|
amduat_octets_t artifact_input;
|
|
bool artifact_input_owned = false;
|
|
|
|
if (!have_body || req->content_length == 0u) {
|
|
free(body);
|
|
{
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
if (req->content_type[0] != '\0' &&
|
|
strstr(req->content_type,
|
|
"application/vnd.amduat.asl.artifact+v1") != NULL) {
|
|
input_format = AMDUAT_ASL_IO_ARTIFACT;
|
|
}
|
|
if (record_type == AMDUAT_FED_REC_TGK_EDGE) {
|
|
type_tag = amduat_type_tag(AMDUAT_TYPE_TAG_TGK1_EDGE_V1);
|
|
has_type_tag = true;
|
|
} else if (record_type == AMDUAT_FED_REC_PER) {
|
|
type_tag = amduat_type_tag(AMDUAT_TYPE_TAG_FER1_RECEIPT_1);
|
|
has_type_tag = true;
|
|
}
|
|
|
|
memset(&existing_artifact, 0, sizeof(existing_artifact));
|
|
exist_err = amduat_asl_store_get(store, ref, &existing_artifact);
|
|
if (exist_err == AMDUAT_ASL_STORE_OK) {
|
|
already_present = true;
|
|
amduat_asl_artifact_free(&existing_artifact);
|
|
}
|
|
|
|
artifact_input = amduat_octets(body, req->content_length);
|
|
if (input_format == AMDUAT_ASL_IO_RAW) {
|
|
if (!amduat_octets_clone(artifact_input, &artifact_input)) {
|
|
free(body);
|
|
{
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"error",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
}
|
|
artifact_input_owned = true;
|
|
}
|
|
|
|
memset(&artifact, 0, sizeof(artifact));
|
|
if (!amduat_asl_artifact_from_bytes(artifact_input,
|
|
input_format,
|
|
has_type_tag,
|
|
type_tag,
|
|
&artifact)) {
|
|
if (artifact_input_owned) {
|
|
amduat_octets_free(&artifact_input);
|
|
}
|
|
free(body);
|
|
{
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
}
|
|
free(body);
|
|
body = NULL;
|
|
|
|
if (input_format == AMDUAT_ASL_IO_ARTIFACT && has_type_tag &&
|
|
(!artifact.has_type_tag ||
|
|
artifact.type_tag.tag_id != type_tag.tag_id)) {
|
|
amduat_asl_artifact_free(&artifact);
|
|
{
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
if (store->ops.put_indexed == NULL) {
|
|
amduat_asl_artifact_free(&artifact);
|
|
{
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
501,
|
|
"Not Implemented",
|
|
"error",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
}
|
|
store_err = amduat_asl_store_put_indexed(store, artifact, &stored_ref,
|
|
&state);
|
|
amduat_asl_artifact_free(&artifact);
|
|
if (store_err != AMDUAT_ASL_STORE_OK) {
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"error",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
if (!amduat_reference_eq(ref, stored_ref)) {
|
|
amduat_reference_free(&stored_ref);
|
|
{
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
NULL,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
}
|
|
amduat_reference_free(&stored_ref);
|
|
{
|
|
bool ok = amduatd_fed_ingest_send_response(
|
|
fd,
|
|
200,
|
|
"OK",
|
|
already_present ? "already_present" : "ok",
|
|
!already_present,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
if (have_body && req->content_length != 0u) {
|
|
free(body);
|
|
{
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
400,
|
|
"Bad Request",
|
|
"invalid",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
}
|
|
free(body);
|
|
body = NULL;
|
|
if (store->ops.tombstone == NULL) {
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
501,
|
|
"Not Implemented",
|
|
"error",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
store_err = amduat_asl_store_tombstone(store, ref, 0u, 0u, &state);
|
|
if (store_err != AMDUAT_ASL_STORE_OK) {
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
500,
|
|
"Internal Server Error",
|
|
"error",
|
|
false,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
{
|
|
bool ok = amduatd_fed_ingest_send_response(fd,
|
|
200,
|
|
"OK",
|
|
"ok",
|
|
true,
|
|
&ref,
|
|
req->effective_space);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
static bool amduatd_handle_get_fed_pull_plan(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char peer_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
char remote_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
const char *remote_space_id = NULL;
|
|
const char *remote_error = NULL;
|
|
char limit_buf[32];
|
|
uint64_t limit = 128u;
|
|
uint64_t from_logseq = 0u;
|
|
uint32_t domain_id = 0u;
|
|
amduatd_fed_pull_plan_status_t plan_status;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduatd_fed_cursor_record_t cursor;
|
|
amduat_reference_t cursor_ref;
|
|
bool cursor_present = false;
|
|
amduat_fed_transport_unix_t transport;
|
|
amduat_fed_transport_t ops;
|
|
amduat_fed_record_t *records = NULL;
|
|
size_t record_len = 0;
|
|
size_t record_len_total = 0;
|
|
int remote_status = 0;
|
|
char *remote_body = NULL;
|
|
char *json = NULL;
|
|
amduatd_fed_pull_plan_input_t input;
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || root_path == NULL ||
|
|
dcfg == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
plan_status = amduatd_fed_pull_plan_check(fed_cfg, store);
|
|
if (plan_status == AMDUATD_FED_PULL_PLAN_ERR_DISABLED) {
|
|
return amduatd_send_json_error(fd, 503, "Service Unavailable",
|
|
"federation disabled");
|
|
}
|
|
if (plan_status == AMDUATD_FED_PULL_PLAN_ERR_UNSUPPORTED) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"requires index backend");
|
|
}
|
|
if (plan_status != AMDUATD_FED_PULL_PLAN_OK) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"plan unavailable");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "peer",
|
|
peer_buf, sizeof(peer_buf)) == NULL ||
|
|
peer_buf[0] == '\0') {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing peer");
|
|
}
|
|
if (!amduatd_get_remote_space_id(req,
|
|
remote_buf,
|
|
sizeof(remote_buf),
|
|
&remote_space_id,
|
|
&remote_error)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
remote_error);
|
|
}
|
|
{
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
if (remote_space_id != NULL) {
|
|
if (!amduatd_fed_cursor_pointer_name_v2(req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
} else if (!amduatd_fed_cursor_pointer_name(req->effective_space,
|
|
peer_buf,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
}
|
|
if (!amduatd_parse_u32_str(peer_buf, &domain_id)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "limit",
|
|
limit_buf, sizeof(limit_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(limit_buf, &limit) || limit == 0u ||
|
|
limit > 10000u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid limit");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
amduatd_fed_cursor_record_init(&cursor);
|
|
memset(&cursor_ref, 0, sizeof(cursor_ref));
|
|
{
|
|
amduatd_fed_cursor_status_t cursor_status;
|
|
cursor_status = amduatd_fed_cursor_get_remote(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&cursor,
|
|
&cursor_ref);
|
|
if (cursor_status == AMDUATD_FED_CURSOR_ERR_NOT_FOUND) {
|
|
cursor_present = false;
|
|
} else if (cursor_status == AMDUATD_FED_CURSOR_OK) {
|
|
cursor_present = true;
|
|
if (cursor.has_logseq) {
|
|
if (cursor.last_logseq == UINT64_MAX) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&cursor_ref);
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"cursor logseq overflow");
|
|
}
|
|
from_logseq = cursor.last_logseq + 1u;
|
|
}
|
|
} else if (cursor_status == AMDUATD_FED_CURSOR_ERR_INVALID) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&cursor_ref);
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
} else {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&cursor_ref);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"cursor read failed");
|
|
}
|
|
}
|
|
|
|
if (fed_cfg->transport_kind != AMDUATD_FED_TRANSPORT_UNIX ||
|
|
!fed_cfg->unix_socket_set) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&cursor_ref);
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"federation transport unavailable");
|
|
}
|
|
if (!amduat_fed_transport_unix_init(&transport, fed_cfg->unix_socket_path)) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&cursor_ref);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"transport init failed");
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
(void)amduat_fed_transport_unix_set_space(
|
|
&transport, (const char *)req->effective_space->space_id.data);
|
|
}
|
|
|
|
if (!amduat_fed_transport_unix_get_records_with_limit(&transport,
|
|
domain_id,
|
|
from_logseq,
|
|
limit,
|
|
&remote_status,
|
|
&records,
|
|
&record_len,
|
|
&remote_body)) {
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&cursor_ref);
|
|
free(remote_body);
|
|
return amduatd_send_json_error(fd, 502, "Bad Gateway",
|
|
"remote fetch failed");
|
|
}
|
|
if (remote_status != 200) {
|
|
const char *msg = "remote error";
|
|
if (remote_status == 501) {
|
|
msg = "remote requires index backend";
|
|
} else if (remote_status == 403) {
|
|
msg = "remote forbidden";
|
|
} else if (remote_status == 503) {
|
|
msg = "remote federation disabled";
|
|
} else if (remote_body != NULL && remote_body[0] != '\0') {
|
|
msg = remote_body;
|
|
}
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&cursor_ref);
|
|
free(remote_body);
|
|
return amduatd_send_json_error(fd, 502, "Bad Gateway", msg);
|
|
}
|
|
free(remote_body);
|
|
remote_body = NULL;
|
|
|
|
record_len_total = record_len;
|
|
if (record_len > limit) {
|
|
record_len = (size_t)limit;
|
|
}
|
|
|
|
memset(&input, 0, sizeof(input));
|
|
input.peer_key = peer_buf;
|
|
input.effective_space = req->effective_space;
|
|
input.cursor_present = cursor_present;
|
|
input.cursor = cursor_present ? &cursor : NULL;
|
|
input.cursor_ref = cursor_present ? &cursor_ref : NULL;
|
|
input.records = records;
|
|
input.record_count = record_len;
|
|
|
|
plan_status = amduatd_fed_pull_plan_json(&input, &json);
|
|
if (plan_status != AMDUATD_FED_PULL_PLAN_OK || json == NULL) {
|
|
ops = amduat_fed_transport_unix_ops(&transport);
|
|
if (ops.free_records != NULL && records != NULL) {
|
|
ops.free_records(ops.ctx, records, record_len_total);
|
|
}
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&cursor_ref);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"plan encode failed");
|
|
}
|
|
|
|
{
|
|
bool ok = amduatd_http_send_json(fd, 200, "OK", json, false);
|
|
free(json);
|
|
ops = amduat_fed_transport_unix_ops(&transport);
|
|
if (ops.free_records != NULL && records != NULL) {
|
|
ops.free_records(ops.ctx, records, record_len_total);
|
|
}
|
|
amduatd_fed_cursor_record_free(&cursor);
|
|
amduat_reference_free(&cursor_ref);
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
static bool amduatd_handle_get_fed_push_plan(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char peer_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
char remote_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
const char *remote_space_id = NULL;
|
|
const char *remote_error = NULL;
|
|
char limit_buf[32];
|
|
uint64_t limit = 128u;
|
|
uint32_t domain_id = 0u;
|
|
amduatd_fed_push_plan_status_t plan_status;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_asl_index_state_t state;
|
|
amduatd_fed_push_plan_scan_t scan;
|
|
char *json = NULL;
|
|
amduatd_fed_push_plan_input_t input;
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || root_path == NULL ||
|
|
dcfg == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
plan_status = amduatd_fed_push_plan_check(fed_cfg, store);
|
|
if (plan_status == AMDUATD_FED_PUSH_PLAN_ERR_DISABLED) {
|
|
return amduatd_send_json_error(fd, 503, "Service Unavailable",
|
|
"federation disabled");
|
|
}
|
|
if (plan_status == AMDUATD_FED_PUSH_PLAN_ERR_UNSUPPORTED) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"requires index backend");
|
|
}
|
|
if (plan_status != AMDUATD_FED_PUSH_PLAN_OK) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"plan unavailable");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "peer",
|
|
peer_buf, sizeof(peer_buf)) == NULL ||
|
|
peer_buf[0] == '\0') {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing peer");
|
|
}
|
|
if (!amduatd_get_remote_space_id(req,
|
|
remote_buf,
|
|
sizeof(remote_buf),
|
|
&remote_space_id,
|
|
&remote_error)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
remote_error);
|
|
}
|
|
{
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
if (remote_space_id != NULL) {
|
|
if (!amduatd_fed_push_cursor_pointer_name_v2(req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
} else if (!amduatd_fed_push_cursor_pointer_name(req->effective_space,
|
|
peer_buf,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
}
|
|
if (!amduatd_parse_u32_str(peer_buf, &domain_id)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "limit",
|
|
limit_buf, sizeof(limit_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(limit_buf, &limit) || limit == 0u ||
|
|
limit > 2048u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid limit");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
if (!amduat_asl_index_current_state(store, &state)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"store error");
|
|
}
|
|
(void)state;
|
|
|
|
plan_status = amduatd_fed_push_plan_scan(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
limit,
|
|
root_path,
|
|
&scan);
|
|
if (plan_status != AMDUATD_FED_PUSH_PLAN_OK) {
|
|
amduatd_fed_push_plan_scan_free(&scan);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"plan scan failed");
|
|
}
|
|
|
|
memset(&input, 0, sizeof(input));
|
|
input.peer_key = peer_buf;
|
|
input.domain_id = domain_id;
|
|
input.effective_space = req->effective_space;
|
|
input.cursor_present = scan.cursor_present;
|
|
input.cursor = scan.cursor_present ? &scan.cursor : NULL;
|
|
input.cursor_ref = scan.cursor_present ? &scan.cursor_ref : NULL;
|
|
input.records = scan.records;
|
|
input.record_count = scan.record_count;
|
|
|
|
plan_status = amduatd_fed_push_plan_json(&input, &json);
|
|
if (plan_status != AMDUATD_FED_PUSH_PLAN_OK || json == NULL) {
|
|
amduatd_fed_push_plan_scan_free(&scan);
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"plan encode failed");
|
|
}
|
|
|
|
{
|
|
bool ok = amduatd_http_send_json(fd, 200, "OK", json, false);
|
|
free(json);
|
|
amduatd_fed_push_plan_scan_free(&scan);
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
static bool amduatd_handle_post_artifacts(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
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;
|
|
amduat_asl_index_state_t state;
|
|
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 || root_path == 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);
|
|
}
|
|
|
|
if (store->ops.put_indexed != NULL) {
|
|
err = amduat_asl_store_put_indexed(store, artifact, &ref, &state);
|
|
} else {
|
|
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);
|
|
}
|
|
|
|
{
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_asl_log_store_t log_store;
|
|
amduat_octets_t log_name = amduat_octets(NULL, 0u);
|
|
amduat_asl_log_entry_t entry;
|
|
uint64_t offset = 0u;
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path) ||
|
|
!amduat_asl_log_store_init(&log_store, root_path, store,
|
|
&pointer_store)) {
|
|
free(ref_hex);
|
|
free((void *)ref.digest.data);
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"log store error\n", false);
|
|
}
|
|
if (!amduatd_fed_records_log_name(req->effective_space, &log_name)) {
|
|
free(ref_hex);
|
|
free((void *)ref.digest.data);
|
|
return amduatd_http_send_text(fd, 400, "Bad Request",
|
|
"invalid X-Amduat-Space\n", false);
|
|
}
|
|
memset(&entry, 0, sizeof(entry));
|
|
entry.kind = AMDUATD_FED_LOG_KIND_ARTIFACT;
|
|
entry.has_timestamp = false;
|
|
entry.timestamp = 0u;
|
|
entry.payload_ref = ref;
|
|
entry.has_actor = false;
|
|
entry.actor = amduat_octets(NULL, 0u);
|
|
err = amduat_asl_log_append(&log_store,
|
|
(const char *)log_name.data,
|
|
&entry,
|
|
1u,
|
|
&offset);
|
|
amduat_octets_free(&log_name);
|
|
if (err != AMDUAT_ASL_STORE_OK) {
|
|
free(ref_hex);
|
|
free((void *)ref.digest.data);
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"log append failed\n", false);
|
|
}
|
|
}
|
|
free((void *)ref.digest.data);
|
|
|
|
{
|
|
int n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", ref_hex);
|
|
free(ref_hex);
|
|
if (n <= 0 || (size_t)n >= sizeof(json)) {
|
|
return amduatd_http_send_text(fd, 500, "Internal Server Error",
|
|
"error\n", false);
|
|
}
|
|
return amduatd_http_send_json(fd, 200, "OK", json, false);
|
|
}
|
|
}
|
|
|
|
static bool amduatd_handle_post_pel_run(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduat_asl_store_fs_config_t *cfg,
|
|
const amduatd_concepts_t *concepts,
|
|
const amduatd_cfg_t *dcfg,
|
|
const char *root_path,
|
|
const amduatd_http_req_t *req) {
|
|
uint8_t *body = NULL;
|
|
const char *p = NULL;
|
|
const char *end = NULL;
|
|
bool have_program_ref = false;
|
|
bool have_input_refs = false;
|
|
bool have_scheme_ref = false;
|
|
bool scheme_is_dag = false;
|
|
bool has_params_ref = false;
|
|
bool want_receipt = false;
|
|
bool receipt_have_input_manifest = false;
|
|
bool receipt_have_environment = false;
|
|
bool receipt_have_evaluator = false;
|
|
bool receipt_have_executor = false;
|
|
bool receipt_have_started = false;
|
|
bool receipt_have_completed = false;
|
|
bool receipt_has_sbom = false;
|
|
bool receipt_has_executor_fingerprint = false;
|
|
bool receipt_has_run_id = false;
|
|
bool receipt_has_limits = false;
|
|
bool receipt_has_logs = false;
|
|
bool receipt_has_determinism = false;
|
|
bool receipt_has_rng_seed = false;
|
|
bool receipt_has_signature = false;
|
|
char *receipt_evaluator_id = NULL;
|
|
uint8_t *receipt_parity_digest = NULL;
|
|
size_t receipt_parity_digest_len = 0;
|
|
uint8_t *receipt_run_id = NULL;
|
|
size_t receipt_run_id_len = 0;
|
|
uint8_t *receipt_rng_seed = NULL;
|
|
size_t receipt_rng_seed_len = 0;
|
|
uint8_t *receipt_signature = NULL;
|
|
size_t receipt_signature_len = 0;
|
|
uint64_t receipt_started_at = 0;
|
|
uint64_t receipt_completed_at = 0;
|
|
uint8_t receipt_determinism_level = 0;
|
|
amduat_reference_t receipt_input_manifest_ref;
|
|
amduat_reference_t receipt_environment_ref;
|
|
amduat_reference_t receipt_executor_ref;
|
|
amduat_reference_t receipt_sbom_ref;
|
|
amduat_reference_t receipt_executor_fingerprint_ref;
|
|
amduat_fer1_limits_t receipt_limits;
|
|
amduat_fer1_log_entry_t *receipt_logs = NULL;
|
|
size_t receipt_logs_len = 0;
|
|
size_t receipt_logs_cap = 0;
|
|
amduat_reference_t scheme_ref;
|
|
amduat_reference_t program_ref;
|
|
amduat_reference_t params_ref;
|
|
amduat_reference_t *input_refs = NULL;
|
|
size_t input_refs_len = 0;
|
|
amduat_pel_run_result_t run_result;
|
|
amduat_artifact_t receipt_artifact;
|
|
amduat_reference_t receipt_ref;
|
|
bool receipt_emitted = false;
|
|
int status_code = 200;
|
|
const char *status_reason = "OK";
|
|
bool ok = false;
|
|
|
|
memset(&scheme_ref, 0, sizeof(scheme_ref));
|
|
memset(&program_ref, 0, sizeof(program_ref));
|
|
memset(¶ms_ref, 0, sizeof(params_ref));
|
|
memset(&run_result, 0, sizeof(run_result));
|
|
memset(&receipt_input_manifest_ref, 0, sizeof(receipt_input_manifest_ref));
|
|
memset(&receipt_environment_ref, 0, sizeof(receipt_environment_ref));
|
|
memset(&receipt_executor_ref, 0, sizeof(receipt_executor_ref));
|
|
memset(&receipt_sbom_ref, 0, sizeof(receipt_sbom_ref));
|
|
memset(&receipt_executor_fingerprint_ref, 0,
|
|
sizeof(receipt_executor_fingerprint_ref));
|
|
memset(&receipt_limits, 0, sizeof(receipt_limits));
|
|
memset(&receipt_artifact, 0, sizeof(receipt_artifact));
|
|
memset(&receipt_ref, 0, sizeof(receipt_ref));
|
|
|
|
if (store == NULL || req == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (cfg == NULL || concepts == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
|
|
if (req->content_length > (1u * 1024u * 1024u)) {
|
|
return amduatd_send_json_error(fd, 413, "Payload Too Large",
|
|
"payload too large");
|
|
}
|
|
|
|
if (req->content_length == 0) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "missing body");
|
|
}
|
|
|
|
body = NULL;
|
|
if (req->content_length != 0) {
|
|
body = (uint8_t *)malloc(req->content_length);
|
|
if (body == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
}
|
|
if (!amduatd_read_exact(fd, body, req->content_length)) {
|
|
free(body);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
p = (const char *)body;
|
|
end = (const char *)body + req->content_length;
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_run_cleanup;
|
|
}
|
|
|
|
for (;;) {
|
|
const char *key = NULL;
|
|
size_t key_len = 0;
|
|
const char *sv = NULL;
|
|
size_t sv_len = 0;
|
|
const char *cur = NULL;
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid json key");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_run_cleanup;
|
|
}
|
|
|
|
if (key_len == strlen("program_ref") &&
|
|
memcmp(key, "program_ref", key_len) == 0) {
|
|
amduatd_ref_status_t st;
|
|
if (have_program_ref) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"duplicate program_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid program_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv,
|
|
sv_len,
|
|
&program_ref);
|
|
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
|
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
|
"program_ref not found");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (st != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid program_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
have_program_ref = true;
|
|
} else if (key_len == strlen("scheme_ref") &&
|
|
memcmp(key, "scheme_ref", key_len) == 0) {
|
|
if (have_scheme_ref) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"duplicate scheme_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid scheme_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (sv_len == 3 && memcmp(sv, "dag", 3) == 0) {
|
|
scheme_ref = amduat_pel_program_dag_scheme_ref();
|
|
scheme_is_dag = true;
|
|
} else if (!amduatd_decode_ref_hex_str(sv, sv_len, &scheme_ref)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid scheme_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
have_scheme_ref = true;
|
|
} else if (key_len == strlen("params_ref") &&
|
|
memcmp(key, "params_ref", key_len) == 0) {
|
|
amduatd_ref_status_t st;
|
|
if (has_params_ref) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"duplicate params_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid params_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg, sv,
|
|
sv_len,
|
|
¶ms_ref);
|
|
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
|
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
|
"params_ref not found");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (st != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid params_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
has_params_ref = true;
|
|
} else if (key_len == strlen("input_refs") &&
|
|
memcmp(key, "input_refs", key_len) == 0) {
|
|
size_t cap = 0;
|
|
if (have_input_refs) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"duplicate input_refs");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduatd_json_expect(&p, end, '[')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input_refs");
|
|
goto pel_run_cleanup;
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
have_input_refs = true;
|
|
} else {
|
|
for (;;) {
|
|
amduat_reference_t ref;
|
|
memset(&ref, 0, sizeof(ref));
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
{
|
|
amduatd_ref_status_t st = amduatd_decode_ref_or_name_latest(
|
|
store, cfg, concepts, dcfg, sv, sv_len, &ref);
|
|
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
|
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
|
"input_ref not found");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (st != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
if (input_refs_len == cap) {
|
|
size_t next_cap = cap != 0 ? cap * 2u : 4u;
|
|
amduat_reference_t *next;
|
|
if (next_cap > (SIZE_MAX / sizeof(*input_refs))) {
|
|
amduat_reference_free(&ref);
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"too many input_refs");
|
|
goto pel_run_cleanup;
|
|
}
|
|
next = (amduat_reference_t *)realloc(
|
|
input_refs, next_cap * sizeof(*input_refs));
|
|
if (next == NULL) {
|
|
amduat_reference_free(&ref);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"oom");
|
|
goto pel_run_cleanup;
|
|
}
|
|
input_refs = next;
|
|
cap = next_cap;
|
|
}
|
|
input_refs[input_refs_len++] = ref;
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur >= end) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input_refs");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (*cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (*cur == ']') {
|
|
p = cur + 1;
|
|
have_input_refs = true;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input_refs");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
} else if (key_len == strlen("receipt") &&
|
|
memcmp(key, "receipt", key_len) == 0) {
|
|
const char *rkey = NULL;
|
|
size_t rkey_len = 0;
|
|
if (want_receipt) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"duplicate receipt");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid receipt");
|
|
goto pel_run_cleanup;
|
|
}
|
|
want_receipt = true;
|
|
for (;;) {
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &rkey, &rkey_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid receipt");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (rkey_len == strlen("input_manifest_ref") &&
|
|
memcmp(rkey, "input_manifest_ref", rkey_len) == 0) {
|
|
amduatd_ref_status_t st;
|
|
if (receipt_have_input_manifest ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input_manifest_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg,
|
|
sv, sv_len,
|
|
&receipt_input_manifest_ref);
|
|
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
|
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
|
"input_manifest_ref not found");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (st != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input_manifest_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_have_input_manifest = true;
|
|
} else if (rkey_len == strlen("environment_ref") &&
|
|
memcmp(rkey, "environment_ref", rkey_len) == 0) {
|
|
amduatd_ref_status_t st;
|
|
if (receipt_have_environment ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid environment_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg,
|
|
sv, sv_len,
|
|
&receipt_environment_ref);
|
|
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
|
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
|
"environment_ref not found");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (st != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid environment_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_have_environment = true;
|
|
} else if (rkey_len == strlen("evaluator_id") &&
|
|
memcmp(rkey, "evaluator_id", rkey_len) == 0) {
|
|
char *tmp = NULL;
|
|
if (receipt_have_evaluator ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &tmp)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid evaluator_id");
|
|
free(tmp);
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(receipt_evaluator_id);
|
|
receipt_evaluator_id = tmp;
|
|
receipt_have_evaluator = true;
|
|
} else if (rkey_len == strlen("executor_ref") &&
|
|
memcmp(rkey, "executor_ref", rkey_len) == 0) {
|
|
amduatd_ref_status_t st;
|
|
if (receipt_have_executor ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid executor_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg,
|
|
sv, sv_len,
|
|
&receipt_executor_ref);
|
|
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
|
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
|
"executor_ref not found");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (st != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid executor_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_have_executor = true;
|
|
} else if (rkey_len == strlen("sbom_ref") &&
|
|
memcmp(rkey, "sbom_ref", rkey_len) == 0) {
|
|
amduatd_ref_status_t st;
|
|
if (receipt_has_sbom ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid sbom_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, dcfg,
|
|
sv, sv_len, &receipt_sbom_ref);
|
|
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
|
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
|
"sbom_ref not found");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (st != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid sbom_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_has_sbom = true;
|
|
} else if (rkey_len == strlen("executor_fingerprint_ref") &&
|
|
memcmp(rkey, "executor_fingerprint_ref", rkey_len) == 0) {
|
|
amduatd_ref_status_t st;
|
|
if (receipt_has_executor_fingerprint ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid executor_fingerprint_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
st = amduatd_decode_ref_or_name_latest(
|
|
store, cfg, concepts, dcfg, sv, sv_len,
|
|
&receipt_executor_fingerprint_ref);
|
|
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
|
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
|
"executor_fingerprint_ref not found");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (st != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid executor_fingerprint_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_has_executor_fingerprint = true;
|
|
} else if (rkey_len == strlen("parity_digest_hex") &&
|
|
memcmp(rkey, "parity_digest_hex", rkey_len) == 0) {
|
|
char *tmp = NULL;
|
|
uint8_t *bytes = NULL;
|
|
size_t bytes_len = 0;
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &tmp)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid parity_digest_hex");
|
|
free(tmp);
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) {
|
|
free(tmp);
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid parity_digest_hex");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(tmp);
|
|
free(receipt_parity_digest);
|
|
receipt_parity_digest = bytes;
|
|
receipt_parity_digest_len = bytes_len;
|
|
} else if (rkey_len == strlen("run_id_hex") &&
|
|
memcmp(rkey, "run_id_hex", rkey_len) == 0) {
|
|
char *tmp = NULL;
|
|
uint8_t *bytes = NULL;
|
|
size_t bytes_len = 0;
|
|
if (receipt_has_run_id ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &tmp)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid run_id_hex");
|
|
free(tmp);
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) {
|
|
free(tmp);
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid run_id_hex");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(tmp);
|
|
free(receipt_run_id);
|
|
receipt_run_id = bytes;
|
|
receipt_run_id_len = bytes_len;
|
|
receipt_has_run_id = true;
|
|
} else if (rkey_len == strlen("limits") &&
|
|
memcmp(rkey, "limits", rkey_len) == 0) {
|
|
bool have_cpu = false;
|
|
bool have_wall = false;
|
|
bool have_rss = false;
|
|
bool have_reads = false;
|
|
bool have_writes = false;
|
|
if (receipt_has_limits || !amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits");
|
|
goto pel_run_cleanup;
|
|
}
|
|
for (;;) {
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (sv_len == strlen("cpu_ms") &&
|
|
memcmp(sv, "cpu_ms", sv_len) == 0) {
|
|
if (have_cpu || !amduatd_json_parse_u64(&p, end,
|
|
&receipt_limits.cpu_ms)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits cpu_ms");
|
|
goto pel_run_cleanup;
|
|
}
|
|
have_cpu = true;
|
|
} else if (sv_len == strlen("wall_ms") &&
|
|
memcmp(sv, "wall_ms", sv_len) == 0) {
|
|
if (have_wall || !amduatd_json_parse_u64(&p, end,
|
|
&receipt_limits.wall_ms)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits wall_ms");
|
|
goto pel_run_cleanup;
|
|
}
|
|
have_wall = true;
|
|
} else if (sv_len == strlen("max_rss_kib") &&
|
|
memcmp(sv, "max_rss_kib", sv_len) == 0) {
|
|
if (have_rss || !amduatd_json_parse_u64(&p, end,
|
|
&receipt_limits.max_rss_kib)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits max_rss_kib");
|
|
goto pel_run_cleanup;
|
|
}
|
|
have_rss = true;
|
|
} else if (sv_len == strlen("io_reads") &&
|
|
memcmp(sv, "io_reads", sv_len) == 0) {
|
|
if (have_reads || !amduatd_json_parse_u64(&p, end,
|
|
&receipt_limits.io_reads)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits io_reads");
|
|
goto pel_run_cleanup;
|
|
}
|
|
have_reads = true;
|
|
} else if (sv_len == strlen("io_writes") &&
|
|
memcmp(sv, "io_writes", sv_len) == 0) {
|
|
if (have_writes || !amduatd_json_parse_u64(&p, end,
|
|
&receipt_limits.io_writes)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits io_writes");
|
|
goto pel_run_cleanup;
|
|
}
|
|
have_writes = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur >= end) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (*cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (*cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid limits");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!have_cpu || !have_wall || !have_rss ||
|
|
!have_reads || !have_writes) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing limits fields");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_has_limits = true;
|
|
} else if (rkey_len == strlen("logs") &&
|
|
memcmp(rkey, "logs", rkey_len) == 0) {
|
|
if (receipt_has_logs || !amduatd_json_expect(&p, end, '[')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid logs");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_has_logs = true;
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
} else {
|
|
for (;;) {
|
|
amduat_fer1_log_entry_t entry;
|
|
bool have_kind = false;
|
|
bool have_log_ref = false;
|
|
bool have_sha = false;
|
|
memset(&entry, 0, sizeof(entry));
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid log entry");
|
|
goto pel_run_cleanup;
|
|
}
|
|
for (;;) {
|
|
const char *lkey = NULL;
|
|
size_t lkey_len = 0;
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &lkey, &lkey_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid log entry");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (lkey_len == strlen("kind") &&
|
|
memcmp(lkey, "kind", lkey_len) == 0) {
|
|
uint32_t kind = 0;
|
|
if (have_kind ||
|
|
!amduatd_json_parse_u32_loose(&p, end, &kind)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid log kind");
|
|
goto pel_run_cleanup;
|
|
}
|
|
entry.kind = kind;
|
|
have_kind = true;
|
|
} else if (lkey_len == strlen("log_ref") &&
|
|
memcmp(lkey, "log_ref", lkey_len) == 0) {
|
|
amduatd_ref_status_t st;
|
|
if (have_log_ref ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid log_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
st = amduatd_decode_ref_or_name_latest(
|
|
store, cfg, concepts, dcfg, sv, sv_len, &entry.log_ref);
|
|
if (st == AMDUATD_REF_ERR_NOT_FOUND) {
|
|
ok = amduatd_send_json_error(fd, 404, "Not Found",
|
|
"log_ref not found");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (st != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid log_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
have_log_ref = true;
|
|
} else if (lkey_len == strlen("sha256_hex") &&
|
|
memcmp(lkey, "sha256_hex", lkey_len) == 0) {
|
|
char *tmp = NULL;
|
|
uint8_t *bytes = NULL;
|
|
size_t bytes_len = 0;
|
|
if (have_sha ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &tmp)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid sha256_hex");
|
|
free(tmp);
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) {
|
|
free(tmp);
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid sha256_hex");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(tmp);
|
|
entry.sha256 = amduat_octets(bytes, bytes_len);
|
|
have_sha = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid log entry");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur >= end) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid log entry");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (*cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (*cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid log entry");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!have_kind || !have_log_ref || !have_sha) {
|
|
if (have_log_ref) {
|
|
amduat_reference_free(&entry.log_ref);
|
|
}
|
|
if (have_sha) {
|
|
amduat_octets_free(&entry.sha256);
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing log fields");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (receipt_logs_len == receipt_logs_cap) {
|
|
size_t next_cap = receipt_logs_cap != 0 ? receipt_logs_cap * 2u : 4u;
|
|
amduat_fer1_log_entry_t *next =
|
|
(amduat_fer1_log_entry_t *)realloc(
|
|
receipt_logs, next_cap * sizeof(*receipt_logs));
|
|
if (next == NULL) {
|
|
amduat_reference_free(&entry.log_ref);
|
|
amduat_octets_free(&entry.sha256);
|
|
ok = amduatd_send_json_error(fd, 500,
|
|
"Internal Server Error",
|
|
"oom");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_logs = next;
|
|
receipt_logs_cap = next_cap;
|
|
}
|
|
receipt_logs[receipt_logs_len++] = entry;
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid logs");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
} else if (rkey_len == strlen("determinism_level") &&
|
|
memcmp(rkey, "determinism_level", rkey_len) == 0) {
|
|
uint32_t level = 0;
|
|
if (receipt_has_determinism ||
|
|
!amduatd_json_parse_u32_loose(&p, end, &level) ||
|
|
level > 255u) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid determinism_level");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_determinism_level = (uint8_t)level;
|
|
receipt_has_determinism = true;
|
|
} else if (rkey_len == strlen("rng_seed_hex") &&
|
|
memcmp(rkey, "rng_seed_hex", rkey_len) == 0) {
|
|
char *tmp = NULL;
|
|
uint8_t *bytes = NULL;
|
|
size_t bytes_len = 0;
|
|
if (receipt_has_rng_seed ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &tmp)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid rng_seed_hex");
|
|
free(tmp);
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) {
|
|
free(tmp);
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid rng_seed_hex");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(tmp);
|
|
free(receipt_rng_seed);
|
|
receipt_rng_seed = bytes;
|
|
receipt_rng_seed_len = bytes_len;
|
|
receipt_has_rng_seed = true;
|
|
} else if (rkey_len == strlen("signature_hex") &&
|
|
memcmp(rkey, "signature_hex", rkey_len) == 0) {
|
|
char *tmp = NULL;
|
|
uint8_t *bytes = NULL;
|
|
size_t bytes_len = 0;
|
|
if (receipt_has_signature ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &tmp)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid signature_hex");
|
|
free(tmp);
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduat_hex_decode_alloc(tmp, &bytes, &bytes_len)) {
|
|
free(tmp);
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid signature_hex");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(tmp);
|
|
free(receipt_signature);
|
|
receipt_signature = bytes;
|
|
receipt_signature_len = bytes_len;
|
|
receipt_has_signature = true;
|
|
} else if (rkey_len == strlen("started_at") &&
|
|
memcmp(rkey, "started_at", rkey_len) == 0) {
|
|
if (receipt_have_started ||
|
|
!amduatd_json_parse_u64(&p, end, &receipt_started_at)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid started_at");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_have_started = true;
|
|
} else if (rkey_len == strlen("completed_at") &&
|
|
memcmp(rkey, "completed_at", rkey_len) == 0) {
|
|
if (receipt_have_completed ||
|
|
!amduatd_json_parse_u64(&p, end, &receipt_completed_at)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid completed_at");
|
|
goto pel_run_cleanup;
|
|
}
|
|
receipt_have_completed = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid receipt");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur >= end) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid receipt");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (*cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (*cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid receipt");
|
|
goto pel_run_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur >= end) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (*cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (*cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_run_cleanup;
|
|
}
|
|
|
|
p = amduatd_json_skip_ws(p, end);
|
|
if (p != end) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(body);
|
|
body = NULL;
|
|
|
|
if (!have_program_ref) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing program_ref");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!have_input_refs) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing input_refs");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!have_scheme_ref) {
|
|
scheme_ref = amduat_pel_program_dag_scheme_ref();
|
|
scheme_is_dag = true;
|
|
}
|
|
if (want_receipt &&
|
|
(!receipt_have_input_manifest || !receipt_have_environment ||
|
|
!receipt_have_evaluator || !receipt_have_executor ||
|
|
!receipt_have_started || !receipt_have_completed)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing receipt fields");
|
|
goto pel_run_cleanup;
|
|
}
|
|
|
|
if (!amduat_pel_surf_run_with_result(store,
|
|
scheme_ref,
|
|
program_ref,
|
|
input_refs,
|
|
input_refs_len,
|
|
has_params_ref,
|
|
params_ref,
|
|
&run_result)) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pel run failed");
|
|
goto pel_run_cleanup;
|
|
}
|
|
|
|
if (run_result.has_result_value && run_result.result_value.has_store_failure) {
|
|
if (run_result.result_value.store_failure.error_code ==
|
|
AMDUAT_PEL_STORE_ERROR_NOT_FOUND) {
|
|
status_code = 404;
|
|
status_reason = "Not Found";
|
|
} else {
|
|
status_code = 500;
|
|
status_reason = "Internal Server Error";
|
|
}
|
|
}
|
|
|
|
if (want_receipt) {
|
|
amduat_octets_t evaluator_id;
|
|
amduat_octets_t parity_digest;
|
|
bool use_receipt_v1_1 = receipt_has_executor_fingerprint ||
|
|
receipt_has_run_id ||
|
|
receipt_has_limits ||
|
|
receipt_has_logs ||
|
|
receipt_has_determinism ||
|
|
receipt_has_rng_seed ||
|
|
receipt_has_signature;
|
|
|
|
if (!run_result.has_result_value) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"receipt unavailable");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (run_result.result_value.output_refs_len != 1) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"receipt requires single output");
|
|
goto pel_run_cleanup;
|
|
}
|
|
evaluator_id = amduat_octets(receipt_evaluator_id,
|
|
receipt_evaluator_id != NULL
|
|
? strlen(receipt_evaluator_id)
|
|
: 0u);
|
|
parity_digest = amduat_octets(receipt_parity_digest,
|
|
receipt_parity_digest_len);
|
|
if (use_receipt_v1_1) {
|
|
amduat_octets_t run_id = amduat_octets(receipt_run_id,
|
|
receipt_run_id_len);
|
|
amduat_octets_t rng_seed = amduat_octets(receipt_rng_seed,
|
|
receipt_rng_seed_len);
|
|
amduat_octets_t signature = amduat_octets(receipt_signature,
|
|
receipt_signature_len);
|
|
if (!amduat_fer1_receipt_from_pel_run_v1_1(
|
|
&run_result,
|
|
receipt_input_manifest_ref,
|
|
receipt_environment_ref,
|
|
evaluator_id,
|
|
receipt_executor_ref,
|
|
receipt_has_sbom,
|
|
receipt_sbom_ref,
|
|
parity_digest,
|
|
receipt_started_at,
|
|
receipt_completed_at,
|
|
receipt_has_executor_fingerprint,
|
|
receipt_executor_fingerprint_ref,
|
|
receipt_has_run_id,
|
|
run_id,
|
|
receipt_has_limits,
|
|
receipt_limits,
|
|
receipt_logs,
|
|
receipt_logs_len,
|
|
receipt_has_determinism,
|
|
receipt_determinism_level,
|
|
receipt_has_rng_seed,
|
|
rng_seed,
|
|
receipt_has_signature,
|
|
signature,
|
|
&receipt_artifact)) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"receipt build failed");
|
|
goto pel_run_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduat_fer1_receipt_from_pel_result(
|
|
&run_result.result_value,
|
|
receipt_input_manifest_ref,
|
|
receipt_environment_ref,
|
|
evaluator_id,
|
|
receipt_executor_ref,
|
|
receipt_has_sbom,
|
|
receipt_sbom_ref,
|
|
parity_digest,
|
|
receipt_started_at,
|
|
receipt_completed_at,
|
|
&receipt_artifact)) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"receipt build failed");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
if (amduat_asl_store_put(store, receipt_artifact, &receipt_ref) !=
|
|
AMDUAT_ASL_STORE_OK) {
|
|
amduat_asl_artifact_free(&receipt_artifact);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"receipt store failed");
|
|
goto pel_run_cleanup;
|
|
}
|
|
amduat_asl_artifact_free(&receipt_artifact);
|
|
receipt_emitted = true;
|
|
}
|
|
|
|
if (dcfg != NULL && dcfg->derivation_index_enabled) {
|
|
amduat_asl_store_error_t idx_err = AMDUAT_ASL_STORE_OK;
|
|
if (!amduatd_derivation_index_pel_run(root_path,
|
|
true,
|
|
program_ref,
|
|
input_refs,
|
|
input_refs_len,
|
|
has_params_ref,
|
|
params_ref,
|
|
&run_result,
|
|
receipt_emitted,
|
|
receipt_ref,
|
|
&idx_err)) {
|
|
if (dcfg->derivation_index_strict) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"derivation index failed");
|
|
goto pel_run_cleanup;
|
|
}
|
|
amduat_log(AMDUAT_LOG_WARN,
|
|
"pel run derivation index failed: %d",
|
|
(int)idx_err);
|
|
}
|
|
}
|
|
|
|
{
|
|
amduatd_strbuf_t resp;
|
|
char *result_hex = NULL;
|
|
char *trace_hex = NULL;
|
|
char *receipt_hex = NULL;
|
|
const char *status = "UNKNOWN";
|
|
size_t i;
|
|
|
|
memset(&resp, 0, sizeof(resp));
|
|
|
|
if (!amduat_asl_ref_encode_hex(run_result.result_ref, &result_hex)) {
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
goto pel_run_cleanup;
|
|
}
|
|
|
|
if (run_result.has_result_value) {
|
|
status = amduat_format_pel_status_name(
|
|
run_result.result_value.core_result.status);
|
|
if (run_result.result_value.has_trace_ref) {
|
|
if (!amduat_asl_ref_encode_hex(run_result.result_value.trace_ref,
|
|
&trace_hex)) {
|
|
free(result_hex);
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&resp, "{\"result_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&resp, result_hex) ||
|
|
!amduatd_strbuf_append_cstr(&resp, "\"")) {
|
|
free(result_hex);
|
|
free(trace_hex);
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(result_hex);
|
|
|
|
if (trace_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&resp, ",\"trace_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&resp, trace_hex) ||
|
|
!amduatd_strbuf_append_cstr(&resp, "\"")) {
|
|
free(trace_hex);
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(trace_hex);
|
|
}
|
|
|
|
if (receipt_emitted) {
|
|
if (!amduat_asl_ref_encode_hex(receipt_ref, &receipt_hex)) {
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&resp, ",\"receipt_ref\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&resp, receipt_hex) ||
|
|
!amduatd_strbuf_append_cstr(&resp, "\"")) {
|
|
free(receipt_hex);
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(receipt_hex);
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&resp, ",\"output_refs\":[")) {
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
goto pel_run_cleanup;
|
|
}
|
|
|
|
for (i = 0; i < run_result.output_refs_len; ++i) {
|
|
char *out_hex = NULL;
|
|
if (!amduat_asl_ref_encode_hex(run_result.output_refs[i], &out_hex)) {
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
goto pel_run_cleanup;
|
|
}
|
|
if (i != 0) {
|
|
if (!amduatd_strbuf_append_char(&resp, ',')) {
|
|
free(out_hex);
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
goto pel_run_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&resp, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&resp, out_hex) ||
|
|
!amduatd_strbuf_append_cstr(&resp, "\"")) {
|
|
free(out_hex);
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
goto pel_run_cleanup;
|
|
}
|
|
free(out_hex);
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&resp, "],\"status\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&resp, status) ||
|
|
!amduatd_strbuf_append_cstr(&resp, "\"}\n")) {
|
|
amduatd_strbuf_free(&resp);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
goto pel_run_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, status_code, status_reason, resp.data, false);
|
|
amduatd_strbuf_free(&resp);
|
|
}
|
|
|
|
pel_run_cleanup:
|
|
if (body != NULL) {
|
|
free(body);
|
|
}
|
|
if (run_result.has_result_value) {
|
|
amduat_enc_pel1_result_free(&run_result.result_value);
|
|
}
|
|
if (run_result.output_refs != NULL) {
|
|
amduat_pel_surf_free_refs(run_result.output_refs, run_result.output_refs_len);
|
|
}
|
|
amduat_pel_surf_free_ref(&run_result.result_ref);
|
|
|
|
if (input_refs != NULL) {
|
|
size_t i;
|
|
for (i = 0; i < input_refs_len; ++i) {
|
|
amduat_reference_free(&input_refs[i]);
|
|
}
|
|
free(input_refs);
|
|
}
|
|
if (has_params_ref) {
|
|
amduat_reference_free(¶ms_ref);
|
|
}
|
|
if (have_program_ref) {
|
|
amduat_reference_free(&program_ref);
|
|
}
|
|
if (have_scheme_ref && !scheme_is_dag) {
|
|
amduat_reference_free(&scheme_ref);
|
|
}
|
|
if (receipt_emitted) {
|
|
amduat_reference_free(&receipt_ref);
|
|
}
|
|
if (receipt_have_input_manifest) {
|
|
amduat_reference_free(&receipt_input_manifest_ref);
|
|
}
|
|
if (receipt_have_environment) {
|
|
amduat_reference_free(&receipt_environment_ref);
|
|
}
|
|
if (receipt_have_executor) {
|
|
amduat_reference_free(&receipt_executor_ref);
|
|
}
|
|
if (receipt_has_sbom) {
|
|
amduat_reference_free(&receipt_sbom_ref);
|
|
}
|
|
if (receipt_has_executor_fingerprint) {
|
|
amduat_reference_free(&receipt_executor_fingerprint_ref);
|
|
}
|
|
if (receipt_logs != NULL) {
|
|
size_t i;
|
|
for (i = 0; i < receipt_logs_len; ++i) {
|
|
amduat_reference_free(&receipt_logs[i].log_ref);
|
|
amduat_octets_free(&receipt_logs[i].sha256);
|
|
}
|
|
free(receipt_logs);
|
|
}
|
|
free(receipt_evaluator_id);
|
|
free(receipt_parity_digest);
|
|
free(receipt_run_id);
|
|
free(receipt_rng_seed);
|
|
free(receipt_signature);
|
|
return ok;
|
|
}
|
|
|
|
typedef struct {
|
|
amduat_pel_node_t *nodes;
|
|
size_t nodes_len;
|
|
amduat_pel_root_ref_t *roots;
|
|
size_t roots_len;
|
|
} amduatd_pel_program_tmp_t;
|
|
|
|
static void amduatd_pel_program_tmp_free(amduatd_pel_program_tmp_t *tmp) {
|
|
size_t i;
|
|
|
|
if (tmp == NULL) {
|
|
return;
|
|
}
|
|
if (tmp->nodes != NULL) {
|
|
for (i = 0; i < tmp->nodes_len; ++i) {
|
|
free(tmp->nodes[i].inputs);
|
|
tmp->nodes[i].inputs = NULL;
|
|
tmp->nodes[i].inputs_len = 0;
|
|
free((void *)tmp->nodes[i].params.data);
|
|
tmp->nodes[i].params.data = NULL;
|
|
tmp->nodes[i].params.len = 0;
|
|
}
|
|
}
|
|
free(tmp->nodes);
|
|
tmp->nodes = NULL;
|
|
tmp->nodes_len = 0;
|
|
free(tmp->roots);
|
|
tmp->roots = NULL;
|
|
tmp->roots_len = 0;
|
|
}
|
|
|
|
static bool amduatd_parse_params_hex(const char *s,
|
|
size_t len,
|
|
amduat_octets_t *out) {
|
|
char *tmp = NULL;
|
|
uint8_t *bytes = NULL;
|
|
size_t out_len = 0;
|
|
|
|
if (out != NULL) {
|
|
*out = amduat_octets(NULL, 0);
|
|
}
|
|
if (s == NULL || out == NULL) {
|
|
return false;
|
|
}
|
|
if (!amduatd_copy_json_str(s, len, &tmp)) {
|
|
return false;
|
|
}
|
|
if (tmp[0] == '\0') {
|
|
free(tmp);
|
|
*out = amduat_octets(NULL, 0);
|
|
return true;
|
|
}
|
|
if (!amduat_hex_decode_alloc(tmp, &bytes, &out_len)) {
|
|
free(tmp);
|
|
return false;
|
|
}
|
|
free(tmp);
|
|
*out = amduat_octets(bytes, out_len);
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_fed_pull_unix_get_records(void *ctx,
|
|
uint32_t domain_id,
|
|
uint64_t from_logseq,
|
|
uint64_t limit,
|
|
int *out_status,
|
|
amduat_fed_record_t **out_records,
|
|
size_t *out_len,
|
|
char **out_body) {
|
|
return amduat_fed_transport_unix_get_records_with_limit(
|
|
(amduat_fed_transport_unix_t *)ctx,
|
|
domain_id,
|
|
from_logseq,
|
|
limit,
|
|
out_status,
|
|
out_records,
|
|
out_len,
|
|
out_body);
|
|
}
|
|
|
|
static bool amduatd_fed_pull_unix_get_artifact(void *ctx,
|
|
amduat_reference_t ref,
|
|
int *out_status,
|
|
amduat_octets_t *out_bytes,
|
|
char **out_body) {
|
|
return amduat_fed_transport_unix_get_artifact_with_status(
|
|
(amduat_fed_transport_unix_t *)ctx,
|
|
ref,
|
|
out_status,
|
|
out_bytes,
|
|
out_body);
|
|
}
|
|
|
|
static bool amduatd_fed_push_unix_post_ingest(
|
|
void *ctx,
|
|
amduat_fed_record_type_t record_type,
|
|
amduat_reference_t ref,
|
|
amduat_octets_t bytes,
|
|
int *out_status,
|
|
char **out_body) {
|
|
return amduat_fed_transport_unix_post_ingest(
|
|
(amduat_fed_transport_unix_t *)ctx,
|
|
record_type,
|
|
ref,
|
|
bytes,
|
|
out_status,
|
|
out_body);
|
|
}
|
|
|
|
static bool amduatd_handle_post_fed_pull(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char peer_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
char remote_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
const char *remote_space_id = NULL;
|
|
const char *remote_error = NULL;
|
|
char limit_buf[32];
|
|
uint64_t limit = 128u;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_fed_transport_unix_t transport;
|
|
amduat_fed_transport_t ops;
|
|
amduatd_fed_pull_transport_t pull_transport;
|
|
amduatd_fed_pull_apply_report_t report;
|
|
amduatd_fed_pull_apply_status_t status;
|
|
amduatd_strbuf_t b;
|
|
char *cursor_ref_hex = NULL;
|
|
char *cursor_after_hex = NULL;
|
|
char *candidate_ref_hex = NULL;
|
|
bool ok = false;
|
|
int http_status = 200;
|
|
const char *http_reason = "OK";
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || root_path == NULL ||
|
|
dcfg == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
if (amduatd_fed_pull_plan_check(fed_cfg, store) ==
|
|
AMDUATD_FED_PULL_PLAN_ERR_DISABLED) {
|
|
return amduatd_send_json_error(fd, 503, "Service Unavailable",
|
|
"federation disabled");
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "peer",
|
|
peer_buf, sizeof(peer_buf)) == NULL ||
|
|
peer_buf[0] == '\0') {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing peer");
|
|
}
|
|
if (!amduatd_get_remote_space_id(req,
|
|
remote_buf,
|
|
sizeof(remote_buf),
|
|
&remote_space_id,
|
|
&remote_error)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
remote_error);
|
|
}
|
|
{
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
if (remote_space_id != NULL) {
|
|
if (!amduatd_fed_cursor_pointer_name_v2(req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
} else if (!amduatd_fed_cursor_pointer_name(req->effective_space,
|
|
peer_buf,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "limit",
|
|
limit_buf, sizeof(limit_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(limit_buf, &limit) || limit == 0u ||
|
|
limit > 10000u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid limit");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
if (fed_cfg->transport_kind != AMDUATD_FED_TRANSPORT_UNIX ||
|
|
!fed_cfg->unix_socket_set) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"federation transport unavailable");
|
|
}
|
|
if (!amduat_fed_transport_unix_init(&transport, fed_cfg->unix_socket_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"transport init failed");
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
(void)amduat_fed_transport_unix_set_space(
|
|
&transport, (const char *)req->effective_space->space_id.data);
|
|
}
|
|
|
|
ops = amduat_fed_transport_unix_ops(&transport);
|
|
memset(&pull_transport, 0, sizeof(pull_transport));
|
|
pull_transport.ctx = &transport;
|
|
pull_transport.get_records = amduatd_fed_pull_unix_get_records;
|
|
pull_transport.free_records = ops.free_records;
|
|
pull_transport.get_artifact = amduatd_fed_pull_unix_get_artifact;
|
|
|
|
amduatd_fed_pull_apply_report_init(&report);
|
|
status = amduatd_fed_pull_apply(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
limit,
|
|
fed_cfg,
|
|
&pull_transport,
|
|
&report);
|
|
|
|
if (status == AMDUATD_FED_PULL_APPLY_ERR_DISABLED) {
|
|
http_status = 503;
|
|
http_reason = "Service Unavailable";
|
|
} else if (status == AMDUATD_FED_PULL_APPLY_ERR_UNSUPPORTED) {
|
|
http_status = 501;
|
|
http_reason = "Not Implemented";
|
|
} else if (status == AMDUATD_FED_PULL_APPLY_ERR_INVALID) {
|
|
http_status = 400;
|
|
http_reason = "Bad Request";
|
|
} else if (status == AMDUATD_FED_PULL_APPLY_ERR_REMOTE) {
|
|
http_status = 502;
|
|
http_reason = "Bad Gateway";
|
|
} else if (status == AMDUATD_FED_PULL_APPLY_ERR_CONFLICT) {
|
|
http_status = 409;
|
|
http_reason = "Conflict";
|
|
} else if (status != AMDUATD_FED_PULL_APPLY_OK) {
|
|
http_status = 500;
|
|
http_reason = "Internal Server Error";
|
|
}
|
|
|
|
if (report.cursor_ref_set) {
|
|
(void)amduat_asl_ref_encode_hex(report.cursor_ref, &cursor_ref_hex);
|
|
}
|
|
if (report.cursor_after_ref_set) {
|
|
(void)amduat_asl_ref_encode_hex(report.cursor_after_ref,
|
|
&cursor_after_hex);
|
|
}
|
|
if (report.plan_candidate.has_ref) {
|
|
(void)amduat_asl_ref_encode_hex(report.plan_candidate.ref,
|
|
&candidate_ref_hex);
|
|
}
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
if (!amduatd_strbuf_append_cstr(&b, "{")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"peer\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, peer_buf) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"effective_space\":{")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)limit);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"limit\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"cursor_before\":{")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"present\":") ||
|
|
!amduatd_strbuf_append_cstr(&b,
|
|
report.cursor_present ? "true" : "false")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"last_logseq\":")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (report.cursor_present && report.cursor_has_logseq) {
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.cursor_logseq);
|
|
if (!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"ref\":")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (cursor_ref_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, cursor_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"plan_summary\":{")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.plan_record_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"record_count\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",\"next_cursor_candidate\":{")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"last_logseq\":")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (report.plan_candidate.has_logseq) {
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.plan_candidate.logseq);
|
|
if (!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"last_record_hash\":")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (candidate_ref_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, candidate_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}},")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"applied\":{")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.applied_record_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"record_count\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.applied_artifact_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"artifact_count\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"cursor_after\":{")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"advanced\":") ||
|
|
!amduatd_strbuf_append_cstr(&b,
|
|
report.cursor_advanced ? "true" : "false")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"last_logseq\":")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (report.cursor_advanced && report.cursor_after_has_logseq) {
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.cursor_after_logseq);
|
|
if (!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"ref\":")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (report.cursor_advanced && cursor_after_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, cursor_after_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"errors\":[")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (status == AMDUATD_FED_PULL_APPLY_OK) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "]}\n")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
} else {
|
|
char tmp[64];
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"message\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b,
|
|
report.error[0] != '\0'
|
|
? report.error
|
|
: "error") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
if (report.remote_status != 0) {
|
|
snprintf(tmp, sizeof(tmp), "%d", report.remote_status);
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"remote_status\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}]}\n")) {
|
|
goto fed_pull_cleanup;
|
|
}
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, http_status, http_reason, b.data, false);
|
|
|
|
fed_pull_cleanup:
|
|
amduatd_strbuf_free(&b);
|
|
free(cursor_ref_hex);
|
|
free(cursor_after_hex);
|
|
free(candidate_ref_hex);
|
|
amduatd_fed_pull_apply_report_free(&report);
|
|
if (!ok) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void amduatd_fed_pull_apply_status_http(
|
|
amduatd_fed_pull_apply_status_t status,
|
|
int *out_status,
|
|
const char **out_reason) {
|
|
int http_status = 200;
|
|
const char *http_reason = "OK";
|
|
|
|
if (status == AMDUATD_FED_PULL_APPLY_ERR_DISABLED) {
|
|
http_status = 503;
|
|
http_reason = "Service Unavailable";
|
|
} else if (status == AMDUATD_FED_PULL_APPLY_ERR_UNSUPPORTED) {
|
|
http_status = 501;
|
|
http_reason = "Not Implemented";
|
|
} else if (status == AMDUATD_FED_PULL_APPLY_ERR_INVALID) {
|
|
http_status = 400;
|
|
http_reason = "Bad Request";
|
|
} else if (status == AMDUATD_FED_PULL_APPLY_ERR_REMOTE) {
|
|
http_status = 502;
|
|
http_reason = "Bad Gateway";
|
|
} else if (status == AMDUATD_FED_PULL_APPLY_ERR_CONFLICT) {
|
|
http_status = 409;
|
|
http_reason = "Conflict";
|
|
} else if (status != AMDUATD_FED_PULL_APPLY_OK) {
|
|
http_status = 500;
|
|
http_reason = "Internal Server Error";
|
|
}
|
|
|
|
if (out_status != NULL) {
|
|
*out_status = http_status;
|
|
}
|
|
if (out_reason != NULL) {
|
|
*out_reason = http_reason;
|
|
}
|
|
}
|
|
|
|
static void amduatd_fed_push_apply_status_http(
|
|
amduatd_fed_push_apply_status_t status,
|
|
int *out_status,
|
|
const char **out_reason) {
|
|
int http_status = 200;
|
|
const char *http_reason = "OK";
|
|
|
|
if (status == AMDUATD_FED_PUSH_APPLY_ERR_DISABLED) {
|
|
http_status = 503;
|
|
http_reason = "Service Unavailable";
|
|
} else if (status == AMDUATD_FED_PUSH_APPLY_ERR_UNSUPPORTED) {
|
|
http_status = 501;
|
|
http_reason = "Not Implemented";
|
|
} else if (status == AMDUATD_FED_PUSH_APPLY_ERR_INVALID) {
|
|
http_status = 400;
|
|
http_reason = "Bad Request";
|
|
} else if (status == AMDUATD_FED_PUSH_APPLY_ERR_REMOTE) {
|
|
http_status = 502;
|
|
http_reason = "Bad Gateway";
|
|
} else if (status == AMDUATD_FED_PUSH_APPLY_ERR_CONFLICT) {
|
|
http_status = 409;
|
|
http_reason = "Conflict";
|
|
} else if (status != AMDUATD_FED_PUSH_APPLY_OK) {
|
|
http_status = 500;
|
|
http_reason = "Internal Server Error";
|
|
}
|
|
|
|
if (out_status != NULL) {
|
|
*out_status = http_status;
|
|
}
|
|
if (out_reason != NULL) {
|
|
*out_reason = http_reason;
|
|
}
|
|
}
|
|
|
|
static bool amduatd_handle_post_fed_pull_until(
|
|
int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char peer_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
char remote_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
const char *remote_space_id = NULL;
|
|
const char *remote_error = NULL;
|
|
char limit_buf[32];
|
|
char rounds_buf[32];
|
|
uint64_t limit = 128u;
|
|
uint64_t max_rounds = 10u;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_fed_transport_unix_t transport;
|
|
amduat_fed_transport_t ops;
|
|
amduatd_fed_pull_transport_t pull_transport;
|
|
amduatd_fed_until_report_t report;
|
|
amduatd_fed_pull_apply_status_t status;
|
|
amduatd_strbuf_t b;
|
|
char *cursor_ref_hex = NULL;
|
|
bool ok = false;
|
|
int http_status = 200;
|
|
const char *http_reason = "OK";
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || root_path == NULL ||
|
|
dcfg == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
{
|
|
amduatd_fed_pull_plan_status_t check =
|
|
amduatd_fed_pull_plan_check(fed_cfg, store);
|
|
if (check == AMDUATD_FED_PULL_PLAN_ERR_DISABLED) {
|
|
return amduatd_send_json_error(fd, 503, "Service Unavailable",
|
|
"federation disabled");
|
|
}
|
|
if (check == AMDUATD_FED_PULL_PLAN_ERR_UNSUPPORTED) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"requires index backend");
|
|
}
|
|
if (check != AMDUATD_FED_PULL_PLAN_OK) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"plan unavailable");
|
|
}
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "peer",
|
|
peer_buf, sizeof(peer_buf)) == NULL ||
|
|
peer_buf[0] == '\0') {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing peer");
|
|
}
|
|
if (!amduatd_get_remote_space_id(req,
|
|
remote_buf,
|
|
sizeof(remote_buf),
|
|
&remote_space_id,
|
|
&remote_error)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
remote_error);
|
|
}
|
|
{
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
if (remote_space_id != NULL) {
|
|
if (!amduatd_fed_cursor_pointer_name_v2(req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
} else if (!amduatd_fed_cursor_pointer_name(req->effective_space,
|
|
peer_buf,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "limit",
|
|
limit_buf, sizeof(limit_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(limit_buf, &limit) || limit == 0u ||
|
|
limit > 10000u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid limit");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "max_rounds",
|
|
rounds_buf, sizeof(rounds_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(rounds_buf, &max_rounds) || max_rounds == 0u ||
|
|
max_rounds > 100u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid max_rounds");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
if (fed_cfg->transport_kind != AMDUATD_FED_TRANSPORT_UNIX ||
|
|
!fed_cfg->unix_socket_set) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"federation transport unavailable");
|
|
}
|
|
if (!amduat_fed_transport_unix_init(&transport, fed_cfg->unix_socket_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"transport init failed");
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
(void)amduat_fed_transport_unix_set_space(
|
|
&transport, (const char *)req->effective_space->space_id.data);
|
|
}
|
|
|
|
ops = amduat_fed_transport_unix_ops(&transport);
|
|
memset(&pull_transport, 0, sizeof(pull_transport));
|
|
pull_transport.ctx = &transport;
|
|
pull_transport.get_records = amduatd_fed_pull_unix_get_records;
|
|
pull_transport.free_records = ops.free_records;
|
|
pull_transport.get_artifact = amduatd_fed_pull_unix_get_artifact;
|
|
|
|
status = amduatd_fed_pull_until(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
limit,
|
|
max_rounds,
|
|
fed_cfg,
|
|
&pull_transport,
|
|
&report);
|
|
amduatd_fed_pull_apply_status_http(status, &http_status, &http_reason);
|
|
if (status != AMDUATD_FED_PULL_APPLY_OK) {
|
|
const char *msg = report.error[0] != '\0' ? report.error : "error";
|
|
amduatd_fed_until_report_free(&report);
|
|
return amduatd_send_json_error(fd, http_status, http_reason, msg);
|
|
}
|
|
|
|
if (report.cursor_ref_set) {
|
|
(void)amduat_asl_ref_encode_hex(report.cursor_ref, &cursor_ref_hex);
|
|
}
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
if (!amduatd_strbuf_append_cstr(&b, "{")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"peer\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, peer_buf) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"effective_space\":{")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"operation\":\"pull\",")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)limit);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"limit\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)max_rounds);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"max_rounds\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.rounds_executed);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"rounds_executed\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"caught_up\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, report.caught_up ? "true" : "false") ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"applied\":{")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.total_records);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"records\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.total_artifacts);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"artifacts\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"cursor\":{")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"last_logseq\":")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
if (report.cursor_has_logseq) {
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.cursor_logseq);
|
|
if (!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"ref\":")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
if (cursor_ref_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, cursor_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}}\n")) {
|
|
goto fed_pull_until_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
fed_pull_until_cleanup:
|
|
amduatd_strbuf_free(&b);
|
|
free(cursor_ref_hex);
|
|
amduatd_fed_until_report_free(&report);
|
|
if (!ok) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_handle_post_fed_push(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char peer_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
char remote_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
const char *remote_space_id = NULL;
|
|
const char *remote_error = NULL;
|
|
char limit_buf[32];
|
|
uint64_t limit = 128u;
|
|
uint32_t domain_id = 0u;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_fed_transport_unix_t transport;
|
|
amduatd_fed_push_transport_t push_transport;
|
|
amduatd_fed_push_apply_report_t report;
|
|
amduatd_fed_push_apply_status_t status;
|
|
amduatd_strbuf_t b;
|
|
char *cursor_ref_hex = NULL;
|
|
char *cursor_after_hex = NULL;
|
|
char *candidate_ref_hex = NULL;
|
|
bool ok = false;
|
|
int http_status = 200;
|
|
const char *http_reason = "OK";
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || root_path == NULL ||
|
|
dcfg == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
{
|
|
amduatd_fed_push_plan_status_t check =
|
|
amduatd_fed_push_plan_check(fed_cfg, store);
|
|
if (check == AMDUATD_FED_PUSH_PLAN_ERR_DISABLED) {
|
|
return amduatd_send_json_error(fd, 503, "Service Unavailable",
|
|
"federation disabled");
|
|
}
|
|
if (check == AMDUATD_FED_PUSH_PLAN_ERR_UNSUPPORTED) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"requires index backend");
|
|
}
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "peer",
|
|
peer_buf, sizeof(peer_buf)) == NULL ||
|
|
peer_buf[0] == '\0') {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing peer");
|
|
}
|
|
if (!amduatd_get_remote_space_id(req,
|
|
remote_buf,
|
|
sizeof(remote_buf),
|
|
&remote_space_id,
|
|
&remote_error)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
remote_error);
|
|
}
|
|
{
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
if (remote_space_id != NULL) {
|
|
if (!amduatd_fed_push_cursor_pointer_name_v2(req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
} else if (!amduatd_fed_push_cursor_pointer_name(req->effective_space,
|
|
peer_buf,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
}
|
|
if (!amduatd_parse_u32_str(peer_buf, &domain_id)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "limit",
|
|
limit_buf, sizeof(limit_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(limit_buf, &limit) || limit == 0u ||
|
|
limit > 2048u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid limit");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
if (fed_cfg->transport_kind != AMDUATD_FED_TRANSPORT_UNIX ||
|
|
!fed_cfg->unix_socket_set) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"federation transport unavailable");
|
|
}
|
|
if (!amduat_fed_transport_unix_init(&transport, fed_cfg->unix_socket_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"transport init failed");
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
(void)amduat_fed_transport_unix_set_space(
|
|
&transport, (const char *)req->effective_space->space_id.data);
|
|
}
|
|
|
|
memset(&push_transport, 0, sizeof(push_transport));
|
|
push_transport.ctx = &transport;
|
|
push_transport.post_ingest = amduatd_fed_push_unix_post_ingest;
|
|
|
|
amduatd_fed_push_apply_report_init(&report);
|
|
status = amduatd_fed_push_apply(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
limit,
|
|
root_path,
|
|
fed_cfg,
|
|
&push_transport,
|
|
&report);
|
|
|
|
if (status == AMDUATD_FED_PUSH_APPLY_ERR_DISABLED) {
|
|
http_status = 503;
|
|
http_reason = "Service Unavailable";
|
|
} else if (status == AMDUATD_FED_PUSH_APPLY_ERR_UNSUPPORTED) {
|
|
http_status = 501;
|
|
http_reason = "Not Implemented";
|
|
} else if (status == AMDUATD_FED_PUSH_APPLY_ERR_INVALID) {
|
|
http_status = 400;
|
|
http_reason = "Bad Request";
|
|
} else if (status == AMDUATD_FED_PUSH_APPLY_ERR_REMOTE) {
|
|
http_status = 502;
|
|
http_reason = "Bad Gateway";
|
|
} else if (status == AMDUATD_FED_PUSH_APPLY_ERR_CONFLICT) {
|
|
http_status = 409;
|
|
http_reason = "Conflict";
|
|
} else if (status != AMDUATD_FED_PUSH_APPLY_OK) {
|
|
http_status = 500;
|
|
http_reason = "Internal Server Error";
|
|
}
|
|
|
|
if (report.cursor_ref_set) {
|
|
(void)amduat_asl_ref_encode_hex(report.cursor_ref, &cursor_ref_hex);
|
|
}
|
|
if (report.cursor_after_ref_set) {
|
|
(void)amduat_asl_ref_encode_hex(report.cursor_after_ref,
|
|
&cursor_after_hex);
|
|
}
|
|
if (report.plan_candidate.has_ref) {
|
|
(void)amduat_asl_ref_encode_hex(report.plan_candidate.ref,
|
|
&candidate_ref_hex);
|
|
}
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
if (!amduatd_strbuf_append_cstr(&b, "{")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"peer\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, peer_buf) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%u", (unsigned int)domain_id);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"domain_id\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"effective_space\":{")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)limit);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"limit\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"cursor_before\":{")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"present\":") ||
|
|
!amduatd_strbuf_append_cstr(&b,
|
|
report.cursor_present ? "true" : "false")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"last_logseq\":")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (report.cursor_present && report.cursor_has_logseq) {
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.cursor_logseq);
|
|
if (!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"ref\":")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (cursor_ref_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, cursor_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"plan_summary\":{")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.plan_record_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"record_count\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",\"next_cursor_candidate\":{")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"last_logseq\":")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (report.plan_candidate.has_logseq) {
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.plan_candidate.logseq);
|
|
if (!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"ref\":")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (candidate_ref_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, candidate_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}},")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"sent\":{")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.sent_record_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"record_count\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.sent_bytes_total);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"bytes_total\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",\"by_type\":{")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.sent_artifact_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"artifact\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.sent_per_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"per\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.sent_tgk_edge_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"tgk_edge\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.sent_tombstone_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"tombstone\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.peer_ok_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"peer_ok_count\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.peer_already_present_count);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"peer_already_present_count\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"cursor_after\":{")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"advanced\":") ||
|
|
!amduatd_strbuf_append_cstr(&b,
|
|
report.cursor_advanced ? "true" : "false")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"last_logseq\":")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (report.cursor_advanced && report.cursor_after_has_logseq) {
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.cursor_after_logseq);
|
|
if (!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"ref\":")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (report.cursor_advanced && cursor_after_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, cursor_after_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"errors\":[")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (status == AMDUATD_FED_PUSH_APPLY_OK) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "]}\n")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
} else {
|
|
char tmp[64];
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"message\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b,
|
|
report.error[0] != '\0'
|
|
? report.error
|
|
: "error") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
if (report.remote_status != 0) {
|
|
snprintf(tmp, sizeof(tmp), "%d", report.remote_status);
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"remote_status\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}]}\n")) {
|
|
goto fed_push_cleanup;
|
|
}
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, http_status, http_reason, b.data, false);
|
|
|
|
fed_push_cleanup:
|
|
amduatd_strbuf_free(&b);
|
|
free(cursor_ref_hex);
|
|
free(cursor_after_hex);
|
|
free(candidate_ref_hex);
|
|
amduatd_fed_push_apply_report_free(&report);
|
|
if (!ok) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool amduatd_handle_post_fed_push_until(
|
|
int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduatd_caps_t *caps,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req,
|
|
const char *root_path) {
|
|
char peer_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
char remote_buf[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
|
const char *remote_space_id = NULL;
|
|
const char *remote_error = NULL;
|
|
char limit_buf[32];
|
|
char rounds_buf[32];
|
|
uint64_t limit = 128u;
|
|
uint64_t max_rounds = 10u;
|
|
amduat_asl_pointer_store_t pointer_store;
|
|
amduat_fed_transport_unix_t transport;
|
|
amduatd_fed_push_transport_t push_transport;
|
|
amduatd_fed_until_report_t report;
|
|
amduatd_fed_push_apply_status_t status;
|
|
amduatd_strbuf_t b;
|
|
char *cursor_ref_hex = NULL;
|
|
bool ok = false;
|
|
int http_status = 200;
|
|
const char *http_reason = "OK";
|
|
|
|
if (store == NULL || fed_cfg == NULL || req == NULL || root_path == NULL ||
|
|
dcfg == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (!amduatd_fed_require_space(fd, fed_cfg, req)) {
|
|
return false;
|
|
}
|
|
{
|
|
amduatd_fed_push_plan_status_t check =
|
|
amduatd_fed_push_plan_check(fed_cfg, store);
|
|
if (check == AMDUATD_FED_PUSH_PLAN_ERR_DISABLED) {
|
|
return amduatd_send_json_error(fd, 503, "Service Unavailable",
|
|
"federation disabled");
|
|
}
|
|
if (check == AMDUATD_FED_PUSH_PLAN_ERR_UNSUPPORTED) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"requires index backend");
|
|
}
|
|
if (check != AMDUATD_FED_PUSH_PLAN_OK) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"plan unavailable");
|
|
}
|
|
}
|
|
|
|
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
|
const char *reason = NULL;
|
|
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
|
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"space not permitted by capability");
|
|
}
|
|
return amduatd_send_json_error(fd, 403, "Forbidden",
|
|
"invalid capability");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "peer",
|
|
peer_buf, sizeof(peer_buf)) == NULL ||
|
|
peer_buf[0] == '\0') {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing peer");
|
|
}
|
|
if (!amduatd_get_remote_space_id(req,
|
|
remote_buf,
|
|
sizeof(remote_buf),
|
|
&remote_space_id,
|
|
&remote_error)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
remote_error);
|
|
}
|
|
{
|
|
amduat_octets_t scoped = amduat_octets(NULL, 0u);
|
|
if (remote_space_id != NULL) {
|
|
if (!amduatd_fed_push_cursor_pointer_name_v2(req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
} else if (!amduatd_fed_push_cursor_pointer_name(req->effective_space,
|
|
peer_buf,
|
|
&scoped)) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid peer");
|
|
}
|
|
amduat_octets_free(&scoped);
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "limit",
|
|
limit_buf, sizeof(limit_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(limit_buf, &limit) || limit == 0u ||
|
|
limit > 2048u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "invalid limit");
|
|
}
|
|
}
|
|
|
|
if (amduatd_query_param(req->path, "max_rounds",
|
|
rounds_buf, sizeof(rounds_buf)) != NULL) {
|
|
if (!amduatd_parse_u64_str(rounds_buf, &max_rounds) || max_rounds == 0u ||
|
|
max_rounds > 100u) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid max_rounds");
|
|
}
|
|
}
|
|
|
|
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"pointer store error");
|
|
}
|
|
|
|
if (fed_cfg->transport_kind != AMDUATD_FED_TRANSPORT_UNIX ||
|
|
!fed_cfg->unix_socket_set) {
|
|
return amduatd_send_json_error(fd, 501, "Not Implemented",
|
|
"federation transport unavailable");
|
|
}
|
|
if (!amduat_fed_transport_unix_init(&transport, fed_cfg->unix_socket_path)) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"transport init failed");
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
(void)amduat_fed_transport_unix_set_space(
|
|
&transport, (const char *)req->effective_space->space_id.data);
|
|
}
|
|
|
|
memset(&push_transport, 0, sizeof(push_transport));
|
|
push_transport.ctx = &transport;
|
|
push_transport.post_ingest = amduatd_fed_push_unix_post_ingest;
|
|
|
|
status = amduatd_fed_push_until(store,
|
|
&pointer_store,
|
|
req->effective_space,
|
|
peer_buf,
|
|
remote_space_id,
|
|
limit,
|
|
max_rounds,
|
|
root_path,
|
|
fed_cfg,
|
|
&push_transport,
|
|
&report);
|
|
amduatd_fed_push_apply_status_http(status, &http_status, &http_reason);
|
|
if (status != AMDUATD_FED_PUSH_APPLY_OK) {
|
|
const char *msg = report.error[0] != '\0' ? report.error : "error";
|
|
amduatd_fed_until_report_free(&report);
|
|
return amduatd_send_json_error(fd, http_status, http_reason, msg);
|
|
}
|
|
|
|
if (report.cursor_ref_set) {
|
|
(void)amduat_asl_ref_encode_hex(report.cursor_ref, &cursor_ref_hex);
|
|
}
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
if (!amduatd_strbuf_append_cstr(&b, "{")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"peer\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, peer_buf) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\",")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"effective_space\":{")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
if (req->effective_space != NULL && req->effective_space->enabled &&
|
|
req->effective_space->space_id.data != NULL) {
|
|
const char *space_id = (const char *)req->effective_space->space_id.data;
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, space_id) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"mode\":\"unscoped\",") ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"space_id\":null")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"operation\":\"push\",")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)limit);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"limit\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)max_rounds);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"max_rounds\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.rounds_executed);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"rounds_executed\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"caught_up\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, report.caught_up ? "true" : "false") ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"applied\":{")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
{
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.total_records);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"records\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, ",")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
snprintf(tmp, sizeof(tmp), "%zu", report.total_artifacts);
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"artifacts\":") ||
|
|
!amduatd_strbuf_append_cstr(&b, tmp) ||
|
|
!amduatd_strbuf_append_cstr(&b, "},")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"cursor\":{")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"last_logseq\":")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
if (report.cursor_has_logseq) {
|
|
char tmp[64];
|
|
snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)report.cursor_logseq);
|
|
if (!amduatd_strbuf_append_cstr(&b, tmp)) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, ",\"ref\":")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
if (cursor_ref_hex != NULL) {
|
|
if (!amduatd_strbuf_append_cstr(&b, "\"") ||
|
|
!amduatd_strbuf_append_cstr(&b, cursor_ref_hex) ||
|
|
!amduatd_strbuf_append_cstr(&b, "\"")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
} else {
|
|
if (!amduatd_strbuf_append_cstr(&b, "null")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
}
|
|
if (!amduatd_strbuf_append_cstr(&b, "}}\n")) {
|
|
goto fed_push_until_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_json(fd, 200, "OK", b.data, false);
|
|
|
|
fed_push_until_cleanup:
|
|
amduatd_strbuf_free(&b);
|
|
free(cursor_ref_hex);
|
|
amduatd_fed_until_report_free(&report);
|
|
if (!ok) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
}
|
|
return true;
|
|
}
|
|
static bool amduatd_handle_post_pel_programs(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduatd_http_req_t *req) {
|
|
uint8_t *body = NULL;
|
|
const char *p = NULL;
|
|
const char *end = NULL;
|
|
amduatd_pel_program_tmp_t tmp;
|
|
amduat_pel_program_t program;
|
|
amduat_octets_t program_bytes;
|
|
amduat_artifact_t artifact;
|
|
amduat_reference_t program_ref;
|
|
char *ref_hex = NULL;
|
|
char json[2048];
|
|
bool ok = false;
|
|
|
|
memset(&tmp, 0, sizeof(tmp));
|
|
memset(&program, 0, sizeof(program));
|
|
program_bytes = amduat_octets(NULL, 0);
|
|
memset(&artifact, 0, sizeof(artifact));
|
|
memset(&program_ref, 0, sizeof(program_ref));
|
|
|
|
if (store == NULL || req == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (req->content_length == 0) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "missing body");
|
|
}
|
|
if (req->content_length > (2u * 1024u * 1024u)) {
|
|
return amduatd_send_json_error(fd, 413, "Payload Too Large",
|
|
"payload too large");
|
|
}
|
|
|
|
body = (uint8_t *)malloc(req->content_length);
|
|
if (body == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
}
|
|
if (!amduatd_read_exact(fd, body, req->content_length)) {
|
|
free(body);
|
|
return false;
|
|
}
|
|
|
|
p = (const char *)body;
|
|
end = (const char *)body + req->content_length;
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
|
|
{
|
|
bool have_nodes = false;
|
|
bool have_roots = false;
|
|
|
|
for (;;) {
|
|
const char *key = NULL;
|
|
size_t key_len = 0;
|
|
const char *cur = NULL;
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
|
|
if (key_len == strlen("nodes") && memcmp(key, "nodes", key_len) == 0) {
|
|
size_t cap = 0;
|
|
if (have_nodes) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"duplicate nodes");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (!amduatd_json_expect(&p, end, '[')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid nodes");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
have_nodes = true;
|
|
} else {
|
|
for (;;) {
|
|
amduat_pel_node_t node;
|
|
bool have_id = false;
|
|
bool have_op = false;
|
|
bool have_inputs = false;
|
|
bool have_params_hex = false;
|
|
amduat_octets_t op_name = amduat_octets(NULL, 0);
|
|
uint32_t op_ver = 0;
|
|
|
|
memset(&node, 0, sizeof(node));
|
|
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid node");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
for (;;) {
|
|
const char *nk = NULL;
|
|
size_t nk_len = 0;
|
|
const char *sv = NULL;
|
|
size_t sv_len = 0;
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &nk, &nk_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid node");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
|
|
if (nk_len == strlen("id") && memcmp(nk, "id", nk_len) == 0) {
|
|
uint32_t id = 0;
|
|
if (have_id) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"duplicate node id");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (!amduatd_json_parse_u32_loose(&p, end, &id)) {
|
|
char near[32];
|
|
char msg[96];
|
|
int n;
|
|
amduatd_json_peek_token(p, end, near, sizeof(near));
|
|
n = snprintf(msg,
|
|
sizeof(msg),
|
|
"invalid node id near '%s'",
|
|
near[0] != '\0' ? near : "?");
|
|
if (n > 0 && (size_t)n < sizeof(msg)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", msg);
|
|
} else {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid node id");
|
|
}
|
|
goto pel_programs_cleanup;
|
|
}
|
|
node.id = (amduat_pel_node_id_t)id;
|
|
have_id = true;
|
|
} else if (nk_len == strlen("op") &&
|
|
memcmp(nk, "op", nk_len) == 0) {
|
|
bool have_name = false;
|
|
bool have_version = false;
|
|
if (have_op || !amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid op");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
for (;;) {
|
|
const char *okey = NULL;
|
|
size_t okey_len = 0;
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &okey,
|
|
&okey_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid op");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (okey_len == strlen("name") &&
|
|
memcmp(okey, "name", okey_len) == 0) {
|
|
if (have_name ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid op name");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
op_name = amduat_octets(sv, sv_len);
|
|
have_name = true;
|
|
} else if (okey_len == strlen("version") &&
|
|
memcmp(okey, "version", okey_len) == 0) {
|
|
if (have_version ||
|
|
!amduatd_json_parse_u32_loose(&p, end, &op_ver)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid op version");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
have_version = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid op");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid op");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (!have_name || !have_version) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing op fields");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
node.op.name = op_name;
|
|
node.op.version = op_ver;
|
|
have_op = true;
|
|
} else if (nk_len == strlen("inputs") &&
|
|
memcmp(nk, "inputs", nk_len) == 0) {
|
|
size_t icap = 0;
|
|
if (have_inputs || !amduatd_json_expect(&p, end, '[')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid inputs");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
have_inputs = true;
|
|
} else {
|
|
for (;;) {
|
|
amduat_pel_dag_input_t in;
|
|
memset(&in, 0, sizeof(in));
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
{
|
|
const char *ik = NULL;
|
|
size_t ik_len = 0;
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &ik,
|
|
&ik_len) ||
|
|
!amduatd_json_expect(&p, end, ':') ||
|
|
!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (ik_len == strlen("external") &&
|
|
memcmp(ik, "external", ik_len) == 0) {
|
|
uint32_t idx = 0;
|
|
const char *k2 = NULL;
|
|
size_t k2_len = 0;
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &k2,
|
|
&k2_len) ||
|
|
k2_len != strlen("input_index") ||
|
|
memcmp(k2, "input_index", k2_len) != 0 ||
|
|
!amduatd_json_expect(&p, end, ':') ||
|
|
!amduatd_json_parse_u32_loose(&p, end, &idx) ||
|
|
!amduatd_json_expect(&p, end, '}')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid external input");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
in.kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL;
|
|
in.value.external.input_index = idx;
|
|
} else if (ik_len == strlen("node") &&
|
|
memcmp(ik, "node", ik_len) == 0) {
|
|
bool have_node_id = false;
|
|
bool have_output_index = false;
|
|
uint32_t nid = 0;
|
|
uint32_t oidx = 0;
|
|
for (;;) {
|
|
const char *k2 = NULL;
|
|
size_t k2_len = 0;
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &k2,
|
|
&k2_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid node input");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (k2_len == strlen("node_id") &&
|
|
memcmp(k2, "node_id", k2_len) == 0) {
|
|
if (have_node_id ||
|
|
!amduatd_json_parse_u32_loose(&p, end, &nid)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid node_id");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
have_node_id = true;
|
|
} else if (k2_len == strlen("output_index") &&
|
|
memcmp(k2, "output_index", k2_len) == 0) {
|
|
if (have_output_index ||
|
|
!amduatd_json_parse_u32_loose(&p, end, &oidx)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid output_index");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
have_output_index = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid node input");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid node input");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (!have_node_id || !have_output_index) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing node input fields");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
in.kind = AMDUAT_PEL_DAG_INPUT_NODE;
|
|
in.value.node.node_id = (amduat_pel_node_id_t)nid;
|
|
in.value.node.output_index = oidx;
|
|
} else {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input kind");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (!amduatd_json_expect(&p, end, '}')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid input");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
|
|
if (node.inputs_len == icap) {
|
|
size_t next_cap = icap != 0 ? icap * 2u : 4u;
|
|
amduat_pel_dag_input_t *next =
|
|
(amduat_pel_dag_input_t *)realloc(
|
|
node.inputs, next_cap * sizeof(*node.inputs));
|
|
if (next == NULL) {
|
|
ok = amduatd_send_json_error(fd, 500,
|
|
"Internal Server Error",
|
|
"oom");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
node.inputs = next;
|
|
icap = next_cap;
|
|
}
|
|
node.inputs[node.inputs_len++] = in;
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
have_inputs = true;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid inputs");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
} else if (nk_len == strlen("params_hex") &&
|
|
memcmp(nk, "params_hex", nk_len) == 0) {
|
|
if (have_params_hex ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_parse_params_hex(sv, sv_len, &node.params)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid params_hex");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
have_params_hex = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid node");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!have_id || !have_op || !have_inputs || !have_params_hex) {
|
|
free(node.inputs);
|
|
free((void *)node.params.data);
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing node fields");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (amduat_pel_kernel_op_lookup(node.op.name, node.op.version) ==
|
|
NULL) {
|
|
free(node.inputs);
|
|
free((void *)node.params.data);
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"unknown op");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
|
|
if (tmp.nodes_len == cap) {
|
|
size_t next_cap = cap != 0 ? cap * 2u : 4u;
|
|
amduat_pel_node_t *next =
|
|
(amduat_pel_node_t *)realloc(tmp.nodes,
|
|
next_cap * sizeof(*tmp.nodes));
|
|
if (next == NULL) {
|
|
free(node.inputs);
|
|
free((void *)node.params.data);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"oom");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
tmp.nodes = next;
|
|
cap = next_cap;
|
|
}
|
|
tmp.nodes[tmp.nodes_len++] = node;
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
have_nodes = true;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid nodes");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
} else if (key_len == strlen("roots") &&
|
|
memcmp(key, "roots", key_len) == 0) {
|
|
size_t cap = 0;
|
|
if (have_roots) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"duplicate roots");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (!amduatd_json_expect(&p, end, '[')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid roots");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
have_roots = true;
|
|
} else {
|
|
for (;;) {
|
|
amduat_pel_root_ref_t root;
|
|
bool have_node_id = false;
|
|
bool have_output_index = false;
|
|
|
|
memset(&root, 0, sizeof(root));
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid root");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
for (;;) {
|
|
const char *rk = NULL;
|
|
size_t rk_len = 0;
|
|
uint32_t v = 0;
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &rk, &rk_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid root");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (rk_len == strlen("node_id") &&
|
|
memcmp(rk, "node_id", rk_len) == 0) {
|
|
if (have_node_id ||
|
|
!amduatd_json_parse_u32_loose(&p, end, &v)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid root node_id");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
root.node_id = (amduat_pel_node_id_t)v;
|
|
have_node_id = true;
|
|
} else if (rk_len == strlen("output_index") &&
|
|
memcmp(rk, "output_index", rk_len) == 0) {
|
|
if (have_output_index ||
|
|
!amduatd_json_parse_u32_loose(&p, end, &v)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid root output_index");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
root.output_index = v;
|
|
have_output_index = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid root");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid root");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (!have_node_id || !have_output_index) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing root fields");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (tmp.roots_len == cap) {
|
|
size_t next_cap = cap != 0 ? cap * 2u : 2u;
|
|
amduat_pel_root_ref_t *next =
|
|
(amduat_pel_root_ref_t *)realloc(
|
|
tmp.roots, next_cap * sizeof(*tmp.roots));
|
|
if (next == NULL) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"oom");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
tmp.roots = next;
|
|
cap = next_cap;
|
|
}
|
|
tmp.roots[tmp.roots_len++] = root;
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
have_roots = true;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid roots");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
|
|
p = amduatd_json_skip_ws(p, end);
|
|
if (p != end) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
if (!have_nodes || !have_roots) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing nodes/roots");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
}
|
|
|
|
program.nodes = tmp.nodes;
|
|
program.nodes_len = tmp.nodes_len;
|
|
program.roots = tmp.roots;
|
|
program.roots_len = tmp.roots_len;
|
|
if (!amduat_pel_program_dag_validate(&program)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid program");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
|
|
if (!amduat_enc_pel_program_dag_encode_v1(&program, &program_bytes)) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
|
|
artifact = amduat_artifact_with_type(program_bytes,
|
|
amduat_type_tag(AMDUAT_PEL_TYPE_TAG_PROGRAM_DAG_1));
|
|
if (amduat_asl_store_put(store, artifact, &program_ref) != AMDUAT_ASL_STORE_OK) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"store error");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
|
|
if (!amduat_asl_ref_encode_hex(program_ref, &ref_hex)) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode ref error");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
{
|
|
int n = snprintf(json, sizeof(json), "{\"program_ref\":\"%s\"}\n", ref_hex);
|
|
free(ref_hex);
|
|
if (n <= 0 || (size_t)n >= sizeof(json)) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
goto pel_programs_cleanup;
|
|
}
|
|
ok = amduatd_http_send_json(fd, 200, "OK", json, false);
|
|
goto pel_programs_cleanup;
|
|
}
|
|
|
|
pel_programs_cleanup:
|
|
free(body);
|
|
free((void *)program_bytes.data);
|
|
amduat_reference_free(&program_ref);
|
|
amduatd_pel_program_tmp_free(&tmp);
|
|
return ok;
|
|
}
|
|
|
|
typedef struct {
|
|
char *key_hex;
|
|
char *value_hex;
|
|
uint64_t value_int;
|
|
int value_kind;
|
|
} amduatd_cf_binding_t;
|
|
|
|
static void amduatd_cf_binding_free(amduatd_cf_binding_t *b) {
|
|
if (b == NULL) {
|
|
return;
|
|
}
|
|
free(b->key_hex);
|
|
free(b->value_hex);
|
|
b->key_hex = NULL;
|
|
b->value_hex = NULL;
|
|
b->value_int = 0;
|
|
b->value_kind = 0;
|
|
}
|
|
|
|
static int amduatd_cf_binding_cmp(const void *a, const void *b) {
|
|
const amduatd_cf_binding_t *x = (const amduatd_cf_binding_t *)a;
|
|
const amduatd_cf_binding_t *y = (const amduatd_cf_binding_t *)b;
|
|
int c;
|
|
if (x == NULL || y == NULL) {
|
|
return 0;
|
|
}
|
|
c = strcmp(x->key_hex != NULL ? x->key_hex : "",
|
|
y->key_hex != NULL ? y->key_hex : "");
|
|
if (c != 0) {
|
|
return c;
|
|
}
|
|
if (x->value_kind != y->value_kind) {
|
|
return x->value_kind < y->value_kind ? -1 : 1;
|
|
}
|
|
if (x->value_kind == 1) {
|
|
if (x->value_int < y->value_int) {
|
|
return -1;
|
|
}
|
|
if (x->value_int > y->value_int) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
return strcmp(x->value_hex != NULL ? x->value_hex : "",
|
|
y->value_hex != NULL ? y->value_hex : "");
|
|
}
|
|
|
|
static bool amduatd_handle_post_context_frames(
|
|
int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduat_asl_store_fs_config_t *cfg,
|
|
const amduatd_concepts_t *concepts,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_http_req_t *req) {
|
|
uint8_t *body = NULL;
|
|
const char *p = NULL;
|
|
const char *end = NULL;
|
|
amduatd_cf_binding_t *bindings = NULL;
|
|
size_t bindings_len = 0;
|
|
size_t bindings_cap = 0;
|
|
amduatd_strbuf_t b;
|
|
amduat_artifact_t artifact;
|
|
amduat_reference_t ref;
|
|
char *ref_hex = NULL;
|
|
bool ok = false;
|
|
size_t i;
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
memset(&artifact, 0, sizeof(artifact));
|
|
memset(&ref, 0, sizeof(ref));
|
|
|
|
if (store == NULL || cfg == NULL || concepts == NULL || req == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"internal error");
|
|
}
|
|
if (req->content_length == 0) {
|
|
return amduatd_send_json_error(fd, 400, "Bad Request", "missing body");
|
|
}
|
|
if (req->content_length > (256u * 1024u)) {
|
|
return amduatd_send_json_error(fd, 413, "Payload Too Large",
|
|
"payload too large");
|
|
}
|
|
|
|
body = (uint8_t *)malloc(req->content_length);
|
|
if (body == NULL) {
|
|
return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
}
|
|
if (!amduatd_read_exact(fd, body, req->content_length)) {
|
|
free(body);
|
|
return false;
|
|
}
|
|
|
|
p = (const char *)body;
|
|
end = (const char *)body + req->content_length;
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto cf_cleanup;
|
|
}
|
|
|
|
for (;;) {
|
|
const char *key = NULL;
|
|
size_t key_len = 0;
|
|
const char *cur = NULL;
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto cf_cleanup;
|
|
}
|
|
|
|
if (key_len == strlen("bindings") && memcmp(key, "bindings", key_len) == 0) {
|
|
if (!amduatd_json_expect(&p, end, '[')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid bindings");
|
|
goto cf_cleanup;
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
} else {
|
|
for (;;) {
|
|
const char *bk = NULL;
|
|
size_t bk_len = 0;
|
|
const char *sv = NULL;
|
|
size_t sv_len = 0;
|
|
char *key_text = NULL;
|
|
char *value_text = NULL;
|
|
char *value_ref_text = NULL;
|
|
char *value_enum_text = NULL;
|
|
amduat_reference_t key_ref;
|
|
amduat_reference_t value_ref;
|
|
amduat_reference_t enum_ref;
|
|
bool have_key = false;
|
|
bool have_value = false;
|
|
bool have_value_ref = false;
|
|
bool have_value_scalar = false;
|
|
bool have_value_int = false;
|
|
bool have_value_enum = false;
|
|
uint64_t value_int = 0;
|
|
|
|
memset(&key_ref, 0, sizeof(key_ref));
|
|
memset(&value_ref, 0, sizeof(value_ref));
|
|
memset(&enum_ref, 0, sizeof(enum_ref));
|
|
|
|
if (!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid binding");
|
|
goto cf_cleanup;
|
|
}
|
|
for (;;) {
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &bk, &bk_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid binding");
|
|
goto cf_cleanup;
|
|
}
|
|
if (bk_len == strlen("key") && memcmp(bk, "key", bk_len) == 0) {
|
|
if (have_key ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &key_text)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid key");
|
|
goto cf_cleanup;
|
|
}
|
|
have_key = true;
|
|
} else if (bk_len == strlen("value") &&
|
|
memcmp(bk, "value", bk_len) == 0) {
|
|
if (have_value || have_value_ref || have_value_scalar ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &value_text)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid value");
|
|
goto cf_cleanup;
|
|
}
|
|
have_value = true;
|
|
} else if (bk_len == strlen("value_ref") &&
|
|
memcmp(bk, "value_ref", bk_len) == 0) {
|
|
if (have_value || have_value_ref || have_value_scalar ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &value_ref_text)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid value_ref");
|
|
goto cf_cleanup;
|
|
}
|
|
have_value_ref = true;
|
|
} else if (bk_len == strlen("value_scalar") &&
|
|
memcmp(bk, "value_scalar", bk_len) == 0) {
|
|
if (have_value || have_value_ref || have_value_scalar ||
|
|
!amduatd_json_expect(&p, end, '{')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid value_scalar");
|
|
goto cf_cleanup;
|
|
}
|
|
have_value_scalar = true;
|
|
for (;;) {
|
|
const char *sk = NULL;
|
|
size_t sk_len = 0;
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
if (!amduatd_json_parse_string_noesc(&p, end, &sk, &sk_len) ||
|
|
!amduatd_json_expect(&p, end, ':')) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid value_scalar");
|
|
goto cf_cleanup;
|
|
}
|
|
if (sk_len == strlen("int") && memcmp(sk, "int", sk_len) == 0) {
|
|
if (have_value_int || have_value_enum ||
|
|
!amduatd_json_parse_u64(&p, end, &value_int)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid int");
|
|
goto cf_cleanup;
|
|
}
|
|
have_value_int = true;
|
|
} else if (sk_len == strlen("enum") &&
|
|
memcmp(sk, "enum", sk_len) == 0) {
|
|
if (have_value_int || have_value_enum ||
|
|
!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) ||
|
|
!amduatd_copy_json_str(sv, sv_len, &value_enum_text)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid enum");
|
|
goto cf_cleanup;
|
|
}
|
|
have_value_enum = true;
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid value_scalar");
|
|
goto cf_cleanup;
|
|
}
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid binding");
|
|
goto cf_cleanup;
|
|
}
|
|
}
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!have_key ||
|
|
(!have_value && !have_value_ref && !have_value_scalar)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"missing binding fields");
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
goto cf_cleanup;
|
|
}
|
|
if (amduatd_decode_ref_or_name_latest(store,
|
|
cfg,
|
|
concepts,
|
|
dcfg,
|
|
key_text,
|
|
strlen(key_text),
|
|
&key_ref) != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid key");
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
goto cf_cleanup;
|
|
}
|
|
if (have_value || have_value_ref) {
|
|
const char *vtext = have_value ? value_text : value_ref_text;
|
|
if (amduatd_decode_ref_or_name_latest(store,
|
|
cfg,
|
|
concepts,
|
|
dcfg,
|
|
vtext,
|
|
strlen(vtext),
|
|
&value_ref) != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid value");
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
amduat_reference_free(&key_ref);
|
|
goto cf_cleanup;
|
|
}
|
|
} else if (have_value_scalar) {
|
|
if (have_value_enum) {
|
|
if (amduatd_decode_ref_or_name_latest(store,
|
|
cfg,
|
|
concepts,
|
|
dcfg,
|
|
value_enum_text,
|
|
strlen(value_enum_text),
|
|
&enum_ref) != AMDUATD_REF_OK) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid enum");
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
amduat_reference_free(&key_ref);
|
|
goto cf_cleanup;
|
|
}
|
|
} else if (!have_value_int) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid value_scalar");
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
amduat_reference_free(&key_ref);
|
|
goto cf_cleanup;
|
|
}
|
|
}
|
|
|
|
if (bindings_len == bindings_cap) {
|
|
size_t next_cap = bindings_cap != 0 ? bindings_cap * 2u : 4u;
|
|
amduatd_cf_binding_t *next =
|
|
(amduatd_cf_binding_t *)realloc(bindings,
|
|
next_cap * sizeof(*bindings));
|
|
if (next == NULL) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"oom");
|
|
free(key_text);
|
|
free(value_text);
|
|
amduat_reference_free(&key_ref);
|
|
amduat_reference_free(&value_ref);
|
|
goto cf_cleanup;
|
|
}
|
|
bindings = next;
|
|
bindings_cap = next_cap;
|
|
}
|
|
|
|
{
|
|
char *key_hex = NULL;
|
|
char *value_hex = NULL;
|
|
if (!amduat_asl_ref_encode_hex(key_ref, &key_hex)) {
|
|
free(key_hex);
|
|
free(value_hex);
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
amduat_reference_free(&key_ref);
|
|
amduat_reference_free(&value_ref);
|
|
amduat_reference_free(&enum_ref);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
goto cf_cleanup;
|
|
}
|
|
if (have_value || have_value_ref) {
|
|
if (!amduat_asl_ref_encode_hex(value_ref, &value_hex)) {
|
|
free(key_hex);
|
|
free(value_hex);
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
amduat_reference_free(&key_ref);
|
|
amduat_reference_free(&value_ref);
|
|
amduat_reference_free(&enum_ref);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
goto cf_cleanup;
|
|
}
|
|
bindings[bindings_len].value_kind = 0;
|
|
bindings[bindings_len].value_hex = value_hex;
|
|
} else if (have_value_int) {
|
|
bindings[bindings_len].value_kind = 1;
|
|
bindings[bindings_len].value_int = value_int;
|
|
} else if (have_value_enum) {
|
|
if (!amduat_asl_ref_encode_hex(enum_ref, &value_hex)) {
|
|
free(key_hex);
|
|
free(value_hex);
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
amduat_reference_free(&key_ref);
|
|
amduat_reference_free(&value_ref);
|
|
amduat_reference_free(&enum_ref);
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
goto cf_cleanup;
|
|
}
|
|
bindings[bindings_len].value_kind = 2;
|
|
bindings[bindings_len].value_hex = value_hex;
|
|
} else {
|
|
free(key_hex);
|
|
free(value_hex);
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
amduat_reference_free(&key_ref);
|
|
amduat_reference_free(&value_ref);
|
|
amduat_reference_free(&enum_ref);
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid value_scalar");
|
|
goto cf_cleanup;
|
|
}
|
|
bindings[bindings_len].key_hex = key_hex;
|
|
bindings_len++;
|
|
}
|
|
|
|
free(key_text);
|
|
free(value_text);
|
|
free(value_ref_text);
|
|
free(value_enum_text);
|
|
amduat_reference_free(&key_ref);
|
|
amduat_reference_free(&value_ref);
|
|
amduat_reference_free(&enum_ref);
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == ']') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid bindings");
|
|
goto cf_cleanup;
|
|
}
|
|
}
|
|
} else {
|
|
if (!amduatd_json_skip_value(&p, end, 0)) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json");
|
|
goto cf_cleanup;
|
|
}
|
|
}
|
|
|
|
cur = amduatd_json_skip_ws(p, end);
|
|
if (cur < end && *cur == ',') {
|
|
p = cur + 1;
|
|
continue;
|
|
}
|
|
if (cur < end && *cur == '}') {
|
|
p = cur + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bindings_len == 0) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing bindings");
|
|
goto cf_cleanup;
|
|
}
|
|
|
|
qsort(bindings, bindings_len, sizeof(*bindings), amduatd_cf_binding_cmp);
|
|
|
|
if (!amduatd_strbuf_append_cstr(&b, "{\"bindings\":[")) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom");
|
|
goto cf_cleanup;
|
|
}
|
|
for (i = 0; i < bindings_len; ++i) {
|
|
if (i != 0) {
|
|
(void)amduatd_strbuf_append_char(&b, ',');
|
|
}
|
|
(void)amduatd_strbuf_append_cstr(&b, "{\"key_ref\":\"");
|
|
(void)amduatd_strbuf_append_cstr(&b, bindings[i].key_hex);
|
|
if (bindings[i].value_kind == 0) {
|
|
(void)amduatd_strbuf_append_cstr(&b, "\",\"value_ref\":\"");
|
|
(void)amduatd_strbuf_append_cstr(&b, bindings[i].value_hex);
|
|
(void)amduatd_strbuf_append_cstr(&b, "\"}");
|
|
} else if (bindings[i].value_kind == 1) {
|
|
char tmp[32];
|
|
int n = snprintf(tmp, sizeof(tmp), "%llu",
|
|
(unsigned long long)bindings[i].value_int);
|
|
if (n <= 0 || (size_t)n >= sizeof(tmp)) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
goto cf_cleanup;
|
|
}
|
|
(void)amduatd_strbuf_append_cstr(&b, "\",\"value_int\":");
|
|
(void)amduatd_strbuf_append_cstr(&b, tmp);
|
|
(void)amduatd_strbuf_append_cstr(&b, "}");
|
|
} else if (bindings[i].value_kind == 2) {
|
|
(void)amduatd_strbuf_append_cstr(&b, "\",\"value_enum_ref\":\"");
|
|
(void)amduatd_strbuf_append_cstr(&b, bindings[i].value_hex);
|
|
(void)amduatd_strbuf_append_cstr(&b, "\"}");
|
|
} else {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
goto cf_cleanup;
|
|
}
|
|
}
|
|
(void)amduatd_strbuf_append_cstr(&b, "]}\n");
|
|
|
|
artifact = amduat_artifact(amduat_octets(b.data, b.len));
|
|
if (amduat_asl_store_put(store, artifact, &ref) != AMDUAT_ASL_STORE_OK) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"store error");
|
|
goto cf_cleanup;
|
|
}
|
|
if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error",
|
|
"encode error");
|
|
goto cf_cleanup;
|
|
}
|
|
{
|
|
char json[2048];
|
|
int n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", ref_hex);
|
|
free(ref_hex);
|
|
if (n <= 0 || (size_t)n >= sizeof(json)) {
|
|
ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error");
|
|
goto cf_cleanup;
|
|
}
|
|
ok = amduatd_http_send_json(fd, 200, "OK", json, false);
|
|
}
|
|
|
|
cf_cleanup:
|
|
free(body);
|
|
for (i = 0; i < bindings_len; ++i) {
|
|
amduatd_cf_binding_free(&bindings[i]);
|
|
}
|
|
free(bindings);
|
|
amduatd_strbuf_free(&b);
|
|
amduat_reference_free(&ref);
|
|
return ok;
|
|
}
|
|
|
|
static bool amduatd_handle_conn(int fd,
|
|
amduat_asl_store_t *store,
|
|
const amduat_asl_store_fs_config_t *cfg,
|
|
amduat_reference_t api_contract_ref,
|
|
amduat_reference_t ui_ref,
|
|
amduatd_concepts_t *concepts,
|
|
const amduatd_cfg_t *dcfg,
|
|
const amduatd_fed_cfg_t *fed_cfg,
|
|
const amduat_fed_coord_t *coord,
|
|
const amduatd_allowlist_t *allowlist,
|
|
amduatd_caps_t *caps,
|
|
const char *root_path,
|
|
amduatd_store_backend_t store_backend) {
|
|
amduatd_http_req_t req;
|
|
char no_query[1024];
|
|
bool ok = false;
|
|
amduat_octets_t actor = amduat_octets(NULL, 0u);
|
|
bool has_actor = false;
|
|
bool has_uid = false;
|
|
uid_t uid = 0;
|
|
amduatd_space_t req_space;
|
|
const amduatd_space_t *effective_space = NULL;
|
|
amduatd_cfg_t req_cfg;
|
|
const amduatd_cfg_t *effective_cfg = dcfg;
|
|
|
|
if (!amduatd_http_parse_request(fd, &req)) {
|
|
return false;
|
|
}
|
|
|
|
if (!amduatd_get_peer_actor(fd, &actor, &has_actor, &has_uid, &uid)) {
|
|
actor = amduat_octets(NULL, 0u);
|
|
has_actor = false;
|
|
has_uid = false;
|
|
}
|
|
req.has_actor = has_actor;
|
|
req.actor = actor;
|
|
req.has_uid = has_uid;
|
|
req.uid = uid;
|
|
|
|
amduatd_path_without_query(req.path, no_query, sizeof(no_query));
|
|
|
|
{
|
|
const char *space_header = NULL;
|
|
amduatd_space_resolve_status_t space_st;
|
|
|
|
if (req.x_space[0] != '\0') {
|
|
space_header = req.x_space;
|
|
}
|
|
space_st = amduatd_space_resolve_effective(&dcfg->space,
|
|
space_header,
|
|
&req_space,
|
|
&effective_space);
|
|
if (space_st != AMDUATD_SPACE_RESOLVE_OK || effective_space == NULL) {
|
|
ok = amduatd_send_json_error(fd, 400, "Bad Request",
|
|
"invalid X-Amduat-Space");
|
|
goto conn_cleanup;
|
|
}
|
|
if (effective_space != &dcfg->space) {
|
|
req_cfg = *dcfg;
|
|
req_cfg.space = req_space;
|
|
effective_cfg = &req_cfg;
|
|
}
|
|
req.effective_space = effective_space;
|
|
}
|
|
|
|
if (!(strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/cap/resolve") == 0) &&
|
|
!amduatd_actor_allowed(allowlist, has_uid, uid)) {
|
|
amduat_log(AMDUAT_LOG_DEBUG,
|
|
"reject uid=%u (not in allowlist)",
|
|
(unsigned int)uid);
|
|
ok = amduatd_http_send_text(fd, 403, "Forbidden", "forbidden\n", false);
|
|
goto conn_cleanup;
|
|
}
|
|
|
|
{
|
|
amduatd_ctx_t ui_ctx;
|
|
amduatd_http_resp_t ui_resp;
|
|
|
|
ui_ctx.store = store;
|
|
ui_ctx.ui_ref = ui_ref;
|
|
ui_ctx.store_cfg = cfg;
|
|
ui_ctx.concepts = concepts;
|
|
ui_ctx.daemon_cfg = effective_cfg;
|
|
ui_ctx.root_path = root_path;
|
|
ui_ctx.caps = caps;
|
|
ui_resp.fd = fd;
|
|
ui_resp.ok = false;
|
|
if (amduatd_ui_can_handle(&req)) {
|
|
if (amduatd_ui_handle(&ui_ctx, &req, &ui_resp)) {
|
|
ok = ui_resp.ok;
|
|
goto conn_cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/meta") == 0) {
|
|
ok = amduatd_handle_meta(fd, cfg, api_contract_ref, false);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/workspace") == 0) {
|
|
ok = amduatd_handle_get_workspace_ui(fd);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "HEAD") == 0 && strcmp(no_query, "/v1/meta") == 0) {
|
|
ok = amduatd_handle_meta(fd, cfg, api_contract_ref, true);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/contract") == 0) {
|
|
ok = amduatd_handle_get_contract(fd, store, &req, api_contract_ref);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/space/doctor") == 0) {
|
|
ok = amduatd_handle_get_space_doctor(fd,
|
|
store,
|
|
&req,
|
|
effective_cfg,
|
|
fed_cfg,
|
|
caps,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/space/roots") == 0) {
|
|
ok = amduatd_handle_get_space_roots(fd,
|
|
&req,
|
|
effective_cfg,
|
|
caps,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/space/manifest") == 0) {
|
|
ok = amduatd_handle_get_space_manifest(fd,
|
|
store,
|
|
&req,
|
|
effective_cfg,
|
|
caps,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/space/mounts/resolve") == 0) {
|
|
ok = amduatd_handle_get_space_mounts_resolve(fd,
|
|
store,
|
|
&req,
|
|
effective_cfg,
|
|
caps,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/space/workspace") == 0) {
|
|
ok = amduatd_handle_get_space_workspace(fd,
|
|
store,
|
|
&req,
|
|
effective_cfg,
|
|
fed_cfg,
|
|
caps,
|
|
root_path,
|
|
store_backend);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "PUT") == 0 &&
|
|
strcmp(no_query, "/v1/space/manifest") == 0) {
|
|
ok = amduatd_handle_put_space_manifest(fd,
|
|
store,
|
|
&req,
|
|
effective_cfg,
|
|
caps,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/space/mounts/sync/until") == 0) {
|
|
ok = amduatd_handle_post_space_mounts_sync_until(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/space/sync/status") == 0) {
|
|
ok = amduatd_handle_get_space_sync_status(fd,
|
|
store,
|
|
&req,
|
|
effective_cfg,
|
|
fed_cfg,
|
|
caps,
|
|
root_path,
|
|
store_backend);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/fed/records") == 0) {
|
|
ok = amduatd_handle_get_fed_records(fd, store, fed_cfg, &req, root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/fed/cursor") == 0) {
|
|
ok = amduatd_handle_get_fed_cursor(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/fed/pull/plan") == 0) {
|
|
ok = amduatd_handle_get_fed_pull_plan(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/fed/push/plan") == 0) {
|
|
ok = amduatd_handle_get_fed_push_plan(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/fed/status") == 0) {
|
|
ok = amduatd_handle_get_fed_status(fd, coord, fed_cfg, &req);
|
|
goto conn_cleanup;
|
|
}
|
|
|
|
if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/artifacts") == 0) {
|
|
ok = amduatd_handle_post_artifacts(fd, store, &req, root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/fed/pull") == 0) {
|
|
ok = amduatd_handle_post_fed_pull(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/fed/pull/until") == 0) {
|
|
ok = amduatd_handle_post_fed_pull_until(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/fed/push") == 0) {
|
|
ok = amduatd_handle_post_fed_push(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/fed/push/until") == 0) {
|
|
ok = amduatd_handle_post_fed_push_until(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/fed/ingest") == 0) {
|
|
ok = amduatd_handle_post_fed_ingest(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/fed/cursor") == 0) {
|
|
ok = amduatd_handle_post_fed_cursor(fd,
|
|
store,
|
|
fed_cfg,
|
|
caps,
|
|
effective_cfg,
|
|
&req,
|
|
root_path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/capabilities") == 0) {
|
|
amduatd_ctx_t caps_ctx;
|
|
amduatd_http_resp_t caps_resp;
|
|
|
|
caps_ctx.store = store;
|
|
caps_ctx.ui_ref = ui_ref;
|
|
caps_ctx.store_cfg = cfg;
|
|
caps_ctx.concepts = concepts;
|
|
caps_ctx.daemon_cfg = effective_cfg;
|
|
caps_ctx.root_path = root_path;
|
|
caps_ctx.caps = caps;
|
|
caps_resp.fd = fd;
|
|
caps_resp.ok = false;
|
|
if (amduatd_caps_handle(&caps_ctx, &req, &caps_resp)) {
|
|
ok = caps_resp.ok;
|
|
}
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/run") == 0) {
|
|
ok = amduatd_handle_post_pel_run(fd, store, cfg, concepts, effective_cfg,
|
|
root_path, &req);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/pel/programs") == 0) {
|
|
ok = amduatd_handle_post_pel_programs(fd, store, &req);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "POST") == 0 &&
|
|
strcmp(no_query, "/v1/context_frames") == 0) {
|
|
ok = amduatd_handle_post_context_frames(fd, store, cfg, concepts,
|
|
effective_cfg,
|
|
&req);
|
|
goto conn_cleanup;
|
|
}
|
|
{
|
|
amduatd_ctx_t concepts_ctx;
|
|
amduatd_http_resp_t concepts_resp;
|
|
|
|
concepts_ctx.store = store;
|
|
concepts_ctx.ui_ref = ui_ref;
|
|
concepts_ctx.store_cfg = cfg;
|
|
concepts_ctx.concepts = concepts;
|
|
concepts_ctx.daemon_cfg = effective_cfg;
|
|
concepts_ctx.root_path = root_path;
|
|
concepts_ctx.caps = caps;
|
|
concepts_resp.fd = fd;
|
|
concepts_resp.ok = false;
|
|
if (amduatd_concepts_can_handle(&req)) {
|
|
if (amduatd_concepts_handle(&concepts_ctx, &req, &concepts_resp)) {
|
|
ok = concepts_resp.ok;
|
|
goto conn_cleanup;
|
|
}
|
|
}
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strncmp(no_query, "/v1/fed/artifacts/", 18) == 0) {
|
|
ok = amduatd_handle_get_fed_artifact(fd, store, fed_cfg, &req, req.path);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strcmp(no_query, "/v1/cap/resolve") == 0) {
|
|
amduatd_ctx_t caps_ctx;
|
|
amduatd_http_resp_t caps_resp;
|
|
|
|
caps_ctx.store = store;
|
|
caps_ctx.ui_ref = ui_ref;
|
|
caps_ctx.store_cfg = cfg;
|
|
caps_ctx.concepts = concepts;
|
|
caps_ctx.daemon_cfg = effective_cfg;
|
|
caps_ctx.root_path = root_path;
|
|
caps_ctx.caps = caps;
|
|
caps_resp.fd = fd;
|
|
caps_resp.ok = false;
|
|
if (amduatd_caps_handle(&caps_ctx, &req, &caps_resp)) {
|
|
ok = caps_resp.ok;
|
|
}
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "GET") == 0 &&
|
|
strncmp(no_query, "/v1/artifacts/", 14) == 0) {
|
|
ok = amduatd_handle_get_artifact(fd, store, &req, req.path, false);
|
|
goto conn_cleanup;
|
|
}
|
|
if (strcmp(req.method, "HEAD") == 0 &&
|
|
strncmp(no_query, "/v1/artifacts/", 14) == 0) {
|
|
ok = amduatd_handle_head_artifact(fd, store, &req, req.path);
|
|
goto conn_cleanup;
|
|
}
|
|
|
|
ok = amduatd_http_send_not_found(fd, &req);
|
|
|
|
conn_cleanup:
|
|
amduatd_http_req_free(&req);
|
|
return ok;
|
|
}
|
|
|
|
static void amduatd_print_usage(FILE *stream) {
|
|
fprintf(stream,
|
|
"usage:\n"
|
|
" amduatd [--root PATH] [--sock PATH]\n"
|
|
" [--space SPACE_ID] [--migrate-unscoped-edges]\n"
|
|
" [--edges-refresh-ms MS]\n"
|
|
" [--store-backend fs|index]\n"
|
|
" [--fed-enable] [--fed-transport stub|unix]\n"
|
|
" [--fed-unix-sock PATH]\n"
|
|
" [--fed-domain-id ID] [--fed-registry-ref REF]\n"
|
|
" [--fed-require-space]\n"
|
|
" [--allow-uid UID] [--allow-user NAME]\n"
|
|
" [--enable-cap-reads]\n"
|
|
" [--enable-derivation-index]\n"
|
|
" [--derivation-index-strict]\n"
|
|
"\n"
|
|
"defaults:\n"
|
|
" --root %s\n"
|
|
" --sock %s\n",
|
|
AMDUATD_DEFAULT_ROOT,
|
|
AMDUATD_DEFAULT_SOCK);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
const char *root = AMDUATD_DEFAULT_ROOT;
|
|
const char *sock_path = AMDUATD_DEFAULT_SOCK;
|
|
const char *space_id = NULL;
|
|
bool migrate_unscoped_edges = false;
|
|
bool enable_cap_reads = false;
|
|
amduatd_store_backend_t store_backend = AMDUATD_STORE_BACKEND_FS;
|
|
amduatd_cfg_t dcfg;
|
|
amduatd_caps_t caps;
|
|
amduat_asl_store_fs_config_t cfg;
|
|
amduatd_store_ctx_t store_ctx;
|
|
amduat_asl_store_t store;
|
|
amduat_reference_t api_contract_ref;
|
|
amduat_reference_t ui_ref;
|
|
amduatd_concepts_t concepts;
|
|
amduatd_fed_cfg_t fed_cfg;
|
|
amduat_fed_transport_stub_t fed_stub;
|
|
amduat_fed_transport_unix_t fed_unix;
|
|
amduat_fed_coord_cfg_t fed_coord_cfg;
|
|
amduat_fed_coord_t *fed_coord = NULL;
|
|
amduatd_allowlist_t allowlist;
|
|
int i;
|
|
int sfd = -1;
|
|
uint64_t last_tick_ms = 0;
|
|
uint64_t last_edges_refresh_ms = 0;
|
|
const char *fed_err = NULL;
|
|
|
|
memset(&api_contract_ref, 0, sizeof(api_contract_ref));
|
|
memset(&ui_ref, 0, sizeof(ui_ref));
|
|
memset(&concepts, 0, sizeof(concepts));
|
|
memset(&allowlist, 0, sizeof(allowlist));
|
|
memset(&dcfg, 0, sizeof(dcfg));
|
|
memset(&caps, 0, sizeof(caps));
|
|
amduatd_fed_cfg_init(&fed_cfg);
|
|
|
|
for (i = 1; i < argc; ++i) {
|
|
amduatd_fed_parse_result_t fed_rc;
|
|
fed_rc = amduatd_fed_cfg_parse_arg(&fed_cfg, argc, argv, &i, &fed_err);
|
|
if (fed_rc == AMDUATD_FED_PARSE_OK) {
|
|
continue;
|
|
}
|
|
if (fed_rc == AMDUATD_FED_PARSE_ERROR) {
|
|
fprintf(stderr, "error: %s\n", fed_err != NULL ? fed_err : "fed parse error");
|
|
return 2;
|
|
}
|
|
if (strcmp(argv[i], "--root") == 0) {
|
|
if (i + 1 >= argc) {
|
|
fprintf(stderr, "error: --root requires a path\n");
|
|
return 2;
|
|
}
|
|
root = argv[++i];
|
|
} else if (strcmp(argv[i], "--sock") == 0) {
|
|
if (i + 1 >= argc) {
|
|
fprintf(stderr, "error: --sock requires a path\n");
|
|
return 2;
|
|
}
|
|
sock_path = argv[++i];
|
|
} else if (strcmp(argv[i], "--space") == 0) {
|
|
if (i + 1 >= argc) {
|
|
fprintf(stderr, "error: --space requires a value\n");
|
|
return 2;
|
|
}
|
|
space_id = argv[++i];
|
|
if (!amduatd_space_space_id_is_valid(space_id)) {
|
|
fprintf(stderr, "error: invalid --space\n");
|
|
return 2;
|
|
}
|
|
} else if (strcmp(argv[i], "--migrate-unscoped-edges") == 0) {
|
|
migrate_unscoped_edges = true;
|
|
} else if (strcmp(argv[i], "--edges-refresh-ms") == 0) {
|
|
char *endp = NULL;
|
|
unsigned long long refresh_val;
|
|
if (i + 1 >= argc) {
|
|
fprintf(stderr, "error: --edges-refresh-ms requires a value\n");
|
|
return 2;
|
|
}
|
|
refresh_val = strtoull(argv[++i], &endp, 10);
|
|
if (endp == argv[i] || *endp != '\0') {
|
|
fprintf(stderr, "error: invalid --edges-refresh-ms\n");
|
|
return 2;
|
|
}
|
|
dcfg.edges_refresh_ms = (uint64_t)refresh_val;
|
|
} else if (strcmp(argv[i], "--store-backend") == 0) {
|
|
if (i + 1 >= argc) {
|
|
fprintf(stderr, "error: --store-backend requires a value\n");
|
|
return 2;
|
|
}
|
|
if (!amduatd_store_backend_parse(argv[++i], &store_backend)) {
|
|
fprintf(stderr, "error: invalid --store-backend\n");
|
|
return 2;
|
|
}
|
|
} else if (strcmp(argv[i], "--allow-uid") == 0) {
|
|
char *endp = NULL;
|
|
unsigned long uid_val;
|
|
if (i + 1 >= argc) {
|
|
fprintf(stderr, "error: --allow-uid requires a value\n");
|
|
return 2;
|
|
}
|
|
uid_val = strtoul(argv[++i], &endp, 10);
|
|
if (endp == argv[i] || *endp != '\0' || uid_val > UINT32_MAX) {
|
|
fprintf(stderr, "error: invalid --allow-uid\n");
|
|
return 2;
|
|
}
|
|
if (!amduatd_allowlist_add(&allowlist, (uid_t)uid_val)) {
|
|
fprintf(stderr, "error: failed to add allow-uid\n");
|
|
return 2;
|
|
}
|
|
} else if (strcmp(argv[i], "--allow-user") == 0) {
|
|
struct passwd *pwd;
|
|
if (i + 1 >= argc) {
|
|
fprintf(stderr, "error: --allow-user requires a value\n");
|
|
return 2;
|
|
}
|
|
pwd = getpwnam(argv[++i]);
|
|
if (pwd == NULL) {
|
|
fprintf(stderr, "error: unknown user for --allow-user\n");
|
|
return 2;
|
|
}
|
|
if (!amduatd_allowlist_add(&allowlist, pwd->pw_uid)) {
|
|
fprintf(stderr, "error: failed to add allow-user\n");
|
|
return 2;
|
|
}
|
|
} else if (strcmp(argv[i], "--enable-cap-reads") == 0) {
|
|
enable_cap_reads = true;
|
|
} else if (strcmp(argv[i], "--enable-derivation-index") == 0) {
|
|
dcfg.derivation_index_enabled = true;
|
|
} else if (strcmp(argv[i], "--derivation-index-strict") == 0) {
|
|
dcfg.derivation_index_enabled = true;
|
|
dcfg.derivation_index_strict = true;
|
|
} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
|
amduatd_print_usage(stdout);
|
|
return 0;
|
|
} else {
|
|
fprintf(stderr, "error: unknown option: %s\n", argv[i]);
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
if (!amduatd_space_init(&dcfg.space, space_id, migrate_unscoped_edges)) {
|
|
fprintf(stderr, "error: invalid --space\n");
|
|
return 2;
|
|
}
|
|
|
|
if (!amduatd_fed_requirements_check(store_backend, &fed_cfg, &fed_err)) {
|
|
fprintf(stderr,
|
|
"error: %s\n",
|
|
fed_err != NULL ? fed_err : "invalid federation config");
|
|
return 2;
|
|
}
|
|
|
|
if (!amduatd_store_init(&store, &cfg, &store_ctx, root, store_backend)) {
|
|
fprintf(stderr, "error: failed to initialize store: %s\n", root);
|
|
return 8;
|
|
}
|
|
amduat_log(AMDUAT_LOG_INFO,
|
|
"store backend=%s",
|
|
amduatd_store_backend_name(store_backend));
|
|
amduat_log(AMDUAT_LOG_INFO,
|
|
"derivation index=%s strict=%s",
|
|
dcfg.derivation_index_enabled ? "on" : "off",
|
|
dcfg.derivation_index_strict ? "on" : "off");
|
|
if (!amduatd_caps_init(&caps, &dcfg, root)) {
|
|
amduat_log(AMDUAT_LOG_WARN, "capabilities unavailable");
|
|
}
|
|
caps.enable_cap_reads = enable_cap_reads;
|
|
|
|
if (!amduatd_seed_api_contract(&store, &cfg, &api_contract_ref)) {
|
|
fprintf(stderr, "error: failed to seed api contract\n");
|
|
return 8;
|
|
}
|
|
#if AMDUATD_ENABLE_UI
|
|
if (!amduatd_seed_ui_html(&store, &cfg, &ui_ref)) {
|
|
fprintf(stderr, "error: failed to seed ui html\n");
|
|
return 8;
|
|
}
|
|
#endif
|
|
if (!amduatd_concepts_init(&concepts, &store, &dcfg.space, root, true)) {
|
|
fprintf(stderr, "error: failed to init concept edges\n");
|
|
return 8;
|
|
}
|
|
#if AMDUATD_ENABLE_UI
|
|
if (!amduatd_seed_ms_ui_state(&store, &concepts, &dcfg)) {
|
|
fprintf(stderr, "error: failed to seed ms ui state\n");
|
|
return 8;
|
|
}
|
|
#endif
|
|
|
|
{
|
|
char *registry_hex = NULL;
|
|
if (fed_cfg.registry_ref_set) {
|
|
if (!amduat_asl_ref_encode_hex(fed_cfg.registry_ref, ®istry_hex)) {
|
|
registry_hex = NULL;
|
|
}
|
|
}
|
|
amduat_log(AMDUAT_LOG_INFO,
|
|
"federation enabled=%s transport=%s domain_id=%u registry_ref=%s require_space=%s",
|
|
fed_cfg.enabled ? "on" : "off",
|
|
amduatd_fed_transport_name(fed_cfg.transport_kind),
|
|
(unsigned int)fed_cfg.local_domain_id,
|
|
registry_hex != NULL ? registry_hex : "null",
|
|
fed_cfg.require_space ? "on" : "off");
|
|
free(registry_hex);
|
|
}
|
|
|
|
amduat_fed_transport_stub_init(&fed_stub);
|
|
memset(&fed_coord_cfg, 0, sizeof(fed_coord_cfg));
|
|
if (fed_cfg.enabled) {
|
|
fed_coord_cfg.local_domain_id = fed_cfg.local_domain_id;
|
|
fed_coord_cfg.authoritative_store = &store;
|
|
fed_coord_cfg.cache_store = NULL;
|
|
if (fed_cfg.registry_ref_set) {
|
|
fed_coord_cfg.registry_ref = fed_cfg.registry_ref;
|
|
} else {
|
|
fed_coord_cfg.registry_ref = amduat_reference(0u, amduat_octets(NULL, 0u));
|
|
}
|
|
if (fed_cfg.transport_kind == AMDUATD_FED_TRANSPORT_UNIX) {
|
|
if (!amduat_fed_transport_unix_init(&fed_unix,
|
|
fed_cfg.unix_socket_path)) {
|
|
fprintf(stderr, "error: invalid --fed-unix-sock\n");
|
|
return 2;
|
|
}
|
|
if (dcfg.space.enabled) {
|
|
(void)amduat_fed_transport_unix_set_space(&fed_unix,
|
|
dcfg.space.space_id_buf);
|
|
}
|
|
fed_coord_cfg.transport = amduat_fed_transport_unix_ops(&fed_unix);
|
|
} else {
|
|
fed_coord_cfg.transport = amduat_fed_transport_stub_ops(&fed_stub);
|
|
}
|
|
if (amduat_fed_coord_open(&fed_coord_cfg, &fed_coord) !=
|
|
AMDUAT_FED_COORD_OK) {
|
|
fprintf(stderr, "error: failed to init federation coordinator\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) {
|
|
fd_set rfds;
|
|
struct timeval tv;
|
|
struct timeval *tvp = NULL;
|
|
int rc;
|
|
|
|
if (fed_coord != NULL) {
|
|
uint64_t now_ms = amduatd_now_ms();
|
|
uint64_t due_ms = last_tick_ms == 0
|
|
? now_ms
|
|
: last_tick_ms + AMDUATD_FED_TICK_MS;
|
|
uint64_t wait_ms = due_ms <= now_ms ? 0 : (due_ms - now_ms);
|
|
tv.tv_sec = (time_t)(wait_ms / 1000u);
|
|
tv.tv_usec = (suseconds_t)((wait_ms % 1000u) * 1000u);
|
|
tvp = &tv;
|
|
}
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_SET(sfd, &rfds);
|
|
rc = select(sfd + 1, &rfds, NULL, NULL, tvp);
|
|
if (rc < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
perror("select");
|
|
break;
|
|
}
|
|
|
|
if (fed_coord != NULL) {
|
|
uint64_t now_ms = amduatd_now_ms();
|
|
if (last_tick_ms == 0 ||
|
|
now_ms - last_tick_ms >= AMDUATD_FED_TICK_MS) {
|
|
(void)amduat_fed_coord_tick(fed_coord, now_ms);
|
|
last_tick_ms = now_ms;
|
|
}
|
|
}
|
|
|
|
if (rc == 0) {
|
|
continue;
|
|
}
|
|
if (!FD_ISSET(sfd, &rfds)) {
|
|
continue;
|
|
}
|
|
|
|
{
|
|
int cfd = accept(sfd, NULL, NULL);
|
|
if (cfd < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
perror("accept");
|
|
break;
|
|
}
|
|
|
|
(void)amduatd_handle_conn(cfd,
|
|
&store,
|
|
&cfg,
|
|
api_contract_ref,
|
|
ui_ref,
|
|
&concepts,
|
|
&dcfg,
|
|
&fed_cfg,
|
|
fed_coord,
|
|
&allowlist,
|
|
&caps,
|
|
root,
|
|
store_backend);
|
|
(void)close(cfd);
|
|
|
|
if (dcfg.edges_refresh_ms != 0u) {
|
|
uint64_t now_ms = amduatd_now_ms();
|
|
if (last_edges_refresh_ms == 0u ||
|
|
now_ms - last_edges_refresh_ms >= dcfg.edges_refresh_ms) {
|
|
amduatd_ctx_t refresh_ctx;
|
|
refresh_ctx.store = &store;
|
|
refresh_ctx.ui_ref = amduat_reference(0u, amduat_octets(NULL, 0u));
|
|
refresh_ctx.store_cfg = &cfg;
|
|
refresh_ctx.concepts = &concepts;
|
|
refresh_ctx.daemon_cfg = &dcfg;
|
|
refresh_ctx.root_path = root;
|
|
refresh_ctx.caps = ∩︀
|
|
(void)amduatd_concepts_refresh_edges(&refresh_ctx, 0u);
|
|
last_edges_refresh_ms = now_ms;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
amduat_reference_free(&api_contract_ref);
|
|
amduat_reference_free(&ui_ref);
|
|
amduatd_concepts_free(&concepts);
|
|
if (fed_coord != NULL) {
|
|
amduat_fed_coord_close(fed_coord);
|
|
}
|
|
amduatd_allowlist_free(&allowlist);
|
|
(void)unlink(sock_path);
|
|
(void)close(sfd);
|
|
return 0;
|
|
}
|