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.
224 lines
12 KiB
224 lines
12 KiB
/* |
|
* ── member_sync — синхронизация участников чата ── |
|
* |
|
* Тонкая прослойка над merkle_sync, адаптированная под мемберов каналов. |
|
* Данные хранятся в таблицах: nodes, node_addresses, peers_<channel_id>. |
|
* Хеш мембера = SHA256(node_id || x25519 || ed25519 || join_sig || join_ts || addrs). |
|
* |
|
* ── Использование ── |
|
* |
|
* // разово: инициализация (из chat_sync_init) |
|
* member_sync_init(inst); |
|
* |
|
* // асинхронный запуск синхронизации (из cs_on_conn_up) |
|
* member_sync_start(inst, peer, ch_id, on_done, ch); |
|
* |
|
* // коллбэк: синхронизация завершена |
|
* static void on_done(uint64_t peer, const char* ch_id, int result, void* arg) { |
|
* struct channel_cache* ch = (struct channel_cache*)arg; |
|
* if (result == MT_OK) ch->synced = CS_SYNC_DONE; |
|
* } |
|
* |
|
* // добавление/обновление мембера (из chat_core_create_channel, cs_handle_welcome) |
|
* member_sync_put(inst, ch_id, node_id, x25519, ed25519, join_sig, join_ts, addrs, ac); |
|
* // дерево автоматически пересчитано |
|
* |
|
* // онлайн-статус (из cs_on_conn_up / cs_on_conn_down) |
|
* member_sync_set_online(inst, node_id, 1); // online |
|
* member_sync_set_online(inst, node_id, 0); // offline |
|
* |
|
* // отмена синхронизации (коллбэк НЕ вызывается) |
|
* member_sync_cancel(inst, peer, ch_id); |
|
* |
|
* // количество мемберов в канале |
|
* int n = member_sync_count(inst, ch_id); |
|
* |
|
* // разово: завершение (из chat_sync_destroy) |
|
* member_sync_destroy(inst); |
|
*/ |
|
|
|
#ifndef MEMBER_SYNC_H |
|
#define MEMBER_SYNC_H |
|
|
|
#include "merkle_sync.h" |
|
|
|
#ifdef __cplusplus |
|
extern "C" { |
|
#endif |
|
|
|
#include <stdint.h> |
|
#include <stddef.h> |
|
|
|
struct UTUN_INSTANCE; |
|
struct TOPO_GROUP; |
|
struct sqlite3; |
|
|
|
/* Результат применения рекорда мембера (compare+update по версиям двух блоков). */ |
|
#define MS_APPLY_CHANGED 0x01 /* блок стал новее — обновлён, нужен recompute + relay остальным */ |
|
#define MS_APPLY_STALE 0x02 /* у нас версия новее — отправить наш полный рекорд автору */ |
|
|
|
/* Распарсенный рекорд мембера (два подписанных блока). */ |
|
struct ms_member_rec { |
|
uint64_t node_id; |
|
const uint8_t* x25519; /* 32 байта, обязательно */ |
|
const uint8_t* ed25519; /* 32 байта, обязательно */ |
|
const uint8_t* join_sig; /* 64 байта, может быть NULL */ |
|
uint64_t join_ts; |
|
const uint8_t* update_sig; /* 64 байта, может быть NULL */ |
|
uint64_t update_ts; /* ver блока мембера */ |
|
const char* userinfo; /* JSON {"name":...}, может быть NULL */ |
|
const char* adm_tags; /* JSON владельца с "ver", может быть NULL */ |
|
const uint8_t* adm_tags_sig; /* 64 байта, может быть NULL */ |
|
int storage; /* 0/1, производная от adm_tags.storage */ |
|
}; |
|
|
|
/* |
|
* Инициализировать модуль: создаёт merkle_sync с коллбэками для мемберов |
|
* (ETCP сервис 0x31), регистрирует _on_node_updated для пересчёта дерева |
|
* при обновлении node info. |
|
*/ |
|
int member_sync_init(struct UTUN_INSTANCE* inst); |
|
|
|
/* Завершить модуль. */ |
|
void member_sync_destroy(struct UTUN_INSTANCE* inst); |
|
|
|
/* Подписаться на BGP node-события chat-группы (BGP → member_sync → node_props_changed). |
|
Вызывается при создании канала (chat_core_ensure_channel_ready). */ |
|
void member_sync_subscribe_group(struct UTUN_INSTANCE* inst, struct TOPO_GROUP* group); |
|
|
|
/* |
|
* Запустить синхронизацию мемберов канала ch_id с пиром peer. |
|
* Делегирует в merkle_sync_start(). |
|
*/ |
|
int member_sync_start(struct UTUN_INSTANCE* inst, uint64_t peer, |
|
const char* ch_id, merkle_sync_done_cb done_cb, void* arg); |
|
|
|
/* |
|
* Отменить синхронизацию. Делегирует в merkle_sync_cancel(). |
|
*/ |
|
void member_sync_cancel(struct UTUN_INSTANCE* inst, uint64_t peer, const char* ch_id); |
|
|
|
/* |
|
* Добавить/обновить мембера и пересчитать дерево. |
|
* |
|
* ch_id — ID канала |
|
* member_id — node_id участника |
|
* x25519 — X25519 публичный ключ (32 байта) |
|
* ed25519 — Ed25519 публичный ключ (32 байта) |
|
* join_sig — Ed25519 подпись join-сообщения (64 байта) |
|
* сообщение: ch_x25519(32) || ch_ed25519(32) || node_id(8LE) || x25519(32) || join_ts(8LE) |
|
* join_ts — NTP-синхронизированное время подписи (unix epoch seconds) |
|
* userinfo — метаданные узла (имя) в канале |
|
* |
|
* Пишет только в таблицу peers_<ch_id> (INSERT OR REPLACE). |
|
* nodes обновляется отдельно через topo_node_sqlite_node_update_verified(). |
|
* Вызывает merkle_sync_recompute_path() для пересчёта дерева. |
|
* |
|
* Возвращает 0 при успехе, -1 при ошибке. |
|
*/ |
|
int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id, |
|
uint64_t member_id, const uint8_t* x25519, |
|
const uint8_t* ed25519, |
|
const uint8_t* join_sig, uint64_t join_ts, |
|
const uint8_t* update_sig, uint64_t update_ts, |
|
const char* userinfo, |
|
const char* adm_tags, const uint8_t* adm_tags_sig, int storage); |
|
|
|
/* |
|
* Строит сообщение для update_sig блока A (мембер): |
|
* join_sig(64) || update_ts(8) || userinfo(с NUL) |
|
* Используется и для подписи, и для верификации — байты обязаны совпадать. |
|
* Возвращает длину сообщения или -1. |
|
*/ |
|
int member_sync_build_update_msg(const uint8_t* join_sig, uint64_t update_ts, |
|
const char* userinfo, |
|
uint8_t* out, int out_sz); |
|
|
|
/* |
|
* Строит сообщение для join_sig (каноническая раскладка 112 байт): |
|
* ch_x25519(32) || ch_ed25519(32) || node_id(8) || node_x25519(32) || join_ts(8) |
|
* Единственный источник истины для подписи и верификации join_sig во всех модулях. |
|
* Возвращает длину сообщения (112) или -1. |
|
*/ |
|
int member_sync_build_join_msg(const uint8_t* ch_x25519, const uint8_t* ch_ed25519, |
|
uint64_t node_id, const uint8_t* node_x25519, uint64_t join_ts, |
|
uint8_t* out, int out_sz); |
|
|
|
/* |
|
* Применить рекорд мембера с по-блочным сравнением версий: |
|
* - блок A (мембер): ver = update_ts (внутри эпохи join_ts), подписан ключом мембера; |
|
* - блок B (владелец): ver = adm_tags.ver, подписан канальным ключом (adm_tags_sig). |
|
* Каждый блок обновляется независимо, только если его ver увеличился. |
|
* Если ver пришедшего блока меньше локального — блок игнорируется и выставляется |
|
* MS_APPLY_STALE (нужно отправить нашу свежую запись автору from_peer). |
|
* Возвращает битовую маску MS_APPLY_*, <0 — ошибка. |
|
*/ |
|
int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, |
|
uint64_t from_peer, const struct ms_member_rec* m); |
|
|
|
/* |
|
* Верифицировать локальную запись мембера (чтение из БД). Единственный сценарий |
|
* удаления: подпись присутствует и невалидна, либо node_id != derive(x25519). |
|
* Отсутствие подписи = «не верифицировано» (self-add/плейсхолдер) — не битая. |
|
* Возвращает 1 если запись валидна/не подписана, 0 если битая. |
|
*/ |
|
int member_sync_verify_local_record(struct sqlite3* db, const char* ch_id, |
|
const struct ms_member_rec* m); |
|
|
|
/* Прогнать все записи канала: битую удалить + пересчитать хеши. Возвращает число удалённых. */ |
|
int member_sync_verify_and_purge(struct UTUN_INSTANCE* inst, const char* ch_id); |
|
|
|
/* Прогнать очистку битых записей по всем каналам (при init). */ |
|
void member_sync_verify_and_purge_all(struct UTUN_INSTANCE* inst); |
|
|
|
/* |
|
* Отправить наш полный рекорд мембера конкретному пиру (send-back при stale). |
|
* Возвращает 0 при успехе, -1 если нет сессии/соединения. |
|
*/ |
|
int member_sync_send_to(struct UTUN_INSTANCE* inst, const char* ch_id, |
|
uint64_t member_id, uint64_t target_peer); |
|
|
|
/* Количество мемберов в канале (SELECT COUNT из peers_<ch_id>). */ |
|
int member_sync_count(struct UTUN_INSTANCE* inst, const char* ch_id); |
|
|
|
/* Сериализовать одного мембера и отправить всем подключённым пирам через merkle_sync_broadcast. |
|
Вызывается после локального изменения данных (adm_tags, имя, адреса). */ |
|
void member_sync_broadcast_one(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t member_id); |
|
|
|
/* |
|
* Установить онлайн-статус узла (nodes.online = 0/1). |
|
* |
|
* ВАЖНО: онлайн-статус НЕ участвует в merkle sync — он не хешируется и не |
|
* версионируется (в _compute_member_hash поля online нет). Это только локальная |
|
* запись в БД. Рассылка online идёт отдельным лёгким push_update (MSG_ITEM_UPDATE), |
|
* вне дерева, и только если значение реально изменилось (возвращает 1). |
|
* |
|
* Возвращает 1 если значение изменилось, 0 если совпало. |
|
*/ |
|
int member_sync_set_online(struct UTUN_INSTANCE* inst, uint64_t node_id, int online); |
|
|
|
/* Получить хеш бакета из merkle_tree_hash (для тестов). */ |
|
const uint8_t* member_sync_get_hash(struct UTUN_INSTANCE* inst, |
|
const char* ch_id, uint8_t level, uint64_t prefix64); |
|
|
|
/* |
|
* Коллбэк: узел изменился по инициативе удалённого пира (через MSG_ITEM_UPDATE). |
|
* Вызывается из uasync-потока. Потребитель (chat_sync) может из него |
|
* постить GUI-события. |
|
*/ |
|
typedef void (*member_sync_node_updated_fn)(uint64_t node_id, int online); |
|
void member_sync_set_node_updated_cb(member_sync_node_updated_fn cb); |
|
|
|
/* |
|
* Коллбэк: изменились adm_tags любого узла (включая себя). |
|
* Вызывается при успешной обработке MSG_ITEM_UPDATE с adm_tags. |
|
* Многоподписочный — можно добавить несколько подписчиков. |
|
*/ |
|
typedef void (*node_props_changed_fn)(uint64_t node_id, const char* adm_tags, void* arg); |
|
void member_sync_add_props_cbk(node_props_changed_fn fn, void* arg); |
|
void member_sync_remove_props_cbk(node_props_changed_fn fn, void* arg); |
|
|
|
#ifdef __cplusplus |
|
} |
|
#endif |
|
#endif /* MEMBER_SYNC_H */
|
|
|