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
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 */
|
|
|