Files
utun2/src/chat/member_sync.h
T

263 lines
19 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.
* Запись содержит блок участника, блок владельца и подпись дерева приглашений.
* Версии блоков сравниваются отдельно; apply_record/put меняют запись и Merkle-хеш атомарно.
* Очистка повреждённых записей владеет транзакцией канала и публикует удаления после commit.
* Локальный put запускает распространение после commit; start ждёт сверки с пиром.
* Участие в CHAT-группе определяется локальной таблицей peers_<channel_id>.
* Сетевые адреса принадлежат topo_node и не входят в блок участника.
* Модуль живёт внутри чат-сервиса; все операции и callbacks — в потоке uasync.
*/
#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;
/* Допуск автора отделён от ожидания ключей и отказа самой проверки. */
enum ms_author_status {
MS_AUTHOR_ERROR = -1, /* SQL/OpenSSL/аргументы: проверка не выполнена */
MS_AUTHOR_INACTIVE = 0, /* участник отсутствует, source!=0 или deleted=1 */
MS_AUTHOR_ACTIVE = 1, /* удостоверение полностью проверено */
MS_AUTHOR_PENDING = 2, /* ещё нет ключа или подтверждения приглашения */
MS_AUTHOR_INVALID = 3 /* неверная подпись, идентичность или формат удостоверения */
};
/* Проверить текущий допуск; возвращает MS_AUTHOR_*, key[32] меняется только при ACTIVE.
* Неактивность и ожидание зависимости не являются ошибками БД или доказательством порчи.
* inst/channel/key обязательны; channel — канонический десятичный ID канала. */
enum ms_author_status member_sync_author_key(struct UTUN_INSTANCE* inst, const char* channel,
uint64_t author, uint8_t key[32]);
/* Проверить source=0/deleted=0, привязку node_id/X25519 и доверенный Ed25519,
* join_sig и подпись непосредственного пригласившего (либо ключом канала для signed_by=0).
* Не обходит цепочку предков. inst/channel/key обязательны, channel — канонический uint64 decimal.
* 0 — ключ записан в key[32], -1 — отказ, key не меняется. */
int member_sync_verified_ed25519(struct UTUN_INSTANCE* inst, const char* channel, uint64_t author, uint8_t key[32]);
/* Результат применения рекорда мембера (compare+update по версиям двух блоков). */
#define MS_APPLY_CHANGED 0x01 /* запись и дерево обновлены */
#define MS_APPLY_STALE 0x02 /* локальная версия новее; обратный проход доставит её пиру */
#define MS_APPLY_FEEDBACK 0x04 /* изменилось удостоверение, доверенный ключ или deleted; индекс feedback обновлён */
#define MS_VERIFY_DEFERRED (-1) /* зависимость подписи ещё не получена */
#define MS_VERIFY_ERROR (-2) /* SQL/crypto/аргументы: проверка не выполнена */
/* 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; /* вход не используется: сохраняемое значение выводится из adm_tags.storage="yes" */
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 если невалидна, MS_VERIFY_DEFERRED если нет ключа,
* MS_VERIFY_ERROR при отказе SQL/crypto. Отсутствие ключа не доказывает порчу записи. */
int member_sync_verify_signature(struct sqlite3* db, const char* ch_id, const struct ms_member_rec* m);
/* Проверить наличие derive(x25519) в peers_<ch_id>; 1/0 (ошибки тоже дают 0).
* Не проверяет source, deleted, подписи или совпадение сохранённого ключа.
* Для допуска в CHAT-группу используется отдельный topo_node_sqlite_member_allowed. */
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_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). */
/* Идемпотентно подписать CHAT-группу; 0/-1, отказ регистрации явно возвращается. */
int member_sync_subscribe_group(struct UTUN_INSTANCE* inst, struct TOPO_GROUP* group);
/*
* Запустить синхронизацию мемберов канала ch_id с пиром peer.
* Сначала вызывает verify_and_purge, затем merkle_sync_start(MEMBERS).
* 0 — запрос принят; <0 — не принят, done_cb не вызывается (см. merkle_sync.h).
* Ошибка очистки отклоняет запрос до merkle_sync_start, без done callback.
*/
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(8) || x25519(32) || join_ts(8)
* node_id и join_ts копируются в порядке байтов хоста (см. member_sync_build_join_msg).
* 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)
* update_ts копируется в порядке байтов хоста.
* Используется и для подписи, и для верификации — байты обязаны совпадать.
* Возвращает длину сообщения или -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)
* node_id и join_ts копируются в порядке байтов хоста, без преобразования endian.
* Единственный источник истины для подписи и верификации 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).
* Блок A сравнивает сначала join_ts, затем update_ts; новая эпоха может снизить update_ts.
* Блок B обновляется только при увеличении adm_tags.ver (разбирается как int).
* Отсутствующее подтверждение приглашения может дополниться без увеличения версий.
* from_peer == inst->node_id отключает проверки идентичности/подписей блока A и дерева;
* подпись непустого блока B проверяется также для локальной записи.
* Если ver пришедшего блока меньше локального — блок игнорируется и выставляется
* MS_APPLY_STALE (локальная версия новее; её доставит обратный проход).
* При внешней транзакции caller после commit вызывает member_sync_record_committed
* для изменённой записи; до commit автоматических уведомлений нет.
* Возвращает битовую маску MS_APPLY_* (0 — без изменения индекса), <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 если доказанно битая, -1 при операционной ошибке. Только 0 разрешает удаление.
*/
int member_sync_verify_local_record(struct sqlite3* db, const char* ch_id,
const struct ms_member_rec* m);
/* Проверить source=0 записи канала и атомарно удалить доказанно повреждённые вместе с их индексом.
* Требуются initialized member_sync, общая БД в autocommit и канонический uint64 decimal ch_id.
* Чужая транзакция отклоняется до изменений; недостающие ключи/зависимости сохраняются.
* >=0 — число удалений после commit, -1 — отказ; до commit уведомления/mute/группа не меняются.
* Props callbacks можно снимать/добавлять и вызывать очистку повторно; stop/destroy — вне callbacks. */
int member_sync_verify_and_purge(struct UTUN_INSTANCE* inst, const char* ch_id);
/* Очистить все peers_*: 0/-1, остановиться на первом отказе. Атомарность — по каналу;
* ранее завершённые каналы сохраняют результат. Не держит SQLite cursor через callbacks. */
int member_sync_verify_and_purge_all(struct UTUN_INSTANCE* inst);
/* COUNT(*) всех строк, включая placeholders/deleted; неверный аргумент/ошибка SQL дают 0. */
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);
/* После внешнего commit уведомить MEMBERS, свойства и планировщик CHAT-группы;
* также переоценить допуск MESSAGES и ожидание общих ключей исторических авторов.
* changes — полная маска MS_APPLY_* из apply_record/put: FEEDBACK разрешает его уведомления.
* inst/channel обязательны; uasync, БД уже вне транзакции. Не вызывает apply callbacks. */
void member_sync_record_committed(struct UTUN_INSTANCE* inst, const char* channel, uint64_t node_id, unsigned changes);
#ifdef __cplusplus
}
#endif
#endif /* MEMBER_SYNC_H */