- 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: сопутствующие изменения
247 lines
14 KiB
C
247 lines
14 KiB
C
/*
|
||
* ── 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 */
|