Serve API contract bytes at /v1/contract

This commit is contained in:
Carl Niklas Rydberg 2025-12-22 19:51:51 +01:00
parent 345c291c1b
commit d8b789443e
2 changed files with 88 additions and 15 deletions

View file

@ -39,6 +39,12 @@ curl --unix-socket amduatd.sock http://localhost/v1/meta
Discover the store-backed API contract ref: Discover the store-backed API contract ref:
```sh
curl --unix-socket amduatd.sock 'http://localhost/v1/contract?format=ref'
```
Fetch the contract bytes (JSON):
```sh ```sh
curl --unix-socket amduatd.sock http://localhost/v1/contract curl --unix-socket amduatd.sock http://localhost/v1/contract
``` ```
@ -75,7 +81,8 @@ curl --unix-socket amduatd.sock -X POST http://localhost/v1/pel/run \
## Current endpoints ## Current endpoints
- `GET /v1/meta``{store_id, encoding_profile_id, hash_id, api_contract_ref}` - `GET /v1/meta``{store_id, encoding_profile_id, hash_id, api_contract_ref}`
- `GET /v1/contract``{ref}` (fetch bytes via `GET /v1/artifacts/{ref}`) - `GET /v1/contract` → contract bytes (JSON) (+ `X-Amduat-Contract-Ref` header)
- `GET /v1/contract?format=ref``{ref}`
- `POST /v1/artifacts` - `POST /v1/artifacts`
- raw bytes: `Content-Type: application/octet-stream` (+ optional `X-Amduat-Type-Tag: 0x...`) - raw bytes: `Content-Type: application/octet-stream` (+ optional `X-Amduat-Type-Tag: 0x...`)
- artifact framing: `Content-Type: application/vnd.amduat.asl.artifact+v1` - artifact framing: `Content-Type: application/vnd.amduat.asl.artifact+v1`

View file

@ -854,16 +854,24 @@ static bool amduatd_handle_meta(int fd,
} }
static bool amduatd_handle_get_contract(int fd, 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) { amduat_reference_t api_contract_ref) {
char *hex = NULL; char *hex = NULL;
char json[2048]; char format[32];
int n;
if (api_contract_ref.hash_id == 0 || api_contract_ref.digest.data == NULL || if (api_contract_ref.hash_id == 0 || api_contract_ref.digest.data == NULL ||
api_contract_ref.digest.len == 0) { api_contract_ref.digest.len == 0) {
return amduatd_http_send_json(fd, 404, "Not Found", "{\"ref\":null}\n", return amduatd_http_send_not_found(fd, req);
false);
} }
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)) { if (!amduat_asl_ref_encode_hex(api_contract_ref, &hex)) {
return amduatd_http_send_text(fd, 500, "Internal Server Error", return amduatd_http_send_text(fd, 500, "Internal Server Error",
"encode error\n", false); "encode error\n", false);
@ -871,12 +879,70 @@ static bool amduatd_handle_get_contract(int fd,
n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", hex); n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", hex);
free(hex); free(hex);
if (n <= 0 || (size_t)n >= sizeof(json)) { if (n <= 0 || (size_t)n >= sizeof(json)) {
return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n", return amduatd_http_send_text(fd, 500, "Internal Server Error",
false); "error\n", false);
} }
return amduatd_http_send_json(fd, 200, "OK", json, false); return amduatd_http_send_json(fd, 200, "OK", json, false);
} }
{
amduat_artifact_t artifact;
amduat_asl_store_error_t err;
memset(&artifact, 0, sizeof(artifact));
err = amduat_asl_store_get(store, api_contract_ref, &artifact);
if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) {
return amduatd_http_send_not_found(fd, req);
}
if (err != AMDUAT_ASL_STORE_OK) {
amduat_asl_artifact_free(&artifact);
return amduatd_http_send_text(fd, 500, "Internal Server Error",
"store error\n", false);
}
if (artifact.bytes.len != 0 && artifact.bytes.data == NULL) {
amduat_asl_artifact_free(&artifact);
return amduatd_http_send_text(fd, 500, "Internal Server Error",
"store error\n", false);
}
if (!amduat_asl_ref_encode_hex(api_contract_ref, &hex)) {
amduat_asl_artifact_free(&artifact);
return amduatd_http_send_text(fd, 500, "Internal Server Error",
"encode error\n", false);
}
{
char hdr[600];
int n = snprintf(hdr,
sizeof(hdr),
"HTTP/1.1 200 OK\r\n"
"Content-Length: %zu\r\n"
"Content-Type: application/json\r\n"
"X-Amduat-Contract-Ref: %s\r\n"
"Connection: close\r\n"
"\r\n",
artifact.bytes.len,
hex);
free(hex);
if (n <= 0 || (size_t)n >= sizeof(hdr)) {
amduat_asl_artifact_free(&artifact);
return false;
}
if (!amduatd_write_all(fd, (const uint8_t *)hdr, (size_t)n)) {
amduat_asl_artifact_free(&artifact);
return false;
}
if (artifact.bytes.len != 0) {
bool ok = amduatd_write_all(fd, artifact.bytes.data, artifact.bytes.len);
amduat_asl_artifact_free(&artifact);
return ok;
}
amduat_asl_artifact_free(&artifact);
return true;
}
}
}
static bool amduatd_seed_api_contract(amduat_asl_store_t *store, static bool amduatd_seed_api_contract(amduat_asl_store_t *store,
const amduat_asl_store_fs_config_t *cfg, const amduat_asl_store_fs_config_t *cfg,
amduat_reference_t *out_ref) { amduat_reference_t *out_ref) {
@ -1518,7 +1584,7 @@ static bool amduatd_handle_conn(int fd,
return amduatd_handle_meta(fd, cfg, api_contract_ref, true); return amduatd_handle_meta(fd, cfg, api_contract_ref, true);
} }
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/contract") == 0) { if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/contract") == 0) {
return amduatd_handle_get_contract(fd, api_contract_ref); return amduatd_handle_get_contract(fd, store, &req, api_contract_ref);
} }
if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/artifacts") == 0) { if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/artifacts") == 0) {