#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdarg.h>
#include "dtob.h"
#include "dtob_types.h"
#include "xdiff/xinclude.h"
#include "bromberg_sl2.h"
#include "xanadoc_types.h"
#define DIF_CCID (DTOB_CUSTOM_MIN + 0)
#define DIF_OP (DTOB_CUSTOM_MIN + 1)
#define DIF_COPY (DTOB_CUSTOM_MIN + 2)
#define DIF_ADD (DTOB_CUSTOM_MIN + 3)
#define DIF_START (DTOB_CUSTOM_MIN + 4)
#define DIF_END (DTOB_CUSTOM_MIN + 5)
#define DIF_DATA (DTOB_CUSTOM_MIN + 6)
DTOB_DEFINE_CUSTOM_TYPES(sculblog_dif, th,
DTOB_CUSTOM_TYPE_RAW (th, DIF_CCID, "ccid")
DTOB_CUSTOM_TYPE_ENUM (th, DIF_OP, "op", DIF_COPY, DIF_ADD)
DTOB_CUSTOM_TYPE_STRUCT (th, DIF_COPY, "copy", DIF_START, DIF_END)
DTOB_CUSTOM_TYPE_STRUCT (th, DIF_ADD, "add", DIF_START, DIF_END, DIF_DATA)
DTOB_CUSTOM_TYPE_UINT64 (th, DIF_START, "start")
DTOB_CUSTOM_TYPE_UINT64 (th, DIF_END, "end")
DTOB_CUSTOM_TYPE_RAW (th, DIF_DATA, "data")
)
static DtobValue* sculblog_dif_u64_field(uint16_t code, uint64_t val) {
uint8_t bytes[8];
for (int i = 7; i >= 0; i--) { bytes[i] = (uint8_t)(val & 0xFF); val >>= 8; }
DtobValue* v = dtob_custom(code, bytes, 8);
v->inner_code = DTOB_CODE_UINT64;
return v;
}
static DtobValue* sculblog_dif_copy(uint64_t s, uint64_t e) {
DtobValue* op = dtob_custom(DIF_OP, NULL, 0);
op->inner_code = DIF_COPY;
dtob_custom_push(op, sculblog_dif_u64_field(DIF_START, s));
dtob_custom_push(op, sculblog_dif_u64_field(DIF_END, e));
return op;
}
static DtobValue* sculblog_dif_add(uint64_t s, uint64_t e, const uint8_t* data, size_t len) {
DtobValue* op = dtob_custom(DIF_OP, NULL, 0);
op->inner_code = DIF_ADD;
dtob_custom_push(op, sculblog_dif_u64_field(DIF_START, s));
dtob_custom_push(op, sculblog_dif_u64_field(DIF_END, e));
DtobValue* dv = dtob_custom(DIF_DATA, data, len);
dv->inner_code = DTOB_CODE_RAW;
dtob_custom_push(op, dv);
return op;
}
static DtobValue* sculblog_dif_diff_new(void) {
return dtob_array();
}
static void sculblog_dif_diff_push(DtobValue* diff, DtobValue* op) {
dtob_array_push(diff, op);
}
int BUG_exit_code = 128;
int bug_called_must_BUG = 0;
void *xmalloc(size_t size) {
if (size == 0) {
size = 1;
}
void *ptr = malloc(size);
if (!ptr) {
fprintf(stderr, "xmalloc: allocation failed for %zu bytes\n", size);
abort();
}
return ptr;
}
void *xcalloc(size_t nmemb, size_t size) {
if (nmemb == 0 || size == 0) {
nmemb = 1;
size = 1;
}
void *ptr = calloc(nmemb, size);
if (!ptr) {
fprintf(stderr, "xcalloc: allocation failed for %zu x %zu bytes\n", nmemb, size);
abort();
}
return ptr;
}
void *xrealloc(void *ptr, size_t size) {
if (size == 0) {
size = 1;
}
void *next = realloc(ptr, size);
if (!next) {
fprintf(stderr, "xrealloc: allocation failed for %zu bytes\n", size);
abort();
}
return next;
}
void BUG_fl(const char *file, int line, const char *fmt, ...) {
va_list args;
fprintf(stderr, "BUG at %s:%d: ", file, line);
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fputc('\n', stderr);
abort();
}
static long record_offset(mmfile_t *mf, xdfile_t *xdf, long line_index) {
if (line_index <= 0) {
return 0;
}
if ((size_t)line_index >= xdf->nrec) {
return mf->size;
}
return (long)(xdf->recs[line_index].ptr - (uint8_t const *)mf->ptr);
}
static void emit_copy_range(DtobValue *diff, long start, long end_exclusive) {
if (end_exclusive > start) {
sculblog_dif_diff_push(diff, sculblog_dif_copy((uint64_t)start, (uint64_t)(end_exclusive - 1)));
}
}
static void emit_add_range(DtobValue *diff, const uint8_t *data, long start, long end_exclusive) {
if (end_exclusive > start) {
sculblog_dif_diff_push(diff, sculblog_dif_add(
(uint64_t)start, (uint64_t)(end_exclusive - 1),
data + start, (size_t)(end_exclusive - start)
));
}
}
void ffi_bromberg_init(void) {
bromberg_init();
}
void ffi_bromberg_hash(const uint8_t* in, size_t len, uint8_t out[32]) {
hash_matrix_t m = bromberg_hash(in, len);
char hex[65];
bromberg_to_hex(m, hex);
for (int i=0; i<32; i++) {
sscanf(&hex[i*2], "%2hhx", &out[i]);
}
}
void ffi_diff_build(const uint8_t *old_data, size_t old_len,
const uint8_t *new_data, size_t new_len,
DtobValue *diff) {
mmfile_t mf1 = { (char *)old_data, (long)old_len };
mmfile_t mf2 = { (char *)new_data, (long)new_len };
xpparam_t xpp = { 0 };
xdfenv_t env = { 0 };
xdchange_t *script = NULL;
if (xdl_do_diff(&mf1, &mf2, &xpp, &env) < 0) {
sculblog_dif_diff_push(diff, sculblog_dif_add(0, new_len > 0 ? new_len - 1 : 0, new_data, new_len));
return;
}
if (xdl_change_compact(&env.xdf1, &env.xdf2, xpp.flags) < 0 ||
xdl_build_script(&env, &script) < 0) {
xdl_free_env(&env);
sculblog_dif_diff_push(diff, sculblog_dif_add(0, new_len > 0 ? new_len - 1 : 0, new_data, new_len));
return;
}
long old_cursor = 0;
for (xdchange_t *change = script; change; change = change->next) {
long unchanged_end = record_offset(&mf1, &env.xdf1, change->i1);
emit_copy_range(diff, old_cursor, unchanged_end);
long new_start = record_offset(&mf2, &env.xdf2, change->i2);
long new_end = record_offset(&mf2, &env.xdf2, change->i2 + change->chg2);
emit_add_range(diff, new_data, new_start, new_end);
old_cursor = record_offset(&mf1, &env.xdf1, change->i1 + change->chg1);
}
emit_copy_range(diff, old_cursor, (long)old_len);
xdl_free_script(script);
xdl_free_env(&env);
}
static uint64_t dtob_data_u64_be(const DtobValue *v) {
if (!v || !v->data) return 0;
uint64_t val = 0;
for (size_t i = 0; i < v->data_len && i < 8; i++)
val = (val << 8) | v->data[i];
return val;
}
uint8_t* ffi_apply_diff(const uint8_t *prev, size_t prev_len, const DtobValue *diff_val, size_t *out_len) {
size_t cap = prev_len > 0 ? prev_len * 2 : 4096;
uint8_t *result = malloc(cap);
size_t rlen = 0;
for (size_t i = 0; i < diff_val->num_elements; i++) {
const DtobValue *op = diff_val->elements[i];
if (!op) continue;
uint16_t variant = op->inner_code;
if (variant == DIF_COPY) {
if (op->num_elements < 2) continue;
uint64_t s = dtob_data_u64_be(op->elements[0]);
uint64_t e = dtob_data_u64_be(op->elements[1]);
if (e >= s && e < prev_len) {
size_t clen = (size_t)(e - s + 1);
while (rlen + clen > cap) { cap *= 2; result = realloc(result, cap); }
memcpy(result + rlen, prev + s, clen);
rlen += clen;
}
} else if (variant == DIF_ADD) {
if (op->num_elements < 3) continue;
const DtobValue *dv = op->elements[2];
if (dv && dv->data && dv->data_len > 0) {
while (rlen + dv->data_len > cap) { cap *= 2; result = realloc(result, cap); }
memcpy(result + rlen, dv->data, dv->data_len);
rlen += dv->data_len;
}
}
}
*out_len = rlen;
return result;
}
DtobValue* ffi_array_new(void) { return dtob_array(); }
DtobValue* ffi_kvset_new(void) { return dtob_kvset(); }
void ffi_array_push(DtobValue* arr, DtobValue* val) { dtob_array_push(arr, val); }
void ffi_kvset_put(DtobValue* kv, const char* key, DtobValue* val) { dtob_kvset_put(kv, key, val); }
DtobValue* ffi_kvset_get(DtobValue* kv, const char* key) { return dtob_kvset_get(kv, key); }
DtobValue* ffi_raw_new(const uint8_t* data, size_t len) { return dtob_raw(data, len); }
DtobValue* ffi_uint_new(uint64_t val) { return dtob_uint(val); }
DtobValue* ffi_xanadoc_ids_new(const uint8_t* ccid, const uint8_t* cid, const uint8_t* sha256) {
DtobValue* op = dtob_custom(XANADOC_IDS, NULL, 0);
DtobValue* v_ccid = dtob_custom(XANADOC_CCID, ccid, 32);
v_ccid->inner_code = DTOB_CODE_RAW;
dtob_custom_push(op, v_ccid);
DtobValue* v_cid = dtob_custom(XANADOC_CID, cid, 32);
v_cid->inner_code = DTOB_CODE_RAW;
dtob_custom_push(op, v_cid);
DtobValue* v_sha256 = dtob_custom(XANADOC_SHA256CID, sha256, 32);
v_sha256->inner_code = DTOB_CODE_RAW;
dtob_custom_push(op, v_sha256);
return op;
}
DtobValue* ffi_dif_ops_new(void) { return sculblog_dif_diff_new(); }
void ffi_dif_ops_push_add(DtobValue* diff, const uint8_t* data, size_t len) {
sculblog_dif_diff_push(diff, sculblog_dif_add(0, len > 0 ? len - 1 : 0, data, len));
}
DtobValue* ffi_build_dif_entry(
DtobValue* ops
) {
return ops;
}
int ffi_verify_file_types(const char* path, int strict) {
DtobTypesHeader types;
build_sculblog_dif_custom_types(&types);
int res = dtob_verify_file_types(path, &types, strict);
dtob_types_cleanup(&types);
return res;
}
static uint8_t* ffi_encode_internal(DtobValue* val, int chunk_mode, size_t* out_len) {
DtobTypesHeader types;
build_sculblog_dif_custom_types(&types);
uint8_t* buf = chunk_mode ?
dtob_encode_chunk(val, &types, 1, out_len) :
dtob_encode_with_types(val, &types, 0, out_len);
dtob_types_cleanup(&types);
return buf;
}
uint8_t* ffi_encode_dif(DtobValue* root, size_t *out_len) {
return ffi_encode_internal(root, 0, out_len);
}
uint8_t* ffi_encode_xanadoc_header(DtobValue* root, size_t *out_len) {
DtobTypesHeader types;
build_xanadoc_custom_types(&types);
uint8_t* buf = dtob_encode_with_types(root, &types, 0, out_len);
dtob_types_cleanup(&types);
return buf;
}
DtobValue* ffi_decode_dif(const uint8_t* buf, size_t len) {
return dtob_decode(buf, len);
}
uint8_t* ffi_encode_chunk(DtobValue* val, size_t* out_len) {
return ffi_encode_internal(val, 1, out_len);
}
void ffi_dtob_free(DtobValue* val) {
dtob_free(val);
}
size_t ffi_dtob_array_len(const DtobValue* val) {
return val ? val->num_elements : 0;
}
const DtobValue* ffi_dtob_array_get(const DtobValue* val, size_t idx) {
if (val && idx < val->num_elements) return val->elements[idx];
return NULL;
}
size_t ffi_dtob_kvset_len(const DtobValue* val) {
return val ? val->num_pairs : 0;
}
const char* ffi_dtob_kvset_key(const DtobValue* val, size_t idx) {
if (val && idx < val->num_pairs) return (const char*)val->pairs[idx].key;
return NULL;
}
const DtobValue* ffi_dtob_kvset_value_at(const DtobValue* val, size_t idx) {
if (val && idx < val->num_pairs) return val->pairs[idx].value;
return NULL;
}
const uint8_t* ffi_dtob_get_raw(const DtobValue* val, size_t* out_len) {
if (!val || !val->data) return NULL;
*out_len = val->data_len;
return val->data;
}
uint64_t ffi_dtob_get_u64(const DtobValue* val) {
return dtob_data_u64_be(val);
}
static DtobValue *xdc_parse(const uint8_t *data, size_t len) {
DtobTypesHeader types;
build_xanadoc_custom_types(&types);
DtobValue *root = dtob_decode_with_types((uint8_t *)data, len, &types);
dtob_types_cleanup(&types);
return root;
}
uint8_t *ffi_xdc_decode_content(const uint8_t *data, size_t len, size_t *out_len) {
*out_len = 0;
DtobValue *root = xdc_parse(data, len);
if (!root) return NULL;
size_t field_len = 0;
const uint8_t *ptr = dtob_kvset_raw(root, "content", &field_len);
if (!ptr || field_len == 0) { dtob_free(root); return NULL; }
uint8_t *result = malloc(field_len);
memcpy(result, ptr, field_len);
*out_len = field_len;
dtob_free(root);
return result;
}
static uint32_t xdc_enum_to_u32(const DtobValue *v) {
if (!v) return 0;
return (uint32_t)dtob_val_to_u64(v);
}
static size_t xdc_raw_copy(const DtobValue *v, uint8_t *dst, size_t max) {
if (!v || !v->data) return 0;
size_t n = v->data_len < max ? v->data_len : max;
memcpy(dst, v->data, n);
return n;
}
static void xdc_copy32(const DtobValue *v, uint8_t *dst) {
memset(dst, 0, 32);
xdc_raw_copy(v, dst, 32);
}
static void write_u32_le(uint8_t *p, uint32_t v) {
p[0] = v & 0xFF; p[1] = (v>>8)&0xFF; p[2] = (v>>16)&0xFF; p[3] = (v>>24)&0xFF;
}
uint8_t *ffi_xdc_decode_references(const uint8_t *data, size_t len, size_t *out_len) {
*out_len = 0;
DtobValue *root = xdc_parse(data, len);
if (!root) return NULL;
DtobValue *refs = dtob_kvset_get(root, "references");
DtobValue *paleolinks = refs ? dtob_kvset_get(refs, "paleolinks") : NULL;
DtobValue *paleo_trans = paleolinks ? dtob_kvset_get(paleolinks, "transclusions") : NULL;
DtobValue *auto_micro = paleolinks ? dtob_kvset_get(paleolinks, "microlinks") : NULL;
DtobValue *macro_arr = paleolinks ? dtob_kvset_get(paleolinks, "macrolinks") : NULL;
size_t nt = paleo_trans ? paleo_trans->num_elements : 0;
size_t nm = auto_micro ? auto_micro->num_elements : 0;
size_t nM = macro_arr ? macro_arr->num_elements : 0;
size_t count = nt + nm + nM;
size_t total = 4 + count * 41;
uint8_t *out = malloc(total);
if (!out) { dtob_free(root); return NULL; }
memset(out, 0, total);
uint8_t *p = out;
write_u32_le(p, (uint32_t)count); p += 4;
for (size_t i = 0; i < nt; i++) {
DtobValue *t = paleo_trans->elements[i];
if (!t || t->num_elements < 4) { p += 41; continue; }
*p++ = 0;
xdc_raw_copy(t->elements[0], p, 32);
p += 32;
uint32_t ref_start = xdc_enum_to_u32(t->elements[3]);
uint32_t length = xdc_enum_to_u32(t->elements[1]);
write_u32_le(p, ref_start); p += 4;
write_u32_le(p, length); p += 4;
}
for (size_t i = 0; i < nm; i++) {
DtobValue *m = auto_micro->elements[i];
if (!m || m->num_elements < 6) { p += 41; continue; }
*p++ = 1;
DtobValue *def_ids = m->elements[0];
if (def_ids && def_ids->num_elements >= 3)
xdc_copy32(def_ids->elements[1], p);
p += 32;
uint32_t ref_start = xdc_enum_to_u32(m->elements[5]);
uint32_t ref_length = xdc_enum_to_u32(m->elements[4]);
write_u32_le(p, ref_start); p += 4;
write_u32_le(p, ref_length); p += 4;
}
for (size_t i = 0; i < nM; i++) {
DtobValue *m = macro_arr->elements[i];
if (!m || m->num_elements < 4) { p += 41; continue; }
*p++ = 2;
DtobValue *def_ids = m->elements[0];
if (def_ids && def_ids->num_elements >= 3)
xdc_copy32(def_ids->elements[1], p);
p += 32;
uint32_t ref_start = xdc_enum_to_u32(m->elements[3]);
uint32_t length = xdc_enum_to_u32(m->elements[2]);
write_u32_le(p, ref_start); p += 4;
write_u32_le(p, length); p += 4;
}
*out_len = total;
dtob_free(root);
return out;
}