From af11665a35ee0e134f90a518624125a60b985649 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sat, 24 Jan 2026 10:35:49 +0100 Subject: [PATCH] amduatd: add opt-in federation config, space scoping, and tests --- CMakeLists.txt | 21 +++- README.md | 24 ++++ federation/transport_unix.c | 59 ++++++++- federation/transport_unix.h | 6 + src/amduatd.c | 134 ++++++++++++++++++-- src/amduatd_fed.c | 236 +++++++++++++++++++++++++++++++++++ src/amduatd_fed.h | 65 ++++++++++ tests/test_amduatd_fed_cfg.c | 121 ++++++++++++++++++ 8 files changed, 649 insertions(+), 17 deletions(-) create mode 100644 src/amduatd_fed.c create mode 100644 src/amduatd_fed.h create mode 100644 tests/test_amduatd_fed_cfg.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c716a6..361f7ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,8 @@ target_link_libraries(amduat_federation set(amduatd_sources src/amduatd.c src/amduatd_http.c src/amduatd_caps.c src/amduatd_space.c src/amduatd_concepts.c - src/amduatd_store.c src/amduatd_derivation_index.c) + src/amduatd_store.c src/amduatd_derivation_index.c + src/amduatd_fed.c) if(AMDUATD_ENABLE_UI) list(APPEND amduatd_sources src/amduatd_ui.c) endif() @@ -117,3 +118,21 @@ target_link_libraries(amduatd_test_derivation_index ) add_test(NAME amduatd_derivation_index COMMAND amduatd_test_derivation_index) + +add_executable(amduatd_test_fed_cfg + tests/test_amduatd_fed_cfg.c + src/amduatd_fed.c + src/amduatd_space.c +) + +target_include_directories(amduatd_test_fed_cfg + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor/amduat/include +) + +target_link_libraries(amduatd_test_fed_cfg + PRIVATE amduat_asl amduat_enc amduat_util amduat_asl_pointer_fs +) + +add_test(NAME amduatd_fed_cfg COMMAND amduatd_test_fed_cfg) diff --git a/README.md b/README.md index 1efeaf4..d8f4835 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,30 @@ Run the daemon with the index-backed store: Note: `/v1/fed/records` requires the index backend. +## Federation (dev) + +Federation is opt-in and disabled by default. Enabling federation requires the +index-backed store and explicit flags: + +```sh +./build/amduatd --root .amduat-asl --sock amduatd.sock --store-backend index \ + --fed-enable --fed-transport unix --fed-unix-sock peer.sock \ + --fed-domain-id 1 --fed-registry-ref +``` + +Flags: + +- `--fed-enable` turns on the coordinator tick loop. +- `--fed-transport stub|unix` selects transport (`stub` by default). +- `--fed-unix-sock PATH` configures the unix transport socket path. +- `--fed-domain-id ID` sets the local domain id. +- `--fed-registry-ref REF` seeds the registry reference (hex ref). +- `--fed-require-space` rejects `/v1/fed/*` requests that do not resolve a space. + +`X-Amduat-Space` is honored for `/v1/fed/*` requests the same way as other +endpoints. If `--space` is configured, unix transport requests will include the +same `X-Amduat-Space` header when contacting peers. + Run the daemon with derivation indexing enabled: ```sh diff --git a/federation/transport_unix.c b/federation/transport_unix.c index 4864a42..0de8579 100644 --- a/federation/transport_unix.c +++ b/federation/transport_unix.c @@ -687,7 +687,9 @@ static int amduat_fed_transport_unix_get_records(void *ctx, amduat_fed_record_t **out_records, size_t *out_len) { amduat_fed_transport_unix_t *transport = (amduat_fed_transport_unix_t *)ctx; - char req[512]; + char req[2048]; + char space_header[AMDUAT_ASL_POINTER_NAME_MAX + 32u]; + const char *space_line = ""; int fd; uint8_t *buf = NULL; size_t buf_len = 0; @@ -703,13 +705,23 @@ static int amduat_fed_transport_unix_get_records(void *ctx, *out_records = NULL; *out_len = 0; + if (transport->has_space) { + snprintf(space_header, + sizeof(space_header), + "X-Amduat-Space: %s\r\n", + transport->space_id); + space_line = space_header; + } + snprintf(req, sizeof(req), "GET /v1/fed/records?domain_id=%u&from_logseq=%llu HTTP/1.1\r\n" "Host: localhost\r\n" + "%s" "Connection: close\r\n" "\r\n", (unsigned int)domain_id, - (unsigned long long)from_logseq); + (unsigned long long)from_logseq, + space_line); fd = amduat_fed_transport_unix_connect(transport->socket_path); if (fd < 0) { @@ -767,7 +779,9 @@ static int amduat_fed_transport_unix_get_artifact(void *ctx, amduat_octets_t *out_bytes) { amduat_fed_transport_unix_t *transport = (amduat_fed_transport_unix_t *)ctx; char *ref_hex = NULL; - char req[512]; + char req[2048]; + char space_header[AMDUAT_ASL_POINTER_NAME_MAX + 32u]; + const char *space_line = ""; int fd; uint8_t *buf = NULL; size_t buf_len = 0; @@ -783,12 +797,21 @@ static int amduat_fed_transport_unix_get_artifact(void *ctx, if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { return -1; } + if (transport->has_space) { + snprintf(space_header, + sizeof(space_header), + "X-Amduat-Space: %s\r\n", + transport->space_id); + space_line = space_header; + } snprintf(req, sizeof(req), "GET /v1/fed/artifacts/%s HTTP/1.1\r\n" "Host: localhost\r\n" + "%s" "Connection: close\r\n" "\r\n", - ref_hex); + ref_hex, + space_line); free(ref_hex); fd = amduat_fed_transport_unix_connect(transport->socket_path); @@ -837,6 +860,34 @@ bool amduat_fed_transport_unix_init(amduat_fed_transport_unix_t *transport, memset(transport->socket_path, 0, sizeof(transport->socket_path)); strncpy(transport->socket_path, socket_path, AMDUAT_FED_TRANSPORT_UNIX_PATH_MAX - 1u); + memset(transport->space_id, 0, sizeof(transport->space_id)); + transport->has_space = false; + return true; +} + +bool amduat_fed_transport_unix_set_space(amduat_fed_transport_unix_t *transport, + const char *space_id) { + size_t len; + size_t i; + if (transport == NULL) { + return false; + } + memset(transport->space_id, 0, sizeof(transport->space_id)); + transport->has_space = false; + if (space_id == NULL || space_id[0] == '\0') { + return true; + } + len = strlen(space_id); + if (len >= sizeof(transport->space_id)) { + return false; + } + for (i = 0; i < len; ++i) { + if (space_id[i] == '\r' || space_id[i] == '\n') { + return false; + } + } + memcpy(transport->space_id, space_id, len); + transport->has_space = true; return true; } diff --git a/federation/transport_unix.h b/federation/transport_unix.h index 6e47782..ee19af7 100644 --- a/federation/transport_unix.h +++ b/federation/transport_unix.h @@ -2,6 +2,7 @@ #define AMDUAT_FED_TRANSPORT_UNIX_H #include "federation/coord.h" +#include "amduat/asl/asl_pointer_fs.h" #ifdef __cplusplus extern "C" { @@ -11,11 +12,16 @@ enum { AMDUAT_FED_TRANSPORT_UNIX_PATH_MAX = 1024 }; typedef struct { char socket_path[AMDUAT_FED_TRANSPORT_UNIX_PATH_MAX]; + char space_id[AMDUAT_ASL_POINTER_NAME_MAX + 1u]; + bool has_space; } amduat_fed_transport_unix_t; bool amduat_fed_transport_unix_init(amduat_fed_transport_unix_t *transport, const char *socket_path); +bool amduat_fed_transport_unix_set_space(amduat_fed_transport_unix_t *transport, + const char *space_id); + amduat_fed_transport_t amduat_fed_transport_unix_ops( amduat_fed_transport_unix_t *transport); diff --git a/src/amduatd.c b/src/amduatd.c index de394eb..1269418 100644 --- a/src/amduatd.c +++ b/src/amduatd.c @@ -26,6 +26,7 @@ #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" @@ -38,6 +39,7 @@ #include "amduatd_ui.h" #include "amduatd_caps.h" #include "amduatd_space.h" +#include "amduatd_fed.h" #include "amduatd_store.h" #include "amduatd_derivation_index.h" @@ -975,8 +977,37 @@ static bool amduatd_parse_u32_str(const char *s, uint32_t *out) { 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_handle_get_fed_records(int fd, amduat_asl_store_t *store, + const amduatd_fed_cfg_t *fed_cfg, const amduatd_http_req_t *req) { char domain_buf[32]; char from_buf[32]; @@ -997,6 +1028,9 @@ static bool amduatd_handle_get_fed_records(int fd, 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)) { @@ -1179,6 +1213,7 @@ fed_records_oom: 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]; @@ -1191,6 +1226,9 @@ static bool amduatd_handle_get_fed_artifact(int fd, 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); @@ -1228,13 +1266,20 @@ static bool amduatd_handle_get_fed_artifact(int fd, 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; - (void)req; + 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) { @@ -3870,6 +3915,7 @@ static bool amduatd_handle_conn(int fd, 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, @@ -3971,12 +4017,12 @@ static bool amduatd_handle_conn(int fd, } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/fed/records") == 0) { - ok = amduatd_handle_get_fed_records(fd, store, &req); + ok = amduatd_handle_get_fed_records(fd, store, fed_cfg, &req); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/fed/status") == 0) { - ok = amduatd_handle_get_fed_status(fd, coord, &req); + ok = amduatd_handle_get_fed_status(fd, coord, fed_cfg, &req); goto conn_cleanup; } @@ -4042,7 +4088,7 @@ static bool amduatd_handle_conn(int fd, } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/fed/artifacts/", 18) == 0) { - ok = amduatd_handle_get_fed_artifact(fd, store, &req, req.path); + ok = amduatd_handle_get_fed_artifact(fd, store, fed_cfg, &req, req.path); goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && @@ -4089,6 +4135,10 @@ static void amduatd_print_usage(FILE *stream) { " [--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" @@ -4116,14 +4166,17 @@ int main(int argc, char **argv) { 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_coord_cfg_t fed_cfg; + 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)); @@ -4131,8 +4184,18 @@ int main(int argc, char **argv) { 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"); @@ -4231,6 +4294,13 @@ int main(int argc, char **argv) { 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; @@ -4268,14 +4338,53 @@ int main(int argc, char **argv) { } #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_cfg, 0, sizeof(fed_cfg)); - fed_cfg.local_domain_id = 0; - fed_cfg.authoritative_store = &store; - fed_cfg.cache_store = NULL; - fed_cfg.transport = amduat_fed_transport_stub_ops(&fed_stub); - if (amduat_fed_coord_open(&fed_cfg, &fed_coord) != AMDUAT_FED_COORD_OK) { - fed_coord = NULL; + 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); @@ -4378,6 +4487,7 @@ int main(int argc, char **argv) { ui_ref, &concepts, &dcfg, + &fed_cfg, fed_coord, &allowlist, &caps, diff --git a/src/amduatd_fed.c b/src/amduatd_fed.c new file mode 100644 index 0000000..908e72e --- /dev/null +++ b/src/amduatd_fed.c @@ -0,0 +1,236 @@ +#include "amduatd_fed.h" + +#include "amduat/asl/ref_text.h" + +#include +#include +#include + +static bool amduatd_fed_parse_u32(const char *s, uint32_t *out) { + unsigned long val; + char *endp = NULL; + if (s == NULL || out == NULL) { + return false; + } + errno = 0; + val = strtoul(s, &endp, 10); + if (errno != 0 || endp == s || *endp != '\0' || val > UINT32_MAX) { + return false; + } + *out = (uint32_t)val; + return true; +} + +void amduatd_fed_cfg_init(amduatd_fed_cfg_t *cfg) { + if (cfg == NULL) { + return; + } + memset(cfg, 0, sizeof(*cfg)); + cfg->transport_kind = AMDUATD_FED_TRANSPORT_STUB; + cfg->registry_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); +} + +void amduatd_fed_cfg_free(amduatd_fed_cfg_t *cfg) { + if (cfg == NULL) { + return; + } + if (cfg->registry_ref_set) { + amduat_reference_free(&cfg->registry_ref); + cfg->registry_ref_set = false; + } + amduatd_fed_cfg_init(cfg); +} + +const char *amduatd_fed_transport_name(amduatd_fed_transport_kind_t kind) { + switch (kind) { + case AMDUATD_FED_TRANSPORT_STUB: + return "stub"; + case AMDUATD_FED_TRANSPORT_UNIX: + return "unix"; + default: + return "unknown"; + } +} + +amduatd_fed_parse_result_t amduatd_fed_cfg_parse_arg(amduatd_fed_cfg_t *cfg, + int argc, + char **argv, + int *io_index, + const char **out_err) { + const char *arg = NULL; + const char *value = NULL; + + if (out_err != NULL) { + *out_err = NULL; + } + if (cfg == NULL || argv == NULL || io_index == NULL || argc <= 0) { + if (out_err != NULL) { + *out_err = "invalid fed config parse inputs"; + } + return AMDUATD_FED_PARSE_ERROR; + } + if (*io_index < 0 || *io_index >= argc) { + if (out_err != NULL) { + *out_err = "fed config parse index out of range"; + } + return AMDUATD_FED_PARSE_ERROR; + } + + arg = argv[*io_index]; + if (strcmp(arg, "--fed-enable") == 0) { + cfg->enabled = true; + return AMDUATD_FED_PARSE_OK; + } + if (strcmp(arg, "--fed-require-space") == 0) { + cfg->require_space = true; + return AMDUATD_FED_PARSE_OK; + } + if (strcmp(arg, "--fed-transport") == 0) { + if (*io_index + 1 >= argc) { + if (out_err != NULL) { + *out_err = "--fed-transport requires a value"; + } + return AMDUATD_FED_PARSE_ERROR; + } + value = argv[++(*io_index)]; + if (strcmp(value, "stub") == 0) { + cfg->transport_kind = AMDUATD_FED_TRANSPORT_STUB; + return AMDUATD_FED_PARSE_OK; + } + if (strcmp(value, "unix") == 0) { + cfg->transport_kind = AMDUATD_FED_TRANSPORT_UNIX; + return AMDUATD_FED_PARSE_OK; + } + if (out_err != NULL) { + *out_err = "invalid --fed-transport"; + } + return AMDUATD_FED_PARSE_ERROR; + } + if (strcmp(arg, "--fed-unix-sock") == 0) { + size_t len; + if (*io_index + 1 >= argc) { + if (out_err != NULL) { + *out_err = "--fed-unix-sock requires a path"; + } + return AMDUATD_FED_PARSE_ERROR; + } + value = argv[++(*io_index)]; + len = strlen(value); + if (len == 0 || len >= sizeof(cfg->unix_socket_path)) { + if (out_err != NULL) { + *out_err = "invalid --fed-unix-sock"; + } + return AMDUATD_FED_PARSE_ERROR; + } + memset(cfg->unix_socket_path, 0, sizeof(cfg->unix_socket_path)); + memcpy(cfg->unix_socket_path, value, len); + cfg->unix_socket_set = true; + return AMDUATD_FED_PARSE_OK; + } + if (strcmp(arg, "--fed-domain-id") == 0) { + uint32_t domain_id = 0; + if (*io_index + 1 >= argc) { + if (out_err != NULL) { + *out_err = "--fed-domain-id requires a value"; + } + return AMDUATD_FED_PARSE_ERROR; + } + value = argv[++(*io_index)]; + if (!amduatd_fed_parse_u32(value, &domain_id)) { + if (out_err != NULL) { + *out_err = "invalid --fed-domain-id"; + } + return AMDUATD_FED_PARSE_ERROR; + } + cfg->local_domain_id = domain_id; + return AMDUATD_FED_PARSE_OK; + } + if (strcmp(arg, "--fed-registry-ref") == 0) { + amduat_reference_t ref; + if (*io_index + 1 >= argc) { + if (out_err != NULL) { + *out_err = "--fed-registry-ref requires a value"; + } + return AMDUATD_FED_PARSE_ERROR; + } + value = argv[++(*io_index)]; + memset(&ref, 0, sizeof(ref)); + if (!amduat_asl_ref_decode_hex(value, &ref)) { + if (out_err != NULL) { + *out_err = "invalid --fed-registry-ref"; + } + return AMDUATD_FED_PARSE_ERROR; + } + if (cfg->registry_ref_set) { + amduat_reference_free(&cfg->registry_ref); + } + cfg->registry_ref = ref; + cfg->registry_ref_set = true; + return AMDUATD_FED_PARSE_OK; + } + + return AMDUATD_FED_PARSE_NOT_HANDLED; +} + +bool amduatd_fed_requirements_check(amduatd_store_backend_t backend, + const amduatd_fed_cfg_t *cfg, + const char **out_err) { + if (out_err != NULL) { + *out_err = NULL; + } + if (cfg == NULL) { + if (out_err != NULL) { + *out_err = "missing fed config"; + } + return false; + } + if (!cfg->enabled) { + return true; + } + if (backend != AMDUATD_STORE_BACKEND_INDEX) { + if (out_err != NULL) { + *out_err = "federation requires --store-backend index"; + } + return false; + } + if (cfg->transport_kind == AMDUATD_FED_TRANSPORT_UNIX && + !cfg->unix_socket_set) { + if (out_err != NULL) { + *out_err = "unix transport requires --fed-unix-sock"; + } + return false; + } + return true; +} + +bool amduatd_fed_scope_names(const amduatd_fed_cfg_t *cfg, + const amduatd_space_t *space, + const char *name, + amduat_octets_t *out_scoped, + const char **out_err) { + if (out_err != NULL) { + *out_err = NULL; + } + if (out_scoped != NULL) { + *out_scoped = amduat_octets(NULL, 0u); + } + if (cfg == NULL || name == NULL || out_scoped == NULL) { + if (out_err != NULL) { + *out_err = "invalid fed scope inputs"; + } + return false; + } + if (cfg->require_space && (space == NULL || !space->enabled)) { + if (out_err != NULL) { + *out_err = "missing X-Amduat-Space"; + } + return false; + } + if (!amduatd_space_scope_name(space, name, out_scoped)) { + if (out_err != NULL) { + *out_err = "failed to scope name"; + } + return false; + } + return true; +} diff --git a/src/amduatd_fed.h b/src/amduatd_fed.h new file mode 100644 index 0000000..7ef3ded --- /dev/null +++ b/src/amduatd_fed.h @@ -0,0 +1,65 @@ +#ifndef AMDUATD_FED_H +#define AMDUATD_FED_H + +#include "amduatd_space.h" +#include "amduatd_store.h" +#include "federation/transport_unix.h" + +#include "amduat/asl/core.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + AMDUATD_FED_TRANSPORT_STUB = 0, + AMDUATD_FED_TRANSPORT_UNIX = 1 +} amduatd_fed_transport_kind_t; + +typedef struct { + bool enabled; + bool require_space; + amduatd_fed_transport_kind_t transport_kind; + uint32_t local_domain_id; + bool registry_ref_set; + amduat_reference_t registry_ref; + bool unix_socket_set; + char unix_socket_path[AMDUAT_FED_TRANSPORT_UNIX_PATH_MAX]; +} amduatd_fed_cfg_t; + +typedef enum { + AMDUATD_FED_PARSE_OK = 0, + AMDUATD_FED_PARSE_NOT_HANDLED = 1, + AMDUATD_FED_PARSE_ERROR = 2 +} amduatd_fed_parse_result_t; + +void amduatd_fed_cfg_init(amduatd_fed_cfg_t *cfg); + +void amduatd_fed_cfg_free(amduatd_fed_cfg_t *cfg); + +const char *amduatd_fed_transport_name(amduatd_fed_transport_kind_t kind); + +amduatd_fed_parse_result_t amduatd_fed_cfg_parse_arg(amduatd_fed_cfg_t *cfg, + int argc, + char **argv, + int *io_index, + const char **out_err); + +bool amduatd_fed_requirements_check(amduatd_store_backend_t backend, + const amduatd_fed_cfg_t *cfg, + const char **out_err); + +bool amduatd_fed_scope_names(const amduatd_fed_cfg_t *cfg, + const amduatd_space_t *space, + const char *name, + amduat_octets_t *out_scoped, + const char **out_err); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUATD_FED_H */ diff --git a/tests/test_amduatd_fed_cfg.c b/tests/test_amduatd_fed_cfg.c new file mode 100644 index 0000000..0431f77 --- /dev/null +++ b/tests/test_amduatd_fed_cfg.c @@ -0,0 +1,121 @@ +#include "amduatd_fed.h" + +#include "amduat/asl/ref_text.h" +#include "amduat/hash/asl1.h" + +#include +#include +#include +#include + +static int failures = 0; + +static void expect(bool cond, const char *msg) { + if (!cond) { + fprintf(stderr, "FAIL: %s\n", msg); + failures++; + } +} + +int main(void) { + amduatd_fed_cfg_t cfg; + amduatd_space_t space; + amduat_octets_t scoped = amduat_octets(NULL, 0u); + uint8_t digest_bytes[32]; + amduat_octets_t digest; + amduat_reference_t ref; + char *ref_hex = NULL; + const char *err = NULL; + int i; + + memset(digest_bytes, 0x2a, sizeof(digest_bytes)); + if (!amduat_octets_clone(amduat_octets(digest_bytes, sizeof(digest_bytes)), + &digest)) { + fprintf(stderr, "FAIL: digest clone\n"); + return 1; + } + + ref = amduat_reference(AMDUAT_HASH_ASL1_ID_SHA256, digest); + if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { + fprintf(stderr, "FAIL: ref encode\n"); + amduat_reference_free(&ref); + return 1; + } + + amduatd_fed_cfg_init(&cfg); + expect(!cfg.enabled, "default disabled"); + expect(cfg.transport_kind == AMDUATD_FED_TRANSPORT_STUB, "default transport"); + expect(!cfg.require_space, "default require_space"); + + { + char *argv[] = { + "amduatd", + "--fed-enable", + "--fed-transport", + "unix", + "--fed-unix-sock", + "amduatd.sock", + "--fed-domain-id", + "42", + "--fed-registry-ref", + ref_hex, + "--fed-require-space", + }; + int argc = (int)(sizeof(argv) / sizeof(argv[0])); + for (i = 1; i < argc; ++i) { + amduatd_fed_parse_result_t rc; + rc = amduatd_fed_cfg_parse_arg(&cfg, argc, argv, &i, &err); + expect(rc == AMDUATD_FED_PARSE_OK, "fed parse ok"); + } + } + + expect(cfg.enabled, "parsed enabled"); + expect(cfg.transport_kind == AMDUATD_FED_TRANSPORT_UNIX, + "parsed transport"); + expect(cfg.unix_socket_set, "parsed unix socket"); + expect(cfg.local_domain_id == 42u, "parsed domain id"); + expect(cfg.registry_ref_set, "parsed registry ref"); + expect(cfg.require_space, "parsed require space"); + + expect(!amduatd_fed_requirements_check(AMDUATD_STORE_BACKEND_FS, + &cfg, + &err), + "requirements reject fs backend"); + expect(amduatd_fed_requirements_check(AMDUATD_STORE_BACKEND_INDEX, + &cfg, + &err), + "requirements accept index backend"); + + if (!amduatd_space_init(&space, NULL, false)) { + fprintf(stderr, "FAIL: space init\n"); + amduatd_fed_cfg_free(&cfg); + amduat_reference_free(&ref); + free(ref_hex); + return 1; + } + expect(!amduatd_fed_scope_names(&cfg, &space, "fed", &scoped, &err), + "scope requires space"); + amduat_octets_free(&scoped); + + if (!amduatd_space_init(&space, "alpha", false)) { + fprintf(stderr, "FAIL: space init alpha\n"); + amduatd_fed_cfg_free(&cfg); + amduat_reference_free(&ref); + free(ref_hex); + return 1; + } + expect(amduatd_fed_scope_names(&cfg, &space, "fed", &scoped, &err), + "scope with space"); + expect(scoped.data != NULL && + scoped.len == strlen("space/alpha/fed") && + memcmp(scoped.data, + "space/alpha/fed", + scoped.len) == 0, + "scoped name"); + amduat_octets_free(&scoped); + + amduatd_fed_cfg_free(&cfg); + amduat_reference_free(&ref); + free(ref_hex); + return failures == 0 ? 0 : 1; +}