amduat-api/src/amduatd_fed_push_plan.c
2026-01-24 17:10:02 +01:00

646 lines
20 KiB
C

#include "amduatd_fed_push_plan.h"
#include "amduat/asl/log_store.h"
#include "amduat/asl/artifact_io.h"
#include "amduat/asl/ref_text.h"
#include "amduat/asl/store.h"
#include "amduat/enc/fer1_receipt.h"
#include "amduat/enc/tgk1_edge.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *data;
size_t len;
size_t cap;
} amduatd_fed_push_plan_strbuf_t;
static void amduatd_fed_push_plan_strbuf_free(
amduatd_fed_push_plan_strbuf_t *b) {
if (b == NULL) {
return;
}
free(b->data);
b->data = NULL;
b->len = 0;
b->cap = 0;
}
static bool amduatd_fed_push_plan_strbuf_reserve(
amduatd_fed_push_plan_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_fed_push_plan_strbuf_append(
amduatd_fed_push_plan_strbuf_t *b,
const char *s,
size_t n) {
if (b == NULL) {
return false;
}
if (n == 0u) {
return true;
}
if (s == NULL) {
return false;
}
if (!amduatd_fed_push_plan_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_fed_push_plan_strbuf_append_cstr(
amduatd_fed_push_plan_strbuf_t *b,
const char *s) {
return amduatd_fed_push_plan_strbuf_append(
b, s != NULL ? s : "", s != NULL ? strlen(s) : 0u);
}
static const char *amduatd_fed_push_plan_record_type_name(
amduat_fed_record_type_t type) {
switch (type) {
case AMDUAT_FED_REC_ARTIFACT:
return "artifact";
case AMDUAT_FED_REC_PER:
return "per";
case AMDUAT_FED_REC_TGK_EDGE:
return "tgk_edge";
case AMDUAT_FED_REC_TOMBSTONE:
return "tombstone";
default:
return "unknown";
}
}
void amduatd_fed_push_plan_candidate_init(
amduatd_fed_push_cursor_candidate_t *candidate) {
if (candidate == NULL) {
return;
}
memset(candidate, 0, sizeof(*candidate));
candidate->ref = amduat_reference(0u, amduat_octets(NULL, 0u));
}
void amduatd_fed_push_plan_candidate_free(
amduatd_fed_push_cursor_candidate_t *candidate) {
if (candidate == NULL) {
return;
}
if (candidate->has_ref) {
amduat_reference_free(&candidate->ref);
}
memset(candidate, 0, sizeof(*candidate));
}
bool amduatd_fed_push_plan_next_cursor_candidate(
const amduatd_fed_cursor_record_t *cursor,
const amduat_fed_record_t *records,
size_t record_count,
amduatd_fed_push_cursor_candidate_t *out_candidate) {
if (out_candidate == NULL) {
return false;
}
amduatd_fed_push_plan_candidate_init(out_candidate);
if (record_count > 0u && records != NULL) {
const amduat_fed_record_t *last = &records[record_count - 1u];
out_candidate->has_logseq = true;
out_candidate->logseq = last->logseq;
out_candidate->has_ref = true;
if (!amduat_reference_clone(last->id.ref, &out_candidate->ref)) {
amduatd_fed_push_plan_candidate_free(out_candidate);
return false;
}
return true;
}
(void)cursor;
return true;
}
amduatd_fed_push_plan_status_t amduatd_fed_push_plan_check(
const amduatd_fed_cfg_t *cfg,
const amduat_asl_store_t *store) {
if (cfg == NULL || store == NULL) {
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
if (!cfg->enabled) {
return AMDUATD_FED_PUSH_PLAN_ERR_DISABLED;
}
if (store->ops.log_scan == NULL || store->ops.current_state == NULL) {
return AMDUATD_FED_PUSH_PLAN_ERR_UNSUPPORTED;
}
return AMDUATD_FED_PUSH_PLAN_OK;
}
static void amduatd_fed_push_plan_records_free(amduat_fed_record_t *records,
size_t record_count) {
size_t i;
if (records == NULL) {
return;
}
for (i = 0; i < record_count; ++i) {
amduat_reference_free(&records[i].id.ref);
}
free(records);
}
static bool amduatd_fed_push_entry_record_type(
amduat_asl_store_t *store,
const amduat_asl_log_entry_t *entry,
amduat_fed_record_type_t *out_type) {
amduat_fed_record_type_t rec_type = AMDUAT_FED_REC_ARTIFACT;
if (store == NULL || entry == NULL || out_type == NULL) {
return false;
}
if (entry->kind == AMDUATD_FED_LOG_KIND_ARTIFACT) {
amduat_artifact_t artifact;
amduat_asl_store_error_t store_err;
memset(&artifact, 0, sizeof(artifact));
store_err = amduat_asl_store_get(store, entry->payload_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 {
return false;
}
*out_type = rec_type;
return true;
}
void amduatd_fed_push_plan_scan_init(amduatd_fed_push_plan_scan_t *scan) {
if (scan == NULL) {
return;
}
memset(scan, 0, sizeof(*scan));
amduatd_fed_cursor_record_init(&scan->cursor);
scan->cursor_ref = amduat_reference(0u, amduat_octets(NULL, 0u));
}
void amduatd_fed_push_plan_scan_free(amduatd_fed_push_plan_scan_t *scan) {
if (scan == NULL) {
return;
}
if (scan->cursor_present) {
amduatd_fed_cursor_record_free(&scan->cursor);
amduat_reference_free(&scan->cursor_ref);
}
amduatd_fed_push_plan_records_free(scan->records, scan->record_count);
memset(scan, 0, sizeof(*scan));
}
amduatd_fed_push_plan_status_t amduatd_fed_push_plan_scan(
amduat_asl_store_t *store,
amduat_asl_pointer_store_t *pointer_store,
const amduatd_space_t *effective_space,
const char *peer_key,
uint64_t limit,
const char *root_path,
amduatd_fed_push_plan_scan_t *out_scan) {
amduat_asl_log_store_t log_store;
amduat_asl_log_entry_t *entries = NULL;
size_t entry_count = 0u;
uint64_t next_offset = 0u;
bool end = false;
amduat_octets_t log_name = amduat_octets(NULL, 0u);
uint64_t from_logseq = 0u;
if (store == NULL || pointer_store == NULL || peer_key == NULL ||
root_path == NULL || out_scan == NULL) {
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
amduatd_fed_push_plan_scan_init(out_scan);
{
amduatd_fed_cursor_status_t cursor_status;
cursor_status = amduatd_fed_push_cursor_get(store,
pointer_store,
effective_space,
peer_key,
&out_scan->cursor,
&out_scan->cursor_ref);
if (cursor_status == AMDUATD_FED_CURSOR_ERR_NOT_FOUND) {
out_scan->cursor_present = false;
} else if (cursor_status == AMDUATD_FED_CURSOR_OK) {
out_scan->cursor_present = true;
if (out_scan->cursor.has_logseq) {
if (out_scan->cursor.last_logseq == UINT64_MAX) {
amduatd_fed_push_plan_scan_free(out_scan);
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
from_logseq = out_scan->cursor.last_logseq + 1u;
}
} else {
amduatd_fed_push_plan_scan_free(out_scan);
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
}
if (!amduat_asl_log_store_init(&log_store, root_path, store,
pointer_store)) {
amduatd_fed_push_plan_scan_free(out_scan);
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
if (!amduatd_space_scope_name(effective_space, "fed/records", &log_name)) {
amduatd_fed_push_plan_scan_free(out_scan);
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
{
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) {
amduatd_fed_push_plan_scan_free(out_scan);
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
}
(void)next_offset;
(void)end;
if (entry_count != 0u) {
out_scan->records =
(amduat_fed_record_t *)calloc(entry_count, sizeof(*out_scan->records));
if (out_scan->records == NULL) {
amduat_asl_log_entries_free(entries, entry_count);
amduatd_fed_push_plan_scan_free(out_scan);
return AMDUATD_FED_PUSH_PLAN_ERR_OOM;
}
}
{
size_t i;
for (i = 0; i < entry_count; ++i) {
const amduat_asl_log_entry_t *entry = &entries[i];
amduat_fed_record_type_t rec_type = AMDUAT_FED_REC_ARTIFACT;
uint64_t logseq;
if (entry->payload_ref.digest.data == NULL ||
entry->payload_ref.digest.len == 0u) {
continue;
}
if (from_logseq > UINT64_MAX - (uint64_t)i) {
amduat_asl_log_entries_free(entries, entry_count);
amduatd_fed_push_plan_scan_free(out_scan);
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
logseq = from_logseq + (uint64_t)i;
if (!amduatd_fed_push_entry_record_type(store, entry, &rec_type)) {
continue;
}
memset(&out_scan->records[out_scan->record_count], 0,
sizeof(out_scan->records[out_scan->record_count]));
out_scan->records[out_scan->record_count].id.type = rec_type;
out_scan->records[out_scan->record_count].logseq = logseq;
if (!amduat_reference_clone(entry->payload_ref,
&out_scan->records[out_scan->record_count]
.id.ref)) {
amduat_asl_log_entries_free(entries, entry_count);
amduatd_fed_push_plan_scan_free(out_scan);
return AMDUATD_FED_PUSH_PLAN_ERR_OOM;
}
out_scan->record_count++;
}
}
amduat_asl_log_entries_free(entries, entry_count);
return AMDUATD_FED_PUSH_PLAN_OK;
}
amduatd_fed_push_plan_status_t amduatd_fed_push_plan_json(
const amduatd_fed_push_plan_input_t *input,
char **out_json) {
amduatd_fed_push_plan_strbuf_t b;
size_t i;
const amduat_fed_record_t *first = NULL;
const amduat_fed_record_t *last = NULL;
amduatd_fed_push_cursor_candidate_t candidate;
char *ref_hex = NULL;
char *cursor_ref_hex = NULL;
char tmp[64];
if (out_json != NULL) {
*out_json = NULL;
}
if (input == NULL || out_json == NULL || input->peer_key == NULL) {
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
if (input->record_count > 0u && input->records == NULL) {
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
if (input->cursor_present && input->cursor == NULL) {
return AMDUATD_FED_PUSH_PLAN_ERR_INVALID;
}
if (input->record_count > 0u && input->records != NULL) {
first = &input->records[0];
last = &input->records[input->record_count - 1u];
}
if (!amduatd_fed_push_plan_next_cursor_candidate(
input->cursor_present ? input->cursor : NULL,
input->records,
input->record_count,
&candidate)) {
return AMDUATD_FED_PUSH_PLAN_ERR_OOM;
}
if (input->cursor_present &&
input->cursor_ref != NULL &&
input->cursor_ref->digest.data != NULL) {
if (!amduat_asl_ref_encode_hex(*input->cursor_ref, &cursor_ref_hex)) {
amduatd_fed_push_plan_candidate_free(&candidate);
return AMDUATD_FED_PUSH_PLAN_ERR_OOM;
}
}
memset(&b, 0, sizeof(b));
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "{")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"peer\":\"") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, input->peer_key) ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\",")) {
goto plan_oom;
}
snprintf(tmp, sizeof(tmp), "%u", (unsigned int)input->domain_id);
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"domain_id\":") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, tmp) ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, ",")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"effective_space\":{")) {
goto plan_oom;
}
if (input->effective_space != NULL &&
input->effective_space->enabled &&
input->effective_space->space_id.data != NULL) {
const char *space_id = (const char *)input->effective_space->space_id.data;
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"mode\":\"scoped\",") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"space_id\":\"") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, space_id) ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"")) {
goto plan_oom;
}
} else {
if (!amduatd_fed_push_plan_strbuf_append_cstr(
&b, "\"mode\":\"unscoped\",") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"space_id\":null")) {
goto plan_oom;
}
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "},")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"cursor\":{")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"present\":") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b,
input->cursor_present ? "true"
: "false")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, ",\"last_logseq\":")) {
goto plan_oom;
}
if (input->cursor_present && input->cursor != NULL &&
input->cursor->has_logseq) {
snprintf(tmp, sizeof(tmp), "%llu",
(unsigned long long)input->cursor->last_logseq);
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, tmp)) {
goto plan_oom;
}
} else {
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "null")) {
goto plan_oom;
}
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, ",\"ref\":")) {
goto plan_oom;
}
if (cursor_ref_hex != NULL) {
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, cursor_ref_hex) ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"")) {
goto plan_oom;
}
} else {
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "null")) {
goto plan_oom;
}
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "},")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"scan\":{")) {
goto plan_oom;
}
snprintf(tmp, sizeof(tmp), "%zu", input->record_count);
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"record_count\":") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, tmp)) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, ",\"first_logseq\":")) {
goto plan_oom;
}
if (first != NULL) {
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)first->logseq);
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, tmp)) {
goto plan_oom;
}
} else {
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "null")) {
goto plan_oom;
}
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, ",\"last_logseq\":")) {
goto plan_oom;
}
if (last != NULL) {
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)last->logseq);
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, tmp)) {
goto plan_oom;
}
} else {
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "null")) {
goto plan_oom;
}
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "},")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"records\":[")) {
goto plan_oom;
}
for (i = 0; i < input->record_count; ++i) {
const amduat_fed_record_t *rec = &input->records[i];
const char *type_name = amduatd_fed_push_plan_record_type_name(rec->id.type);
if (i > 0) {
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, ",")) {
goto plan_oom;
}
}
if (!amduat_asl_ref_encode_hex(rec->id.ref, &ref_hex)) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "{\"logseq\":")) {
goto plan_oom;
}
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)rec->logseq);
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, tmp) ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, ",\"record_type\":\"") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, type_name) ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\",\"ref\":\"") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, ref_hex) ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"}")) {
goto plan_oom;
}
free(ref_hex);
ref_hex = NULL;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "],")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b,
"\"required_artifacts\":[")) {
goto plan_oom;
}
{
bool first_artifact = true;
for (i = 0; i < input->record_count; ++i) {
const amduat_fed_record_t *rec = &input->records[i];
if (rec->id.type == AMDUAT_FED_REC_TOMBSTONE) {
continue;
}
if (!amduat_asl_ref_encode_hex(rec->id.ref, &ref_hex)) {
goto plan_oom;
}
if (!first_artifact) {
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, ",")) {
goto plan_oom;
}
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, ref_hex) ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"")) {
goto plan_oom;
}
first_artifact = false;
free(ref_hex);
ref_hex = NULL;
}
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "],")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(
&b, "\"next_cursor_candidate\":{")) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"last_logseq\":")) {
goto plan_oom;
}
if (candidate.has_logseq) {
snprintf(tmp, sizeof(tmp), "%llu", (unsigned long long)candidate.logseq);
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, tmp)) {
goto plan_oom;
}
} else {
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "null")) {
goto plan_oom;
}
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, ",\"ref\":")) {
goto plan_oom;
}
if (candidate.has_ref) {
if (!amduat_asl_ref_encode_hex(candidate.ref, &ref_hex)) {
goto plan_oom;
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"") ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, ref_hex) ||
!amduatd_fed_push_plan_strbuf_append_cstr(&b, "\"")) {
goto plan_oom;
}
free(ref_hex);
ref_hex = NULL;
} else {
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "null")) {
goto plan_oom;
}
}
if (!amduatd_fed_push_plan_strbuf_append_cstr(&b, "}}\n")) {
goto plan_oom;
}
amduatd_fed_push_plan_candidate_free(&candidate);
free(cursor_ref_hex);
*out_json = b.data;
return AMDUATD_FED_PUSH_PLAN_OK;
plan_oom:
free(ref_hex);
amduatd_fed_push_plan_candidate_free(&candidate);
free(cursor_ref_hex);
amduatd_fed_push_plan_strbuf_free(&b);
return AMDUATD_FED_PUSH_PLAN_ERR_OOM;
}