263 lines
19 KiB
C
263 lines
19 KiB
C
/*
|
||
* 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 */
|