2026-01-18 10:56:52 +01:00
|
|
|
#include "amduat/fed/replay.h"
|
|
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
static int amduat_fed_octets_cmp(amduat_octets_t a, amduat_octets_t b) {
|
|
|
|
|
size_t min_len;
|
|
|
|
|
int cmp;
|
|
|
|
|
|
|
|
|
|
min_len = a.len < b.len ? a.len : b.len;
|
|
|
|
|
if (min_len > 0) {
|
|
|
|
|
cmp = memcmp(a.data, b.data, min_len);
|
|
|
|
|
if (cmp != 0) {
|
|
|
|
|
return cmp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (a.len < b.len) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (a.len > b.len) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int amduat_fed_record_id_cmp(const amduat_fed_record_id_t *a,
|
|
|
|
|
const amduat_fed_record_id_t *b) {
|
|
|
|
|
if (a->type != b->type) {
|
|
|
|
|
return (a->type < b->type) ? -1 : 1;
|
|
|
|
|
}
|
|
|
|
|
if (a->ref.hash_id != b->ref.hash_id) {
|
|
|
|
|
return (a->ref.hash_id < b->ref.hash_id) ? -1 : 1;
|
|
|
|
|
}
|
|
|
|
|
return amduat_fed_octets_cmp(a->ref.digest, b->ref.digest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int amduat_fed_record_cmp(const void *lhs, const void *rhs) {
|
|
|
|
|
const amduat_fed_record_t *a = (const amduat_fed_record_t *)lhs;
|
|
|
|
|
const amduat_fed_record_t *b = (const amduat_fed_record_t *)rhs;
|
|
|
|
|
|
|
|
|
|
if (a->logseq != b->logseq) {
|
|
|
|
|
return (a->logseq < b->logseq) ? -1 : 1;
|
|
|
|
|
}
|
|
|
|
|
return amduat_fed_record_id_cmp(&a->id, &b->id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool amduat_fed_record_clone(const amduat_fed_record_t *src,
|
|
|
|
|
amduat_fed_record_t *out) {
|
|
|
|
|
if (src == NULL || out == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
*out = *src;
|
|
|
|
|
if (!amduat_reference_clone(src->id.ref, &out->id.ref)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-01-18 11:25:39 +01:00
|
|
|
if (!amduat_reference_clone(src->location.ref, &out->location.ref)) {
|
|
|
|
|
amduat_reference_free(&out->id.ref);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-01-18 10:56:52 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void amduat_fed_record_free(amduat_fed_record_t *record) {
|
|
|
|
|
if (record == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
amduat_reference_free(&record->id.ref);
|
2026-01-18 11:25:39 +01:00
|
|
|
amduat_reference_free(&record->location.ref);
|
2026-01-18 10:56:52 +01:00
|
|
|
memset(record, 0, sizeof(*record));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool amduat_fed_bounds_ok(const amduat_fed_record_t *record,
|
|
|
|
|
uint64_t snapshot_id,
|
|
|
|
|
uint64_t log_prefix) {
|
|
|
|
|
if (record->snapshot_id < snapshot_id) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (record->snapshot_id > snapshot_id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return record->logseq <= log_prefix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool amduat_fed_ref_eq(amduat_reference_t a, amduat_reference_t b) {
|
|
|
|
|
return amduat_reference_eq(a, b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool amduat_fed_tombstone_matches(const amduat_fed_record_t *tombstone,
|
|
|
|
|
const amduat_fed_record_t *record) {
|
|
|
|
|
if (tombstone->id.type != AMDUAT_FED_REC_TOMBSTONE) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (record->id.type == AMDUAT_FED_REC_TOMBSTONE) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return amduat_fed_ref_eq(tombstone->id.ref, record->id.ref);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool amduat_fed_tombstone_list_has(
|
|
|
|
|
const amduat_reference_t *refs,
|
|
|
|
|
size_t refs_len,
|
|
|
|
|
amduat_reference_t candidate) {
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < refs_len; ++i) {
|
|
|
|
|
if (amduat_fed_ref_eq(refs[i], candidate)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool amduat_fed_tombstone_list_push(amduat_reference_t **refs,
|
|
|
|
|
size_t *len,
|
|
|
|
|
size_t *cap,
|
|
|
|
|
amduat_reference_t ref) {
|
|
|
|
|
amduat_reference_t *next;
|
|
|
|
|
|
|
|
|
|
if (*len == *cap) {
|
|
|
|
|
size_t next_cap = (*cap == 0u) ? 4u : (*cap * 2u);
|
|
|
|
|
next = (amduat_reference_t *)realloc(*refs,
|
|
|
|
|
next_cap * sizeof(*next));
|
|
|
|
|
if (next == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
*refs = next;
|
|
|
|
|
*cap = next_cap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!amduat_reference_clone(ref, &(*refs)[*len])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
(*len)++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool amduat_fed_record_validate(const amduat_fed_record_t *record) {
|
|
|
|
|
if (record == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (record->meta.visibility > 1u) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (record->meta.has_source > 1u) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (record->meta.has_source == 0u && record->meta.source_domain != 0u) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (record->id.type < AMDUAT_FED_REC_ARTIFACT ||
|
|
|
|
|
record->id.type > AMDUAT_FED_REC_TOMBSTONE) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (record->id.ref.digest.len != 0u &&
|
|
|
|
|
record->id.ref.digest.data == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-01-18 11:25:39 +01:00
|
|
|
if (record->location.ref.digest.len != 0u &&
|
|
|
|
|
record->location.ref.digest.data == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-01-18 10:56:52 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool amduat_fed_replay_domain(const amduat_fed_record_t *records,
|
|
|
|
|
size_t count,
|
|
|
|
|
uint32_t domain_id,
|
|
|
|
|
uint64_t snapshot_id,
|
|
|
|
|
uint64_t log_prefix,
|
|
|
|
|
amduat_fed_replay_view_t *out_view) {
|
|
|
|
|
amduat_fed_record_t *scratch;
|
|
|
|
|
amduat_reference_t *tombstones;
|
|
|
|
|
size_t tombstones_len;
|
|
|
|
|
size_t tombstones_cap;
|
|
|
|
|
size_t scratch_len;
|
|
|
|
|
size_t i;
|
|
|
|
|
size_t out_len;
|
|
|
|
|
bool ok;
|
|
|
|
|
|
|
|
|
|
if (out_view == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
out_view->records = NULL;
|
|
|
|
|
out_view->len = 0;
|
|
|
|
|
|
|
|
|
|
if (records == NULL && count != 0u) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count == 0u) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scratch = (amduat_fed_record_t *)calloc(count, sizeof(*scratch));
|
|
|
|
|
if (scratch == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
tombstones = NULL;
|
|
|
|
|
tombstones_len = 0;
|
|
|
|
|
tombstones_cap = 0;
|
|
|
|
|
|
|
|
|
|
scratch_len = 0;
|
|
|
|
|
for (i = 0; i < count; ++i) {
|
|
|
|
|
const amduat_fed_record_t *record = &records[i];
|
|
|
|
|
|
|
|
|
|
if (record->meta.domain_id != domain_id) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!amduat_fed_record_validate(record)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!amduat_fed_bounds_ok(record, snapshot_id, log_prefix)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!amduat_fed_record_clone(record, &scratch[scratch_len])) {
|
|
|
|
|
while (scratch_len > 0) {
|
|
|
|
|
scratch_len--;
|
|
|
|
|
amduat_fed_record_free(&scratch[scratch_len]);
|
|
|
|
|
}
|
|
|
|
|
free(scratch);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
scratch_len++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qsort(scratch, scratch_len, sizeof(*scratch), amduat_fed_record_cmp);
|
|
|
|
|
|
|
|
|
|
ok = true;
|
|
|
|
|
out_len = 0;
|
|
|
|
|
for (i = 0; i < scratch_len; ++i) {
|
|
|
|
|
amduat_fed_record_t current = scratch[i];
|
|
|
|
|
size_t j;
|
|
|
|
|
bool tombstoned;
|
|
|
|
|
|
|
|
|
|
scratch[i].id.ref.digest.data = NULL;
|
|
|
|
|
scratch[i].id.ref.digest.len = 0u;
|
|
|
|
|
|
|
|
|
|
if (current.id.type == AMDUAT_FED_REC_TOMBSTONE) {
|
|
|
|
|
for (j = 0; j < out_len; ++j) {
|
|
|
|
|
if (amduat_fed_tombstone_matches(¤t, &scratch[j])) {
|
|
|
|
|
amduat_fed_record_free(&scratch[j]);
|
|
|
|
|
memmove(&scratch[j],
|
|
|
|
|
&scratch[j + 1],
|
|
|
|
|
(out_len - j - 1) * sizeof(*scratch));
|
|
|
|
|
out_len--;
|
|
|
|
|
j--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!amduat_fed_tombstone_list_push(&tombstones,
|
|
|
|
|
&tombstones_len,
|
|
|
|
|
&tombstones_cap,
|
|
|
|
|
current.id.ref)) {
|
|
|
|
|
amduat_fed_record_free(¤t);
|
|
|
|
|
ok = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
amduat_fed_record_free(¤t);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tombstoned = amduat_fed_tombstone_list_has(tombstones,
|
|
|
|
|
tombstones_len,
|
|
|
|
|
current.id.ref);
|
|
|
|
|
if (tombstoned) {
|
|
|
|
|
amduat_fed_record_free(¤t);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scratch[out_len++] = current;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < tombstones_len; ++i) {
|
|
|
|
|
amduat_reference_free(&tombstones[i]);
|
|
|
|
|
}
|
|
|
|
|
free(tombstones);
|
|
|
|
|
|
|
|
|
|
if (!ok) {
|
|
|
|
|
for (i = 0; i < scratch_len; ++i) {
|
|
|
|
|
amduat_fed_record_free(&scratch[i]);
|
|
|
|
|
}
|
|
|
|
|
free(scratch);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out_view->records = scratch;
|
|
|
|
|
out_view->len = out_len;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void amduat_fed_replay_view_free(amduat_fed_replay_view_t *view) {
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
|
|
if (view == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (view->records != NULL) {
|
|
|
|
|
for (i = 0; i < view->len; ++i) {
|
|
|
|
|
amduat_fed_record_free(&view->records[i]);
|
|
|
|
|
}
|
|
|
|
|
free(view->records);
|
|
|
|
|
}
|
|
|
|
|
view->records = NULL;
|
|
|
|
|
view->len = 0;
|
|
|
|
|
}
|