Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/ewanc26/wolfram. Primarily C AT Protocol SDK: client-side, wire-level implementation (XRPC, OAuth/DPoP, identity, repos/MST/CAR, firehose/Jetstream, moderation, CLI, generated clients). Not a port of the PDS/AppView/Ozone backends.
ansi-c api-client at-protocol atproto bluesky c c-sdk decentralized decentralized-social embedded lexicon libwolfram memory-management oauth2 posix sdk shared-library static-library systems-programming xrpc
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723#include "wolfram/repo/diff.h"#include "wolfram/repo/cbor.h"#include "wolfram/repo/cid.h"#include "wolfram/repo/car.h"#include "wolfram/repo/commit.h"#include "wolfram/repo/mst.h"#include "cbor_build.h"#include "cbor_map_find.h"
#include <stdlib.h>#include <string.h>
typedef struct repo_leaf { unsigned char *key; size_t key_len; wf_cid cid;} repo_leaf;
typedef struct repo_leaf_list { repo_leaf *items; size_t count;} repo_leaf_list;
typedef struct repo_cid_list { wf_cid *items; size_t count;} repo_cid_list;
/* Open-addressing set of CIDs, used to detect duplicate blocks in a CAR. * Rejecting them by scanning the array for each block is O(N^2); a set makes * validation linear. Capacity is a power of two sized to keep the load factor * at or below 0.5, so a slot is always found within cap probes. An empty slot * is one with len == 0, which no valid CID has. */typedef struct repo_cid_set { wf_cid *slots; size_t cap;} repo_cid_set;
static wf_status repo_cid_set_init(repo_cid_set *set, size_t count) { set->slots = NULL; set->cap = 0; if (count == 0) return WF_OK; if (count > SIZE_MAX / 2 / sizeof(wf_cid)) return WF_ERR_ALLOC; size_t cap = 8; while (cap < count * 2) { if (cap > SIZE_MAX / 2) return WF_ERR_ALLOC; cap *= 2; } set->slots = calloc(cap, sizeof(wf_cid)); if (!set->slots) return WF_ERR_ALLOC; set->cap = cap; return WF_OK;}
static size_t repo_cid_hash(const wf_cid *cid) { size_t h = 14695981039346656037ull; for (size_t i = 0; i < cid->len; i++) { h ^= cid->bytes[i]; h *= 1099511628211ull; } return h;}
/* Insert `cid` into the set. On return, `*was_dup` is 1 if the CID was already * present (in which case nothing is inserted). Fails only on allocation. */static wf_status repo_cid_set_insert(repo_cid_set *set, const wf_cid *cid, int *was_dup) { *was_dup = 0; if (!set || !cid || cid->len == 0 || set->cap == 0) return WF_ERR_INVALID_ARG; size_t mask = set->cap - 1; size_t slot = repo_cid_hash(cid) & mask; for (size_t probes = 0; probes < set->cap; probes++) { if (set->slots[slot].len == 0) { set->slots[slot] = *cid; return WF_OK; } if (cid_equal(&set->slots[slot], cid)) { *was_dup = 1; return WF_OK; } slot = (slot + 1) & mask; } return WF_ERR_ALLOC;}
static int repo_key_cmp(const unsigned char *a, size_t a_len, const unsigned char *b, size_t b_len) { size_t min = a_len < b_len ? a_len : b_len; int cmp = memcmp(a, b, min); if (cmp != 0) return cmp; return (int)(a_len - b_len);}
static void repo_leaf_list_free(repo_leaf_list *list) { if (!list) return; for (size_t i = 0; i < list->count; i++) free(list->items[i].key); free(list->items); memset(list, 0, sizeof(*list));}
static int repo_cid_list_has(const repo_cid_list *list, const wf_cid *cid) { for (size_t i = 0; list && i < list->count; i++) if (cid_equal(&list->items[i], cid)) return 1; return 0;}
static wf_status repo_cid_list_add(repo_cid_list *list, const wf_cid *cid) { if (repo_cid_list_has(list, cid)) return WF_OK; wf_cid *items = realloc(list->items, (list->count + 1) * sizeof(*items)); if (!items) return WF_ERR_ALLOC; list->items = items; list->items[list->count++] = *cid; return WF_OK;}
static wf_status repo_leaf_add(repo_leaf_list *list, const unsigned char *key, size_t key_len, const wf_cid *cid) { if (key_len == 0 || !cid || cid->len != 36) return WF_ERR_PARSE; if (list->count > 0) { repo_leaf *previous = &list->items[list->count - 1]; if (repo_key_cmp(previous->key, previous->key_len, key, key_len) >= 0) return WF_ERR_PARSE; } repo_leaf *items = realloc(list->items, (list->count + 1) * sizeof(*items)); if (!items) return WF_ERR_ALLOC; list->items = items; repo_leaf *leaf = &list->items[list->count]; memset(leaf, 0, sizeof(*leaf)); leaf->key = malloc(key_len); if (!leaf->key) return WF_ERR_ALLOC; memcpy(leaf->key, key, key_len); leaf->key_len = key_len; leaf->cid = *cid; list->count++; return WF_OK;}
static wf_status repo_collect_mst(const wf_car *car, const wf_cid *root, repo_leaf_list *leaves, repo_cid_list *nodes, size_t depth) { if (!car || !root || root->len != 36 || depth > WF_MST_MAX_DEPTH || depth > car->block_count) return WF_ERR_PARSE; if (repo_cid_list_has(nodes, root)) return WF_ERR_PARSE; wf_status status = repo_cid_list_add(nodes, root); if (status != WF_OK) return status; wf_car_block *block = wf_car_find_block((wf_car *)car, root); if (!block) return WF_ERR_NOT_FOUND; wf_mst_node node = {0}; status = wf_mst_node_parse(block->data, block->data_len, root, &node); if (status != WF_OK) return status; if (node.left.len) status = repo_collect_mst(car, &node.left, leaves, nodes, depth + 1); for (size_t i = 0; status == WF_OK && i < node.count; i++) { if (!wf_car_find_block((wf_car *)car, &node.entries[i].value)) { status = WF_ERR_NOT_FOUND; break; } status = repo_leaf_add(leaves, node.entries[i].key, node.entries[i].key_len, &node.entries[i].value); if (status == WF_OK && node.entries[i].subtree.len) status = repo_collect_mst(car, &node.entries[i].subtree, leaves, nodes, depth + 1); } wf_mst_node_free(&node); return status;}
static wf_status repo_car_validate_blocks(const wf_car *car) { if (!car) return WF_ERR_INVALID_ARG; repo_cid_set set = {0}; wf_status s = repo_cid_set_init(&set, car->block_count); if (s != WF_OK) return s; for (size_t i = 0; i < car->block_count; i++) { wf_cid computed = {0}; if (!car->blocks[i].data || car->blocks[i].data_len == 0 || wf_cid_of_block(car->blocks[i].data, car->blocks[i].data_len, &computed) != WF_OK || !cid_equal(&computed, &car->blocks[i].cid)) { s = WF_ERR_PARSE; break; } int dup = 0; s = repo_cid_set_insert(&set, &car->blocks[i].cid, &dup); if (s != WF_OK) break; if (dup) { s = WF_ERR_PARSE; break; } } free(set.slots); return s;}
static wf_status repo_car_add_copy(wf_car *car, const wf_car_block *block) { wf_car_block *existing = wf_car_find_block(car, &block->cid); if (existing) { return existing->data_len == block->data_len && memcmp(existing->data, block->data, block->data_len) == 0 ? WF_OK : WF_ERR_PARSE; } wf_car_block *blocks = realloc(car->blocks, (car->block_count + 1) * sizeof(*blocks)); if (!blocks) return WF_ERR_ALLOC; car->blocks = blocks; wf_car_block *copy = &car->blocks[car->block_count]; memset(copy, 0, sizeof(*copy)); copy->data = malloc(block->data_len); if (!copy->data) return WF_ERR_ALLOC; memcpy(copy->data, block->data, block->data_len); copy->data_len = block->data_len; copy->cid = block->cid; car->block_count++; return WF_OK;}
static wf_status repo_car_add_cid_copy(wf_car *destination, const wf_car *source, const wf_cid *cid) { wf_car_block *block = wf_car_find_block((wf_car *)source, cid); return block ? repo_car_add_copy(destination, block) : WF_ERR_NOT_FOUND;}
static wf_status repo_composite_car(const wf_car *base, const wf_car *update, wf_car *out) { memset(out, 0, sizeof(*out)); out->roots = update->roots; out->root_count = update->root_count; size_t capacity = base->block_count + update->block_count; out->blocks = capacity ? calloc(capacity, sizeof(*out->blocks)) : NULL; if (capacity && !out->blocks) return WF_ERR_ALLOC; for (size_t pass = 0; pass < 2; pass++) { const wf_car *source = pass == 0 ? update : base; for (size_t i = 0; i < source->block_count; i++) { wf_car_block *present = wf_car_find_block(out, &source->blocks[i].cid); if (present) { if (present->data_len != source->blocks[i].data_len || memcmp(present->data, source->blocks[i].data, present->data_len) != 0) { free(out->blocks); memset(out, 0, sizeof(*out)); return WF_ERR_PARSE; } continue; } out->blocks[out->block_count++] = source->blocks[i]; } } return WF_OK;}
static char *repo_string_copy(const char *value) { size_t len = strlen(value); char *copy = malloc(len + 1); if (!copy) return NULL; memcpy(copy, value, len + 1); return copy;}
void wf_repo_operations_free(wf_repo_operation *operations, size_t count) { if (!operations) return; for (size_t i = 0; i < count; i++) { free(operations[i].collection); free(operations[i].rkey); } free(operations);}
wf_status wf_repo_operations_invert(const wf_repo_operation *operations, size_t count, wf_repo_operation **out_operations) { if ((!operations && count > 0) || !out_operations) return WF_ERR_INVALID_ARG; *out_operations = NULL; if (count == 0) return WF_OK; wf_repo_operation *inverted = calloc(count, sizeof(*inverted)); if (!inverted) return WF_ERR_ALLOC; for (size_t i = 0; i < count; i++) { const wf_repo_operation *source = &operations[count - i - 1]; wf_repo_operation *target = &inverted[i]; if (!source->collection || !source->collection[0] || !source->rkey || !source->rkey[0] || source->cid.len != 36 || (source->action == WF_REPO_UPDATE && source->prev.len != 36) || source->action < WF_REPO_CREATE || source->action > WF_REPO_DELETE) { wf_repo_operations_free(inverted, count); return WF_ERR_INVALID_ARG; } target->collection = repo_string_copy(source->collection); target->rkey = repo_string_copy(source->rkey); if (!target->collection || !target->rkey) { wf_repo_operations_free(inverted, count); return WF_ERR_ALLOC; } if (source->action == WF_REPO_CREATE) { target->action = WF_REPO_DELETE; target->cid = source->cid; } else if (source->action == WF_REPO_DELETE) { target->action = WF_REPO_CREATE; target->cid = source->cid; } else { target->action = WF_REPO_UPDATE; target->cid = source->prev; target->prev = source->cid; } } *out_operations = inverted; return WF_OK;}
void wf_repo_diff_free(wf_repo_diff *diff) { if (!diff) return; wf_repo_operations_free(diff->operations, diff->operation_count); wf_car_free(&diff->new_blocks); free(diff->removed_cids); memset(diff, 0, sizeof(*diff));}
static wf_status repo_operation_from_leaf(wf_repo_operation *operation, wf_repo_operation_action action, const repo_leaf *leaf) { const unsigned char *slash = memchr(leaf->key, '/', leaf->key_len); if (!slash || slash == leaf->key || slash == leaf->key + leaf->key_len - 1) return WF_ERR_PARSE; size_t collection_len = (size_t)(slash - leaf->key); size_t rkey_len = leaf->key_len - collection_len - 1; if (memchr(slash + 1, '/', rkey_len) || memchr(leaf->key, '\0', leaf->key_len)) return WF_ERR_PARSE; operation->collection = malloc(collection_len + 1); operation->rkey = malloc(rkey_len + 1); if (!operation->collection || !operation->rkey) { free(operation->collection); free(operation->rkey); operation->collection = NULL; operation->rkey = NULL; return WF_ERR_ALLOC; } memcpy(operation->collection, leaf->key, collection_len); operation->collection[collection_len] = '\0'; memcpy(operation->rkey, slash + 1, rkey_len); operation->rkey[rkey_len] = '\0'; operation->action = action; operation->cid = leaf->cid; return WF_OK;}
static wf_status repo_diff_operations(const repo_leaf_list *previous, const repo_leaf_list *current, wf_repo_diff *out) { size_t capacity = previous->count + current->count; out->operations = capacity ? calloc(capacity, sizeof(*out->operations)) : NULL; if (capacity && !out->operations) return WF_ERR_ALLOC; size_t old_index = 0, new_index = 0; while (old_index < previous->count || new_index < current->count) { int comparison; if (old_index == previous->count) comparison = 1; else if (new_index == current->count) comparison = -1; else comparison = repo_key_cmp(previous->items[old_index].key, previous->items[old_index].key_len, current->items[new_index].key, current->items[new_index].key_len); wf_repo_operation *operation = &out->operations[out->operation_count]; wf_status status; if (comparison < 0) { status = repo_operation_from_leaf(operation, WF_REPO_DELETE, &previous->items[old_index++]); } else if (comparison > 0) { status = repo_operation_from_leaf(operation, WF_REPO_CREATE, ¤t->items[new_index++]); } else { const repo_leaf *old_leaf = &previous->items[old_index++]; const repo_leaf *new_leaf = ¤t->items[new_index++]; if (cid_equal(&old_leaf->cid, &new_leaf->cid)) continue; status = repo_operation_from_leaf(operation, WF_REPO_UPDATE, new_leaf); if (status == WF_OK) operation->prev = old_leaf->cid; } if (status != WF_OK) return status; out->operation_count++; } return WF_OK;}
static int repo_leaves_have_cid(const repo_leaf_list *leaves, const wf_cid *cid) { for (size_t i = 0; i < leaves->count; i++) if (cid_equal(&leaves->items[i].cid, cid)) return 1; return 0;}
static wf_status repo_diff_add_removed(wf_repo_diff *diff, const wf_cid *cid) { repo_cid_list view = {diff->removed_cids, diff->removed_count}; if (repo_cid_list_has(&view, cid)) return WF_OK; wf_cid *items = realloc(diff->removed_cids, (diff->removed_count + 1) * sizeof(*items)); if (!items) return WF_ERR_ALLOC; diff->removed_cids = items; diff->removed_cids[diff->removed_count++] = *cid; return WF_OK;}
static wf_status commit_unsigned_bytes(const wf_commit *commit, unsigned char **out, size_t *out_len) { wf_cbor_item *map = calloc(1, sizeof(*map)); if (!map) return WF_ERR_ALLOC; map->type = WF_CBOR_MAP; map->map.count = 5; map->map.pairs = calloc(5, sizeof(wf_cbor_pair)); if (!map->map.pairs) { free(map); return WF_ERR_ALLOC; } map->map.pairs[0].key = cbor_str("did"); map->map.pairs[0].value = cbor_str(commit->did); map->map.pairs[1].key = cbor_str("rev"); map->map.pairs[1].value = cbor_str(commit->rev); map->map.pairs[2].key = cbor_str("data"); map->map.pairs[2].value = cbor_cid(&commit->data); map->map.pairs[3].key = cbor_str("prev"); map->map.pairs[3].value = commit->has_prev ? cbor_cid(&commit->prev) : cbor_null(); map->map.pairs[4].key = cbor_str("version"); map->map.pairs[4].value = cbor_uint((uint64_t)commit->version); *out = wf_cbor_serialize(map, out_len); wf_cbor_free(map); return *out ? WF_OK : WF_ERR_ALLOC;}
static wf_status verify_mst_links(const wf_car *car, const wf_cid *cid, size_t depth) { if (depth > WF_MST_MAX_DEPTH || depth > car->block_count) return WF_ERR_PARSE; wf_car_block *block = wf_car_find_block((wf_car *)car, cid); if (!block) return WF_ERR_NOT_FOUND; wf_mst_node node; wf_status status = wf_mst_node_parse(block->data, block->data_len, cid, &node); if (status != WF_OK) return status; if (node.left.len) { status = verify_mst_links(car, &node.left, depth + 1); } for (size_t i = 0; status == WF_OK && i < node.count; i++) { if (!wf_car_find_block((wf_car *)car, &node.entries[i].value)) { status = WF_ERR_NOT_FOUND; break; } if (node.entries[i].subtree.len) { status = verify_mst_links(car, &node.entries[i].subtree, depth + 1); } } wf_mst_node_free(&node); return status;}
wf_status wf_repo_verify(const wf_car *car, const wf_repo_verify_options *options, wf_commit *out_commit) { if (!car || !options || !options->expected_did || options->expected_did[0] == '\0' || !options->signing_key || options->signing_key[0] == '\0' || !out_commit) { return WF_ERR_INVALID_ARG; } memset(out_commit, 0, sizeof(*out_commit)); if (car->root_count != 1 || car->roots[0].len != 36) return WF_ERR_PARSE;
repo_cid_set set = {0}; wf_status status = repo_cid_set_init(&set, car->block_count); if (status != WF_OK) return status; for (size_t i = 0; i < car->block_count; i++) { wf_cid computed; if (!car->blocks[i].data || car->blocks[i].data_len == 0 || wf_cid_of_block(car->blocks[i].data, car->blocks[i].data_len, &computed) != WF_OK || !cid_equal(&computed, &car->blocks[i].cid)) { status = WF_ERR_PARSE; break; } int dup = 0; status = repo_cid_set_insert(&set, &car->blocks[i].cid, &dup); if (status != WF_OK) break; if (dup) { status = WF_ERR_PARSE; break; } } free(set.slots); if (status != WF_OK) return status;
wf_car_block *root = wf_car_find_block((wf_car *)car, &car->roots[0]); if (!root) return WF_ERR_NOT_FOUND; wf_commit commit; status = wf_commit_parse(root->data, root->data_len, &commit); if (status != WF_OK || commit.version != 3 || commit.sig_len != 64) return WF_ERR_PARSE; wf_cbor_item *root_object = wf_cbor_parse(root->data, root->data_len); if (!root_object || root_object->type != WF_CBOR_MAP || root_object->map.count != 6 || !wf_cbor_map_find(root_object, "prev", 4)) { wf_cbor_free(root_object); return WF_ERR_PARSE; } wf_cbor_free(root_object); commit.cid = car->roots[0]; if (strcmp(commit.did, options->expected_did) != 0) return WF_ERR_PARSE; /* * `expected_prev` can only be checked against a commit that carries one. * Version 3 commits set `prev` to null by specification, so for those the * chain is established by `rev` ordering (and, on the firehose, by * `prevData`) rather than by this field. Only reject a commit that carries * a prev which disagrees. */ if (options->expected_prev && commit.has_prev && !cid_equal(&commit.prev, options->expected_prev)) return WF_ERR_PARSE;
unsigned char *unsigned_commit = NULL; size_t unsigned_len = 0; status = commit_unsigned_bytes(&commit, &unsigned_commit, &unsigned_len); if (status != WF_OK) return status; status = wf_verify(options->signing_key, unsigned_commit, unsigned_len, commit.sig, commit.sig_len); free(unsigned_commit); if (status != WF_OK) return status;
status = verify_mst_links(car, &commit.data, 0); if (status != WF_OK) return status; *out_commit = commit; return WF_OK;}
wf_status wf_repo_import(const unsigned char *bytes, size_t len, const wf_repo_verify_options *options, wf_car *out_car, wf_commit *out_commit) { if (!bytes || len == 0 || !options || !out_car || !out_commit) return WF_ERR_INVALID_ARG; memset(out_car, 0, sizeof(*out_car)); memset(out_commit, 0, sizeof(*out_commit)); wf_status status = wf_car_parse(bytes, len, out_car); if (status != WF_OK) return status; status = wf_repo_verify(out_car, options, out_commit); if (status != WF_OK) { wf_car_free(out_car); memset(out_commit, 0, sizeof(*out_commit)); } return status;}
wf_status wf_repo_diff_verify(const wf_car *base, const wf_cid *base_commit, const wf_car *update, const wf_repo_verify_options *options, wf_repo_diff *out) { if (!base || !base_commit || base_commit->len != 36 || !update || !options || !out || !options->expected_did || !options->signing_key) return WF_ERR_INVALID_ARG; memset(out, 0, sizeof(*out)); if (base->root_count != 1 || !cid_equal(&base->roots[0], base_commit) || update->root_count != 1 || update->roots[0].len != 36) return WF_ERR_PARSE;
wf_repo_verify_options base_options = *options; base_options.expected_prev = NULL; wf_commit previous_commit = {0}; wf_status status = wf_repo_verify(base, &base_options, &previous_commit); if (status != WF_OK) return status; status = repo_car_validate_blocks(update); if (status != WF_OK) return status;
wf_car composite = {0}; status = repo_composite_car(base, update, &composite); if (status != WF_OK) return status; wf_commit current_commit = {0}; status = wf_repo_verify(&composite, options, ¤t_commit); if (status != WF_OK) { free(composite.blocks); return status; }
repo_leaf_list previous_leaves = {0}, current_leaves = {0}; repo_cid_list previous_nodes = {0}, current_nodes = {0}; status = repo_collect_mst(&composite, &previous_commit.data, &previous_leaves, &previous_nodes, 0); if (status == WF_OK) status = repo_collect_mst(&composite, ¤t_commit.data, ¤t_leaves, ¤t_nodes, 0); if (status == WF_OK) status = repo_diff_operations(&previous_leaves, ¤t_leaves, out); if (status != WF_OK) goto cleanup_diff;
out->commit = current_commit; out->previous_commit = *base_commit; memcpy(out->since, previous_commit.rev, sizeof(out->since)); out->new_blocks.roots = malloc(sizeof(wf_cid)); if (!out->new_blocks.roots) { status = WF_ERR_ALLOC; goto cleanup_diff; } out->new_blocks.roots[0] = current_commit.cid; out->new_blocks.root_count = 1;
for (size_t i = 0; i < current_nodes.count; i++) { if (!repo_cid_list_has(&previous_nodes, ¤t_nodes.items[i])) { status = repo_car_add_cid_copy(&out->new_blocks, &composite, ¤t_nodes.items[i]); if (status != WF_OK) goto cleanup_diff; } } for (size_t i = 0; i < out->operation_count; i++) { wf_repo_operation *operation = &out->operations[i]; if (operation->action == WF_REPO_DELETE) continue; wf_car_block *leaf = wf_car_find_block((wf_car *)update, &operation->cid); if (!leaf) { status = WF_ERR_NOT_FOUND; goto cleanup_diff; } status = repo_car_add_copy(&out->new_blocks, leaf); if (status != WF_OK) goto cleanup_diff; } if (!cid_equal(¤t_commit.cid, base_commit)) { status = repo_car_add_cid_copy(&out->new_blocks, &composite, ¤t_commit.cid); if (status != WF_OK) goto cleanup_diff; } for (size_t i = 0; i < previous_nodes.count; i++) { if (!repo_cid_list_has(¤t_nodes, &previous_nodes.items[i])) { status = repo_diff_add_removed(out, &previous_nodes.items[i]); if (status != WF_OK) goto cleanup_diff; } } for (size_t i = 0; i < out->operation_count; i++) { wf_repo_operation *operation = &out->operations[i]; const wf_cid *old_cid = operation->action == WF_REPO_UPDATE ? &operation->prev : &operation->cid; if (operation->action != WF_REPO_CREATE && !repo_leaves_have_cid(¤t_leaves, old_cid)) { status = repo_diff_add_removed(out, old_cid); if (status != WF_OK) goto cleanup_diff; } } if (!cid_equal(base_commit, ¤t_commit.cid)) status = repo_diff_add_removed(out, base_commit);
cleanup_diff: free(composite.blocks); repo_leaf_list_free(&previous_leaves); repo_leaf_list_free(¤t_leaves); free(previous_nodes.items); free(current_nodes.items); if (status != WF_OK) wf_repo_diff_free(out); return status;}
static int repo_removed_has(const wf_repo_diff *diff, const wf_cid *cid) { for (size_t i = 0; i < diff->removed_count; i++) if (cid_equal(&diff->removed_cids[i], cid)) return 1; return 0;}
wf_status wf_repo_diff_apply(wf_car *repo, const wf_repo_diff *diff) { if (!repo || !diff || (diff->operation_count && !diff->operations) || (diff->removed_count && !diff->removed_cids) || diff->previous_commit.len != 36 || diff->commit.cid.len != 36 || repo->root_count != 1 || !cid_equal(&repo->roots[0], &diff->previous_commit) || diff->new_blocks.root_count != 1 || !cid_equal(&diff->new_blocks.roots[0], &diff->commit.cid)) return WF_ERR_INVALID_ARG;
wf_status status = repo_car_validate_blocks(&diff->new_blocks); if (status != WF_OK) return status; wf_car staged = {0}; staged.roots = malloc(sizeof(wf_cid)); if (!staged.roots) return WF_ERR_ALLOC; staged.roots[0] = diff->commit.cid; staged.root_count = 1; for (size_t i = 0; i < repo->block_count; i++) { if (repo_removed_has(diff, &repo->blocks[i].cid)) continue; status = repo_car_add_copy(&staged, &repo->blocks[i]); if (status != WF_OK) goto apply_fail; } for (size_t i = 0; i < diff->new_blocks.block_count; i++) { status = repo_car_add_copy(&staged, &diff->new_blocks.blocks[i]); if (status != WF_OK) goto apply_fail; } wf_car_block *commit_block = wf_car_find_block(&staged, &diff->commit.cid); wf_commit parsed = {0}; if (!commit_block) { status = WF_ERR_NOT_FOUND; goto apply_fail; } status = wf_commit_parse(commit_block->data, commit_block->data_len, &parsed); if (status != WF_OK || strcmp(parsed.did, diff->commit.did) != 0 || strcmp(parsed.rev, diff->commit.rev) != 0 || parsed.version != diff->commit.version || parsed.has_prev != diff->commit.has_prev || (parsed.has_prev && !cid_equal(&parsed.prev, &diff->commit.prev)) || parsed.sig_len != diff->commit.sig_len || memcmp(parsed.sig, diff->commit.sig, parsed.sig_len) != 0 || !cid_equal(&parsed.data, &diff->commit.data)) { status = WF_ERR_PARSE; goto apply_fail; } status = verify_mst_links(&staged, &parsed.data, 0); if (status != WF_OK) goto apply_fail; wf_car_free(repo); *repo = staged; return WF_OK;
apply_fail: wf_car_free(&staged); return status;}