/* * member_sync — подписанные записи участников каналов и их обмен через merkle_sync. * Запись содержит блок участника, блок владельца и подпись дерева приглашений. * Версии блоков сравниваются отдельно; запись и Merkle-хеш меняются атомарно. * Локальный put запускает распространение после commit; start ждёт сверки с пиром. * Участие в CHAT-группе определяется локальной таблицей peers_. * Сетевые адреса принадлежат topo_node и не входят в блок участника. * Модуль живёт внутри чат-сервиса; все операции и callbacks — в потоке uasync. */ #ifndef MEMBER_SYNC_H #define MEMBER_SYNC_H #include "merkle_sync.h" #ifdef __cplusplus extern "C" { #endif #include #include struct UTUN_INSTANCE; struct TOPO_GROUP; struct sqlite3; /* Результат применения рекорда мембера (compare+update по версиям двух блоков). */ #define MS_APPLY_CHANGED 0x01 /* запись и дерево обновлены */ #define MS_APPLY_STALE 0x02 /* локальная версия новее; обратный проход доставит её пиру */ /* UTF-8 JSON bytes, excluding NUL. A complete record fits in one Merkle page. */ #define MS_ADM_TAGS_MAX 8192 #define MS_USERINFO_MAX 1024 /* Байты для подписи владельца: tags без NUL, затем node_id[8] в порядке байтов хоста. * Возвращает длину или -1, если данные/буфер не подходят. */ int member_sync_build_owner_msg(uint64_t node_id, const char* tags, uint8_t* out, size_t capacity); /* Представление записи; все указатели заимствованы на время вызова. */ struct ms_member_rec { uint64_t node_id; /* derive(x25519), ключ записи участника */ const uint8_t* x25519; /* 32 байта, обязательно */ const uint8_t* ed25519; /* 32 байта, обязательно */ const uint8_t* join_sig; /* 64 байта, может быть NULL */ uint64_t join_ts; /* эпоха вступления, Unix seconds */ 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 */ uint64_t signed_by; /* дерево приглашений: 0 = корень (подписан ключом группы), иначе node_id инвайтера */ const uint8_t* signature; /* 64 байта, Ed25519 подпись x25519||"group_join" ключом родителя (или канальным ed25519) */ }; /* ── Дерево приглашений (подпись pubkey узла) ── */ #define MEMBER_SIGN_SUFFIX "group_join" #define MEMBER_SIGN_SUFFIX_LEN 10 #define MEMBER_SIGN_MSG_SIZE (SC_PUBKEY_SIZE + MEMBER_SIGN_SUFFIX_LEN) /* 42 */ /* Собрать сообщение подписи: x25519(32) || "group_join". Возвращает 42 или -1. */ int member_sync_build_sign_msg(const uint8_t* x25519, uint8_t* out, int out_sz); /* Подписать pubkey узла: sign(signer_ed_priv, x25519||"group_join"). Возвращает 0/-1. */ int member_sync_sign_pubkey(const uint8_t* signer_ed_priv, const uint8_t* x25519, uint8_t sig[64]); /* Верифицировать подпись дерева мембера: * signed_by==0 → ключ канала (ch_ed25519), иначе ed25519 pubkey узла signed_by. * Возвращает 1 если подпись валидна, 0 если невалидна, -1 если подписанта нет в БД * (родитель ещё не пришёл по merkle — верификацию откладываем). */ int member_sync_verify_signature(struct sqlite3* db, const char* ch_id, const struct ms_member_rec* m); /* Проверить, что x25519 pubkey (из ETCP-соединения) есть в таблице мемберов канала. * node_id = derive(x25519) → SELECT 1 FROM peers_. Возвращает 1/0. */ int member_sync_validate_pubkey(struct UTUN_INSTANCE* inst, const char* ch_id, const uint8_t* x25519_pubkey); /* ── Коллбэк приёма/применения рекорда мембера (ready-хендлинг join) ── */ typedef void (*member_apply_cbk_fn)(const char* ch_id, uint64_t node_id, const uint8_t* x25519, uint64_t signed_by, const uint8_t* signature, void* arg); void member_sync_add_apply_cbk(struct UTUN_INSTANCE* inst, member_apply_cbk_fn fn, void* arg); void member_sync_remove_apply_cbk(struct UTUN_INSTANCE* inst, member_apply_cbk_fn fn, void* arg); /* * Инициализировать модуль: создаёт merkle_sync с коллбэками для мемберов * (ETCP_RT_ID_MEMBER_SYNC), проверяет локальные записи, восстанавливает деревья * и подписывается на события CHAT-групп. 0/-1. */ 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); /* * Отменить все merkle-сессии канала (локальное удаление канала). * Делегирует в merkle_sync_cancel_ns(). */ void member_sync_cancel_channel(struct UTUN_INSTANCE* inst, const char* ch_id); /* * Добавить/обновить мембера (локальная запись, доверенная) и пересчитать дерево. * Делегирует в member_sync_apply_record() с from_peer = inst->node_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) * update_sig — подпись блока A (64 байта), update_ts — версия блока A * userinfo — метаданные узла (имя) в канале * adm_tags — атрибуты владельца (JSON с ver/storage), adm_tags_sig — подпись канальным ключом * storage — производная от adm_tags.storage * * Пишет блоки через topo_node_sqlite_member_block_put/member_owner_put, узел — * через topo_node_sqlite_node_update_verified, затем merkle_sync_recompute_path(). * * Возвращает маску MS_APPLY_* (0 = no-op) или -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, uint64_t signed_by, const uint8_t* signature); /* * Строит сообщение для 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 (локальная версия новее; её доставит обратный проход). * Возвращает битовую маску 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); /* Количество мемберов в канале. */ int member_sync_count(struct UTUN_INSTANCE* inst, const char* ch_id); /* * Коллбэк: изменились adm_tags любого узла (включая себя). * channel_id — канал (namespace), в котором произошло изменение. * Уведомляет об изменении свойств после commit и при обновлении состояния узла в BGP. * Многоподписочный — можно добавить несколько подписчиков. */ typedef void (*node_props_changed_fn)(uint64_t node_id, const char* adm_tags, const char* channel_id, void* arg); /* 0 — зарегистрирован, -1 — неверные аргументы/память; потребитель может откатить свой init. */ int member_sync_add_props_cbk(struct UTUN_INSTANCE* inst, node_props_changed_fn fn, void* arg); void member_sync_remove_props_cbk(struct UTUN_INSTANCE* inst, node_props_changed_fn fn, void* arg); #ifdef __cplusplus } #endif #endif /* MEMBER_SYNC_H */