We don't need SHA verification or Merkle dags in our transport bundle. Content stores can handle both bundle and term verification and hashing.
225 lines
8.6 KiB
JavaScript
225 lines
8.6 KiB
JavaScript
/**
|
|
* lib.js — FFI wrapper around libarboricx.so via koffi.
|
|
*
|
|
* Exports low-level C ABI bindings and high-level helpers.
|
|
*/
|
|
|
|
import { existsSync } from 'node:fs';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import koffi from 'koffi';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
koffi.opaque('arb_ctx_t');
|
|
|
|
// ── Library discovery ───────────────────────────────────────────────────────
|
|
|
|
export function findLib() {
|
|
const env = process.env.ARBORICX_LIB;
|
|
if (env) {
|
|
if (existsSync(env)) return env;
|
|
throw new Error(`ARBORICX_LIB set but file not found: ${env}`);
|
|
}
|
|
|
|
const candidates = [
|
|
resolve(__dirname, 'libarboricx.so'),
|
|
'libarboricx.so',
|
|
'./libarboricx.so',
|
|
'/usr/local/lib/libarboricx.so',
|
|
'/usr/lib/libarboricx.so',
|
|
];
|
|
|
|
for (const p of candidates) {
|
|
if (existsSync(p)) return p;
|
|
}
|
|
|
|
throw new Error('libarboricx.so not found. Set ARBORICX_LIB to its full path.');
|
|
}
|
|
|
|
// ── FFI setup ───────────────────────────────────────────────────────────────
|
|
|
|
let _lib = null;
|
|
let _libPath = null;
|
|
|
|
function ensureLib() {
|
|
if (_lib) return _lib;
|
|
const path = findLib();
|
|
_lib = koffi.load(path);
|
|
_libPath = path;
|
|
return _lib;
|
|
}
|
|
|
|
export function loadLib(path) {
|
|
if (_lib && _libPath === path) return;
|
|
_lib = koffi.load(path);
|
|
_libPath = path;
|
|
}
|
|
|
|
function getLib() {
|
|
if (_lib) return _lib;
|
|
return ensureLib();
|
|
}
|
|
|
|
// ── Context lifecycle ───────────────────────────────────────────────────────
|
|
|
|
export function init(libPath) {
|
|
if (libPath) loadLib(libPath);
|
|
const lib = getLib();
|
|
const ctx = lib.func('arb_ctx_t *arboricx_init(void)')();
|
|
if (!ctx) throw new Error('arboricx_init failed');
|
|
return ctx;
|
|
}
|
|
|
|
export function free(ctx) {
|
|
getLib().func('void arboricx_free(arb_ctx_t *ctx)')(ctx);
|
|
}
|
|
|
|
// ── Bundle loading ──────────────────────────────────────────────────────────
|
|
|
|
export function loadBundle(ctx, bytes, name) {
|
|
const result = getLib().func('uint32_t arb_load_bundle(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len, const char *name)')(ctx, bytes, bytes.length, name);
|
|
if (result === 0) throw new Error(`arb_load_bundle failed for export "${name}"`);
|
|
return result;
|
|
}
|
|
|
|
export function loadBundleDefault(ctx, bytes) {
|
|
const result = getLib().func('uint32_t arb_load_bundle_default(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len)')(ctx, bytes, bytes.length);
|
|
if (result === 0) throw new Error('arb_load_bundle_default failed');
|
|
return result;
|
|
}
|
|
|
|
// ── Reduction ───────────────────────────────────────────────────────────────
|
|
|
|
export function reduce(ctx, root, fuel = 1_000_000_000n) {
|
|
const f = getLib().func('uint32_t arb_reduce(arb_ctx_t *ctx, uint32_t root, uint64_t fuel)');
|
|
return f(ctx, root, typeof fuel === 'bigint' ? fuel : BigInt(fuel));
|
|
}
|
|
|
|
// ── Tree construction ───────────────────────────────────────────────────────
|
|
|
|
export function leaf(ctx) {
|
|
return getLib().func('uint32_t arb_leaf(arb_ctx_t *ctx)')(ctx);
|
|
}
|
|
|
|
export function stem(ctx, child) {
|
|
return getLib().func('uint32_t arb_stem(arb_ctx_t *ctx, uint32_t child)')(ctx, child);
|
|
}
|
|
|
|
export function fork(ctx, left, right) {
|
|
return getLib().func('uint32_t arb_fork(arb_ctx_t *ctx, uint32_t left, uint32_t right)')(ctx, left, right);
|
|
}
|
|
|
|
export function app(ctx, func, arg) {
|
|
return getLib().func('uint32_t arb_app(arb_ctx_t *ctx, uint32_t func, uint32_t arg)')(ctx, func, arg);
|
|
}
|
|
|
|
// ── Codec constructors ──────────────────────────────────────────────────────
|
|
|
|
export function ofNumber(ctx, n) {
|
|
const big = typeof n === 'bigint' ? n : BigInt(n);
|
|
return getLib().func('uint32_t arb_of_number(arb_ctx_t *ctx, uint64_t n)')(ctx, big);
|
|
}
|
|
|
|
export function ofString(ctx, s) {
|
|
return getLib().func('uint32_t arb_of_string(arb_ctx_t *ctx, const char *s)')(ctx, s);
|
|
}
|
|
|
|
export function ofBytes(ctx, bytes) {
|
|
return getLib().func('uint32_t arb_of_bytes(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len)')(ctx, bytes, bytes.length);
|
|
}
|
|
|
|
export function ofList(ctx, items) {
|
|
const arr = new Uint32Array(items);
|
|
return getLib().func('uint32_t arb_of_list(arb_ctx_t *ctx, _In_ uint32_t *items, size_t len)')(ctx, arr, arr.length);
|
|
}
|
|
|
|
// ── Codec destructors ───────────────────────────────────────────────────────
|
|
|
|
export function toNumber(ctx, root) {
|
|
const out = [0];
|
|
const ok = getLib().func('int arb_to_number(arb_ctx_t *ctx, uint32_t root, _Out_ uint64_t *out)')(ctx, root, out);
|
|
if (!ok) throw new Error('arb_to_number failed');
|
|
return typeof out[0] === 'bigint' ? Number(out[0]) : out[0];
|
|
}
|
|
|
|
export function toString(ctx, root) {
|
|
const ptrOut = [null];
|
|
const lenOut = [0];
|
|
const ok = getLib().func('int arb_to_string(arb_ctx_t *ctx, uint32_t root, _Out_ uint8_t **out_ptr, _Out_ size_t *out_len)')(ctx, root, ptrOut, lenOut);
|
|
if (!ok) throw new Error('arb_to_string failed');
|
|
|
|
const bytes = koffi.decode(ptrOut[0], 'uint8_t', lenOut[0]);
|
|
const str = Buffer.from(bytes).toString('utf-8');
|
|
getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]);
|
|
return str;
|
|
}
|
|
|
|
export function toBytes(ctx, root) {
|
|
const ptrOut = [null];
|
|
const lenOut = [0];
|
|
const ok = getLib().func('int arb_to_bytes(arb_ctx_t *ctx, uint32_t root, _Out_ uint8_t **out_ptr, _Out_ size_t *out_len)')(ctx, root, ptrOut, lenOut);
|
|
if (!ok) throw new Error('arb_to_bytes failed');
|
|
|
|
const bytes = Buffer.from(koffi.decode(ptrOut[0], 'uint8_t', lenOut[0]));
|
|
getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]);
|
|
return bytes;
|
|
}
|
|
|
|
export function toBool(ctx, root) {
|
|
const out = [0];
|
|
const ok = getLib().func('int arb_to_bool(arb_ctx_t *ctx, uint32_t root, _Out_ int *out)')(ctx, root, out);
|
|
if (!ok) throw new Error('arb_to_bool failed');
|
|
return out[0] !== 0;
|
|
}
|
|
|
|
// ── Result unwrapping ───────────────────────────────────────────────────────
|
|
|
|
export function unwrapResult(ctx, root) {
|
|
const outOk = [0];
|
|
const outValue = [0];
|
|
const outRest = [0];
|
|
const ok = getLib().func('int arb_unwrap_result(arb_ctx_t *ctx, uint32_t root, _Out_ int *out_ok, _Out_ uint32_t *out_value, _Out_ uint32_t *out_rest)')(ctx, root, outOk, outValue, outRest);
|
|
if (!ok) throw new Error('arb_unwrap_result failed');
|
|
return { ok: outOk[0] !== 0, value: outValue[0], rest: outRest[0] };
|
|
}
|
|
|
|
export function unwrapHostValue(ctx, root) {
|
|
const outTag = [0n];
|
|
const outPayload = [0];
|
|
const ok = getLib().func('int arb_unwrap_host_value(arb_ctx_t *ctx, uint32_t root, _Out_ uint64_t *out_tag, _Out_ uint32_t *out_payload)')(ctx, root, outTag, outPayload);
|
|
if (!ok) throw new Error('arb_unwrap_host_value failed');
|
|
return { tag: outTag[0], payload: outPayload[0] };
|
|
}
|
|
|
|
// ── Kernel ──────────────────────────────────────────────────────────────────
|
|
|
|
export function kernelRoot(ctx) {
|
|
return getLib().func('uint32_t arb_kernel_root(arb_ctx_t *ctx)')(ctx);
|
|
}
|
|
|
|
// ── High-level helpers ──────────────────────────────────────────────────────
|
|
|
|
export function decode(ctx, root) {
|
|
try {
|
|
return toBool(ctx, root) ? 'true' : 'false';
|
|
} catch {
|
|
try {
|
|
return toString(ctx, root);
|
|
} catch {
|
|
try {
|
|
return String(toNumber(ctx, root));
|
|
} catch {
|
|
throw new Error('could not decode result');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function decodeType(ctx, root) {
|
|
try { toBool(ctx, root); return 'bool'; } catch {}
|
|
try { toString(ctx, root); return 'string'; } catch {}
|
|
try { toNumber(ctx, root); return 'number'; } catch {}
|
|
return 'unknown (raw tree)';
|
|
}
|