| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| #include "erl_nif.h"
|
| #include <string.h>
|
| #include <stdint.h>
|
| #include <stdlib.h>
|
|
|
|
|
| #include "seb_lattice.c"
|
|
|
| #define FIXED_HEADER_SIZE 68
|
| #define FIXED_FOOTER_SIZE 128
|
| #define HASH_SIZE 32
|
| #define SIG_SIZE 64
|
|
|
| #define FOOTER_PREV_HASH_OFF 0
|
| #define FOOTER_EVENT_HASH_OFF 32
|
| #define FOOTER_SIG_OFF 64
|
|
|
|
|
| typedef struct {
|
| uint64_t segment_id;
|
| uint64_t sequence;
|
| uint8_t tip_hash[HASH_SIZE];
|
| uint64_t tip_offset;
|
| uint64_t events_sealed;
|
| uint64_t segments_rotated;
|
| int initialized;
|
| } seb_wal_handle;
|
|
|
| ErlNifResourceType *wal_handle_type = NULL;
|
|
|
| static void wal_handle_dtor(ErlNifEnv *env, void *obj) { (void)env; (void)obj; }
|
|
|
|
|
| static ERL_NIF_TERM nif_init_kernel(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
|
| {
|
| if (argc != 2) return enif_make_badarg(env);
|
| uint64_t seg_id, seq;
|
| if (!enif_get_uint64(env, argv[0], &seg_id)) return enif_make_badarg(env);
|
| if (!enif_get_uint64(env, argv[1], &seq)) return enif_make_badarg(env);
|
|
|
| seb_wal_handle *h = enif_alloc_resource(wal_handle_type, sizeof(seb_wal_handle));
|
| if (!h) return enif_make_atom(env, "error");
|
|
|
| h->segment_id = seg_id;
|
| h->sequence = seq;
|
| memset(h->tip_hash, 0, HASH_SIZE);
|
| h->tip_offset = 0;
|
| h->events_sealed = 0;
|
| h->segments_rotated = 0;
|
| h->initialized = 1;
|
|
|
| ERL_NIF_TERM res = enif_make_resource(env, h);
|
| enif_release_resource(h);
|
| return enif_make_tuple2(env, enif_make_atom(env, "ok"), res);
|
| }
|
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| static ERL_NIF_TERM nif_append_event(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
|
| {
|
| if (argc != 4) return enif_make_badarg(env);
|
|
|
| seb_wal_handle *h;
|
| ErlNifBinary hdr_bin, pay_bin, ftr_bin;
|
|
|
| if (!enif_get_resource(env, argv[0], wal_handle_type, (void**)&h))
|
| return enif_make_atom(env, "error");
|
| if (!h->initialized)
|
| return enif_make_tuple2(env, enif_make_atom(env, "error"),
|
| enif_make_atom(env, "not_initialized"));
|
|
|
| if (!enif_inspect_binary(env, argv[1], &hdr_bin) ||
|
| hdr_bin.size != FIXED_HEADER_SIZE)
|
| return enif_make_tuple2(env, enif_make_atom(env, "error"),
|
| enif_make_atom(env, "invalid_header"));
|
|
|
| if (!enif_inspect_binary(env, argv[2], &pay_bin))
|
| return enif_make_tuple2(env, enif_make_atom(env, "error"),
|
| enif_make_atom(env, "invalid_payload"));
|
|
|
| if (!enif_inspect_binary(env, argv[3], &ftr_bin) ||
|
| ftr_bin.size != FIXED_FOOTER_SIZE)
|
| return enif_make_tuple2(env, enif_make_atom(env, "error"),
|
| enif_make_atom(env, "invalid_footer"));
|
|
|
| const uint8_t *prev_hash = ftr_bin.data + FOOTER_PREV_HASH_OFF;
|
| const uint8_t *event_hash = ftr_bin.data + FOOTER_EVENT_HASH_OFF;
|
|
|
|
|
| if (h->events_sealed > 0) {
|
| uint8_t diff = 0;
|
| for (int i = 0; i < HASH_SIZE; i++) diff |= prev_hash[i] ^ h->tip_hash[i];
|
| if (diff)
|
| return enif_make_tuple2(env, enif_make_atom(env, "error"),
|
| enif_make_atom(env, "hash_chain_broken"));
|
| }
|
|
|
| |
|
|
| uint8_t in96[96];
|
| memcpy(in96, h->tip_hash, HASH_SIZE);
|
| memcpy(in96 + 32, hdr_bin.data, 64);
|
| uint8_t computed[HASH_SIZE];
|
| circuit(in96, computed);
|
|
|
| {
|
| uint8_t diff = 0;
|
| for (int i = 0; i < HASH_SIZE; i++) diff |= computed[i] ^ event_hash[i];
|
| if (diff)
|
| return enif_make_tuple2(env, enif_make_atom(env, "error"),
|
| enif_make_atom(env, "invalid_commitment"));
|
| }
|
|
|
|
|
| uint64_t new_offset = 0;
|
| for (int i = 0; i < 8; i++)
|
| new_offset |= ((uint64_t)hdr_bin.data[i]) << (i * 8);
|
| if (h->events_sealed > 0 && new_offset <= h->tip_offset)
|
| return enif_make_tuple2(env, enif_make_atom(env, "error"),
|
| enif_make_atom(env, "offset_not_monotonic"));
|
|
|
|
|
| uint32_t payload_size = 0;
|
| for (int i = 0; i < 4; i++)
|
| payload_size |= ((uint32_t)hdr_bin.data[24 + i]) << (i * 8);
|
| uint64_t event_size = FIXED_HEADER_SIZE + payload_size + FIXED_FOOTER_SIZE;
|
| if (event_size > (1ULL << 30))
|
| return enif_make_tuple2(env, enif_make_atom(env, "error"),
|
| enif_make_atom(env, "segment_full"));
|
|
|
|
|
| uint64_t committed = h->tip_offset;
|
| memcpy(h->tip_hash, event_hash, HASH_SIZE);
|
| h->tip_offset = new_offset;
|
| h->events_sealed++;
|
|
|
| return enif_make_tuple2(env, enif_make_atom(env, "ok"),
|
| enif_make_uint64(env, committed));
|
| }
|
|
|
|
|
| static ERL_NIF_TERM nif_rotate_segment(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
|
| {
|
| if (argc != 3) return enif_make_badarg(env);
|
| seb_wal_handle *h;
|
| uint64_t new_id, new_seq;
|
| if (!enif_get_resource(env, argv[0], wal_handle_type, (void**)&h))
|
| return enif_make_atom(env, "error");
|
| if (!enif_get_uint64(env, argv[1], &new_id)) return enif_make_badarg(env);
|
| if (!enif_get_uint64(env, argv[2], &new_seq)) return enif_make_badarg(env);
|
|
|
| if (new_seq <= h->sequence)
|
| return enif_make_tuple2(env, enif_make_atom(env, "error"),
|
| enif_make_atom(env, "sequence_not_monotonic"));
|
|
|
| h->segment_id = new_id;
|
| h->sequence = new_seq;
|
| h->tip_offset = 0;
|
| h->segments_rotated++;
|
|
|
| return enif_make_tuple2(env, enif_make_atom(env, "ok"), enif_make_uint64(env, 0));
|
| }
|
|
|
|
|
| static ERL_NIF_TERM nif_verify_chain(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
|
| {
|
| if (argc != 1) return enif_make_badarg(env);
|
| seb_wal_handle *h;
|
| if (!enif_get_resource(env, argv[0], wal_handle_type, (void**)&h))
|
| return enif_make_atom(env, "error");
|
| return enif_make_tuple2(env, enif_make_atom(env, "ok"),
|
| enif_make_uint64(env, h->events_sealed));
|
| }
|
|
|
|
|
| static ERL_NIF_TERM nif_worm_flush(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
|
| {
|
| if (argc != 1) return enif_make_badarg(env);
|
| seb_wal_handle *h;
|
| if (!enif_get_resource(env, argv[0], wal_handle_type, (void**)&h))
|
| return enif_make_atom(env, "error");
|
| (void)h;
|
| return enif_make_atom(env, "ok");
|
| }
|
|
|
|
|
| static ERL_NIF_TERM nif_get_state(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
|
| {
|
| if (argc != 1) return enif_make_badarg(env);
|
| seb_wal_handle *h;
|
| if (!enif_get_resource(env, argv[0], wal_handle_type, (void**)&h))
|
| return enif_make_atom(env, "error");
|
| return enif_make_tuple5(env,
|
| enif_make_uint64(env, h->segment_id),
|
| enif_make_uint64(env, h->sequence),
|
| enif_make_uint64(env, h->events_sealed),
|
| enif_make_uint64(env, h->segments_rotated),
|
| enif_make_uint64(env, h->tip_offset));
|
| }
|
|
|
|
|
| static ERL_NIF_TERM nif_get_tip_hash(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
|
| {
|
| if (argc != 1) return enif_make_badarg(env);
|
| seb_wal_handle *h;
|
| if (!enif_get_resource(env, argv[0], wal_handle_type, (void**)&h))
|
| return enif_make_atom(env, "error");
|
|
|
| ERL_NIF_TERM bin;
|
| uint8_t *buf = enif_make_new_binary(env, HASH_SIZE, &bin);
|
| memcpy(buf, h->tip_hash, HASH_SIZE);
|
| return enif_make_tuple2(env, enif_make_atom(env, "ok"), bin);
|
| }
|
|
|
|
|
| static ErlNifFunc nif_funcs[] = {
|
| {"init_kernel", 2, nif_init_kernel},
|
| {"append_event", 4, nif_append_event},
|
| {"rotate_segment", 3, nif_rotate_segment},
|
| {"verify_chain", 1, nif_verify_chain},
|
| {"worm_flush", 1, nif_worm_flush},
|
| {"get_state", 1, nif_get_state},
|
| {"get_tip_hash", 1, nif_get_tip_hash}
|
| };
|
|
|
| static int on_load(ErlNifEnv *env, void **priv, ERL_NIF_TERM info)
|
| {
|
| (void)priv; (void)info;
|
| wal_handle_type = enif_open_resource_type(env, NULL, "seb_wal_handle",
|
| wal_handle_dtor,
|
| ERL_NIF_RT_CREATE, NULL);
|
| return wal_handle_type ? 0 : -1;
|
| }
|
|
|
| ERL_NIF_INIT(seb_kernel_nif, nif_funcs, on_load, NULL, NULL, NULL)
|
|
|