#ifndef MERKLE_SYNC_H #define MERKLE_SYNC_H #include #include #include struct UTUN_INSTANCE; #define MT_MAX_LEVEL 5 #define MT_BUCKETS 32 #define MT_HASH_SIZE 32 /* * ── Архитектура ── * * merkle_sync — универсальный протокол синхронизации на Merkle-деревьях. * Группирует элементы (items) в префиксное дерево: 5 уровней × 32 бакета, * SHA256-хеши. Два пира обмениваются хешами уровней, находят различающиеся * бакеты и передают только их содержимое. * * merkle_sync не знает, что такое "элемент" — потребитель (member_sync) * предоставляет три коллбэка, описывающих модель данных: * * update_bucket_hash — перечислить элементы в диапазоне ключей, * вычислить хеш каждого и подать в SHA256-контекст * get_items — сериализовать элементы бакета в wire-формат * apply_items — десериализовать и сохранить полученные элементы * * Потребитель (chat_sync/chat_core) работает только с member_sync * и не видит merkle_sync напрямую: * * // ── разово ── * member_sync_init(inst); * * // ── запустил синхронизацию — забыл ── * member_sync_start(inst, peer, ch_id, on_done, my_ctx); * * // ── получил результат ── * static void on_done(uint64_t peer, const char* ch_id, int result, void* arg) { * if (result == MT_OK) printf("sync ok\n"); * if (result == MT_ERR_TIMEOUT) printf("timeout\n"); * } * * // ── изменил данные — дерево само пересчиталось ── * member_sync_put(inst, ch_id, node_id, x25519, ed25519, join_sig, addrs, ac); * member_sync_set_online(inst, node_id, 1); * * // ── отменил — коллбэк не вызовется ── * member_sync_cancel(inst, peer, ch_id); * * // ── разово ── * member_sync_destroy(inst); * * ── Формат дерева ── * * 5-уровневое префиксное дерево над 64-битными ключами (node_id). * Каждый уровень берёт 5 старших бит ключа: уровень 1 — биты 59-63, * уровень 5 — биты 39-63. Каждый узел дерева разбивается на 32 бакета * (по 5 бит = 32 комбинации). * * level 1 [0..31] корень: 32 бакета по 5 бит * level 2 [0..31]...[0..31] каждый — ещё 32 бакета * ... * level 5 [0..31].........[0..31] листья: 32^5 = 33M бакетов макс * * Хеш бакета = SHA256(хеш_элемента_1 || ... || хеш_элемента_N). * Бакет считается терминальным (leaf) на уровне 5 или если в нём < 8 элементов. * * ── Wire-протокол (сервис ETCP, id задаётся при init) ── * * Каждое сообщение: [svc_id:1][ns_len:1][ns:var][type:1][payload:var] * * MSG_HASHES (0x01): level,prefix,is_data, [bitmap+hashes | member_data] * MSG_REQUEST (0x02): count, [level,prefix,is_terminal]* * MSG_BATCH (0x03): count, [level,prefix,is_terminal,[member_data|bitmap+hashes]]* * * Алгоритм: * A → MSG_HASHES(level=1, bitmap+hashes всех 32 бакетов уровня 2) → B * B сравнивает со своим деревом, находит различающиеся бакеты * B → MSG_REQUEST(level=2, prefix=X, is_terminal) → A * A → MSG_BATCH(данные бакета) → B * B сохраняет, пересчитывает хеши * Рекурсивно для подбакетов, пока хеши не совпадут. * * ── Сессии ── * * Таймаут 10 секунд, 3 ретрая. При таймауте сессия перезапускает * MSG_HASHES с level=1. После 3 ретраев — done_cb с MT_ERR_TIMEOUT. */ /* ── Data model callbacks ── */ struct merkle_sync_data_ops { /* * Подать хеши всех элементов в префиксном диапазоне в sha_ctx. * * ctx — data_ctx, переданный в merkle_sync_init * ns — namespace (например channel_id) * level — уровень дерева (1..5) * prefix64 — префикс ключа (старшие level*5 бит, остальные нули) * sha_ctx — уже инициализирован (EVP_DigestInit_ex), потребитель * делает только EVP_DigestUpdate(sha_ctx, item_hash, 32) * для каждого элемента * * Возвращает количество элементов (0 = бакет пуст, -1 = ошибка). * * Пример реализации для мемберов: * SELECT ... FROM peers_ JOIN nodes * WHERE (node_id & mask) == prefix64 ORDER BY node_id * для каждой строки: _compute_member_hash() → EVP_DigestUpdate() */ int (*update_bucket_hash)(void* ctx, const char* ns, uint8_t level, uint64_t prefix64, EVP_MD_CTX* sha_ctx); /* * Сериализовать элементы бакета в wire-формат. * * buf — буфер для записи (выделяет merkle_sync, мин. 64K) * *len — [in] размер буфера, [out] записанный размер * * Wire-формат для мемберов: * [count:2][node_id:8][x25519:32][ed25519:32][flags:1]([join_sig:64][join_ts:8])[update_sig:64][update_ts:8][userinfo_len:1][userinfo:var][addr_cnt:1][addrs:var]... * * Возвращает 0 при успехе, <0 при ошибке, -2 если буфер мал. */ int (*get_items)(void* ctx, const char* ns, uint8_t level, uint64_t prefix, uint8_t pbytes, uint8_t* buf, size_t* len); /* * Десериализовать и сохранить элементы из wire-формата. * * data, len — данные в том же формате, что выдаёт get_items * * Вызывается при приёме MSG_HASHES(is_data=1) или MSG_BATCH(is_terminal=1). * Должна сохранить элементы в БД и вызвать merkle_sync_recompute_path * для каждого изменённого ключа (через member_sync_put). * * Возвращает 0 при успехе, <0 при ошибке. */ int (*apply_items)(void* ctx, const char* ns, const uint8_t* data, size_t len); /* * Применить лёгкое обновление (не влияющее на Merkle-хеш). * Вызывается при приёме MSG_ITEM_UPDATE(0x04) — online-статус и т.п. * * ns — namespace (channel_id) * key — node_id изменившегося узла * type — тип обновления (0x01 = online-статус) * data,len — данные обновления (type=0x01: [online:1]) * * Возвращает 0 при успехе, <0 при ошибке. */ int (*apply_update)(void* ctx, const char* ns, uint64_t key, uint8_t type, const uint8_t* data, size_t len); }; /* * Коллбэк завершения синхронизации. * * peer — node_id пира * ns — namespace (channel_id) * result — MT_OK (0) = успех, MT_ERR_TIMEOUT (-1) = исчерпаны ретраи * arg — пользовательский контекст * * При отмене через merkle_sync_cancel() коллбэк НЕ вызывается. */ #define MT_OK 0 #define MT_ERR_TIMEOUT -1 typedef void (*merkle_sync_done_cb)(uint64_t peer, const char* ns, int result, void* arg); /* ── Lifecycle ── */ /* * Инициализировать модуль. Создаёт таблицу merkle_tree_hash, биндит * ETCP-сервис svc_id (напр. 0x31), запускает фоновую проверку (bg_timer). * Вызывается один раз, обычно из member_sync_init(). * * inst — экземпляр uTun (нужен для etcp_send, uasync, sqlite3) * svc_id — ID сервиса на ETCP-роутере * ops — коллбэки модели данных (update_bucket_hash, get_items, apply_items) * data_ctx — прозрачный контекст, передаваемый в коллбэки первым аргументом * * Возвращает 0 при успехе, -1 при ошибке. */ int merkle_sync_init(struct UTUN_INSTANCE* inst, uint8_t svc_id, const struct merkle_sync_data_ops* ops, void* data_ctx); /* * Завершить модуль. Отвязывает ETCP-сервис, останавливает bg_timer, * удаляет все активные сессии (коллбэки НЕ вызываются). */ void merkle_sync_destroy(struct UTUN_INSTANCE* inst); /* ── Async sync ── */ /* * Запустить синхронизацию namespace ns с пиром peer. * Отправляет MSG_HASHES(level=1) и запускает таймаут 10s. * * Если сессия для (peer, ns) уже существует — перезапускает её * с новым коллбэком (старый коллбэк теряется без вызова). * * Возвращает 0 при успехе, -1 при ошибке. */ int merkle_sync_start(struct UTUN_INSTANCE* inst, uint64_t peer, const char* ns, merkle_sync_done_cb done_cb, void* arg); /* * Отменить синхронизацию. Удаляет сессию, done_cb НЕ вызывается. * Безопасно вызывать, если сессия не существует. */ void merkle_sync_cancel(struct UTUN_INSTANCE* inst, uint64_t peer, const char* ns); /* ── Recompute tree path after data change ── */ /* * Пересчитать Merkle-дерево для ключа key — все уровни от 1 до 5. * Вызывается после изменения данных: merkle_sync сам не знает, * когда данные изменились — потребитель должен вызвать явно. * member_sync делает это внутри put/del. */ void merkle_sync_recompute_path(struct UTUN_INSTANCE* inst, const char* ns, uint64_t key); /* * Отправить лёгкое обновление всем synced-пирам в namespace ns. * Не влияет на Merkle-дерево — используется для online-статуса и т.п. * * ns — namespace (channel_id) * key — node_id изменившегося узла * type — тип обновления (0x01 = online-статус) * data,len — данные обновления */ void merkle_sync_push_update(struct UTUN_INSTANCE* inst, const char* ns, uint64_t key, uint8_t type, const uint8_t* data, size_t len); /* ── Background consistency check ── */ /* * Проверить консистентность одного случайного level-5 бакета в ns. * Пересчитывает хеш из данных, сравнивает с сохранённым в merkle_tree_hash. * При расхождении — пересчитывает весь путь вверх до корня. * Возвращает 1 если были изменения, 0 если совпало, -1 ошибка. */ int merkle_sync_bg_check(struct UTUN_INSTANCE* inst, const char* ns); /* ── Test helper ── */ /* * Получить сохранённый хеш бакета из merkle_tree_hash. * Возвращает указатель на статический буфер (32 байта), перезаписывается * при следующем вызове. Если бакет не найден — возвращает нулевой хеш. */ const uint8_t* merkle_sync_get_hash(struct UTUN_INSTANCE* inst, const char* ns, uint8_t level, uint64_t prefix64); /* ── Prefix arithmetic (pure, exported for convenience) ── */ /* * Выделить level*5 старших бит из 64-битного ключа. * Уровень 1: биты 59-63 (сдвиг 59) * Уровень 5: биты 39-63 (сдвиг 39) * Пример: merkle_sync_level_prefix(0x1234567890ABCDEF, 1) = 0x1000000000000000 */ uint64_t merkle_sync_level_prefix(uint64_t key, uint8_t level); /* * Количество байт, необходимое для хранения префикса уровня level. * Уровень 1: 1 байт (5 бит), уровень 4: 3 байта (20 бит), уровень 5: 4 байта (25 бит). */ uint8_t merkle_sync_prefix_bytes(uint8_t level); #endif /* MERKLE_SYNC_H */