Files
utun2/src/chat/member_sync.h
T
evgeny 73fe99b33f dm: прямой обмен сообщениями (DM) + soundtouch + архитектура звонков
- src/dm/: dm_core/dm_crypto/dm_mailbox — прямой обмен сообщениями (DM)
- lib/soundtouch-master/: vendored SoundTouch (изменение темпа/высоты аудио)
- doc/dm_arch.md, doc/chat_call_arch.md: архитектура DM и звонков
- tests: test_dm.c, test_dm_e2e.c
- chat: рефакторинг chat_* модулей, member_sync, invite_build
- chatgui/chatgui-android: синхронизация с новым API чата
- lib/u_async, debug_config, utun_instance: сопутствующие изменения
2026-09-15 21:09:17 +03:00

247 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* ── member_sync — синхронизация участников чата ──
*
* Тонкая прослойка над merkle_sync, адаптированная под мемберов каналов.
* Данные хранятся в таблицах: nodes, node_addresses, peers_<channel_id>.
* Хеш мембера = SHA256(node_id || x25519 || ed25519 || join_sig || join_ts || update_sig || update_ts || adm_tags_len || adm_tags || adm_tags_sig).
*
* ── Использование ──
*
* // разово: инициализация (из 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, update_sig, update_ts, userinfo,
* adm_tags, adm_tags_sig, storage);
* // дерево автоматически пересчитано
*
* // отмена синхронизации (коллбэк НЕ вызывается)
* 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 */
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_<ch_id>. Возвращает 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 сервис 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);
/*
* Отменить все 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 (нужно отправить нашу свежую запись автору 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);
/* Получить хеш бакета из merkle_tree_hash (для тестов). */
const uint8_t* member_sync_get_hash(struct UTUN_INSTANCE* inst,
const char* ch_id, uint8_t level, uint64_t prefix64);
/*
* Коллбэк: изменились adm_tags любого узла (включая себя).
* channel_id — канал (namespace), в котором произошло изменение.
* Вызывается при успешной обработке MSG_ITEM_UPDATE с adm_tags.
* Многоподписочный — можно добавить несколько подписчиков.
*/
typedef void (*node_props_changed_fn)(uint64_t node_id, const char* adm_tags, const char* channel_id, void* arg);
void 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 */