You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

250 lines
12 KiB

#ifndef MERKLE_SYNC_H
#define MERKLE_SYNC_H
#include <stdint.h>
#include <stddef.h>
#include <openssl/evp.h>
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_<ns> 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][join_sig:64][online:1][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);
};
/*
* Коллбэк завершения синхронизации.
*
* 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_route_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/set_online.
*/
void merkle_sync_recompute_path(struct UTUN_INSTANCE* inst, const char* ns, uint64_t key);
/* ── 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 */