Files
utun2/src/chat/chat_join.h
T

157 lines
12 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.
/*
* chat_join — добавление участника канала через узел подключения C и инвайтера A.
* Модуль регистрирует invite-ключи, пересылает запрос к A и сохраняет подписанного
* участника. Прямой обмен с новым узлом J выполняет chat_sync; Merkle разносит запись.
*
* Протокол (A и C могут быть одним узлом):
*
* инвайтер A connection-узел C джойнер J
* │ KEY_REGISTER(key,ch) │ │
* ├──────etcp_router─────────▶│ хранит {key→A, 10 мин} │
* │◀── KEY_REGISTER_ACK ──────┤ │
* │ теперь ссылка готова │ │
* │ │◀─── JOIN_INFO_REQ(key) ──────┤ (J подключился к C по ссылке)
* │ │── JOIN_INFO_RESP ───────────▶│ (ch_x25519, ch_ed25519, ch_sig, C-мембер)
* │ │◀─── JOIN_REQUEST ────────────┤ (key + полный набор мембера J)
* │ │ валиден ключ → только pending, БЕЗ BGP new_conn
* │◀── REQUEST_FWD ───────────┤ │
* │ verif join_sig, node_id=derive(x25519) │
* │ member_sync_put(J, signed_by=A, signature=sign(A, J_x25519||"group_join"))
* │ ── merkle broadcast ──▶ (рекорд J дошёл по merkle) │
* │ │── JOIN_READY(ch) ────────────▶│ добавление подтверждено у C
*
* КОНТРАКТ ЧЛЕНСТВА (обязателен для всех путей подключения):
* - A и C уже участвуют в канале; A может совпадать с C.
* - NCD даёт транспорт J–C для протокола добавления. Наличие транспорта,
* invite-ключа или локального TOPO_GROUP не даёт участия J в BGP-группе.
* Инициатор удерживает отдельный NCD handle до результата/таймаута добавления;
* закрывать его на транспортном UP, рассчитывая на владельца BGP, запрещено.
* - После проверки JOIN_REQUEST инвайтер сохраняет мембера через member_sync.
* Merkle распространяет запись между существующими участниками.
* - Каждый узел разрешает групповую сессию J после принятия и сохранения
* записи J в СВОЕЙ таблице участников. Подтверждение всех узлов не требуется.
* - Проверка invite-ключа разрешает только обработку запроса добавления.
* Временного членства и раннего BGP new_conn до сохранения мембера нет.
* - JOIN_READY означает наличие записи J у C. Это не подтверждение доставки
* всем узлам и не окончание обмена топологией. BGP JOIN_GROUP / TABLE_COMPLETE
* из topo_group.h относятся к отдельной групповой сессии.
*
* ПОРЯДОК И ПРОВЕРКИ:
* 1. A регистрирует (канал, join_key) -> A на C на JOIN_KEY_TTL_SECONDS.
* Ссылку можно публиковать только после успешного KEY_REGISTER_ACK.
* При A==C регистрация и обработка запроса локальные.
* 2. J открывает NCD к C; C проверяет ключ из JOIN_INFO_REQ и возвращает
* подписанное описание канала и свою запись участника. J проверяет ответ.
* Создание локальной инфраструктуры канала ещё не добавляет J на других узлах.
* 3. C проверяет JOIN_REQUEST: ключ и привязку идентичности J к транспорту.
* Запрос идёт к A по существующему маршруту канала; маршрут через J не нужен.
* 4. A проверяет запись, подписывает приглашение и сохраняет мембера.
* После локального commit или принятия записи по Merkle C отправляет JOIN_READY.
* 5. JOIN_GROUP, пришедший раньше записи мембера, не создаёт сессию у получателя.
* После commit member_sync повторно инициирует групповое согласование
* с этим пиром, если транспорт уже установлен. Новый ETCP-коннект не требуется.
*
* ОШИБКИ, ПОВТОРЫ И ОТМЕНА:
* - Неверный/истёкший ключ, несовпадение идентичности или неверная подпись
* не создают мембера. Ошибка/таймаут не дают временных прав.
* - Повторная запись проходит обычную проверку подписей и версий member_sync;
* повтор не создаёт второго мембера или второго владельца групповой сессии.
* - Ответ относится к ожидаемым каналу и пиру; ответ другой попытки не должен
* завершать текущее ожидание. UP другого пира не меняет адресата приглашения.
* - Отмена ожидания не откатывает уже сохранённую/реплицированную запись.
* Удаление локального канала отменяет его ожидания и подключения.
* - Recovery восстанавливает сессии существующих участников и не использует
* invite-ключ как замену локальной таблицы участников.
*
* J <-> C: ETCP_RT_ID_CHAT_SYNC, заголовок [service:1][group_id:8][type:1], payload
* описан в chat_sync.h. A <-> C: ETCP_RT_ID_JOIN через etcp_router с подписью
* и шифрованием; subcommands и payload описаны ниже.
*
* Модуль отвечает только за маршрутизируемую (etcp_router) часть:
* - регистрация ключа (инвайтер → connection),
* - форвард join-запроса (connection → инвайтер),
* - обработка на инвайтере: верификация + добавление мембера + подпись дерева.
* Прямой обмен (джойнер ↔ connection, JOIN_INFO_REQ/RESP, JOIN_REQUEST, JOIN_READY)
* и pending-трекинг готовности — в chat_sync.c.
*/
#ifndef CHAT_JOIN_H
#define CHAT_JOIN_H
#include <stdint.h>
#include <stddef.h>
#include "member_sync.h"
struct UTUN_INSTANCE;
#ifdef __cplusplus
extern "C" {
#endif
/* etcp_router service id (connection ↔ инвайтер): ETCP_RT_ID_JOIN (см. etcp_api.h) */
/* subcommands (первый байт payload etcp_router) */
#define JOIN_SUBCMD_KEY_REGISTER 0x01 /* инвайтер → connection: {join_key:8} */
#define JOIN_SUBCMD_REQUEST_FWD 0x02 /* connection → инвайтер: {join_key:8, member...} */
#define JOIN_SUBCMD_KEY_REGISTER_ACK 0x03 /* connection → инвайтер: {join_key:8, status:1}; 0=stored, 1=rejected */
#define JOIN_KEY_TTL_SECONDS 600 /* 10 минут хранения ключа на connection-узле */
#define JOIN_REGISTER_TIMEOUT_TB 50000 /* 5 секунд на подтверждение */
enum chat_join_result { CHAT_JOIN_OK = 0, CHAT_JOIN_ERROR = -1, CHAT_JOIN_TIMEOUT = -2, CHAT_JOIN_CANCELLED = -3 };
struct chat_join_registration;
typedef void (*chat_join_registered_fn)(void* arg, int result);
/* Полный набор мембера джойнера (без подписи дерева — её ставит инвайтер). */
struct join_member_data {
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; /* версия подписанного блока участника */
char userinfo[MS_USERINFO_MAX + 1]; /* JSON, null-terminated */
};
/* Сериализовать member-блок (без join_key). Возвращает 0 и *out_len, -1 если не влез. */
int chat_join_serialize_member(const struct join_member_data* m,
uint8_t* out, size_t out_sz, size_t* out_len);
/* Разобрать member-блок. Указатели ссылаются на data (не копируют). 0=ок, -1=ошибка. */
int chat_join_parse_member(const uint8_t* data, size_t len, struct join_member_data* m);
/* Инициализация: etcp_router_bind(ETCP_RT_ID_JOIN). */
int chat_join_init(struct UTUN_INSTANCE* inst);
/* Снять обработчик, освободить ключи; ожидающие регистрации получают CANCELLED. */
void chat_join_destroy(struct UTUN_INSTANCE* inst);
/* Локальное приглашение (инвайтер == connection). 0=stored, -1=ошибка. */
int chat_join_store_local_key(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key);
/* Все вызовы в uasync-потоке. NULL=ошибка запуска, callback не вызывается.
* Иначе callback ровно один раз, после возврата register_key (также для self).
* Handle действителен до callback. cancel/destroy вызывают callback синхронно с CANCELLED. */
struct chat_join_registration* chat_join_register_key(struct UTUN_INSTANCE* inst, uint64_t channel_id,
uint64_t target_node_id, uint64_t join_key, chat_join_registered_fn callback, void* arg);
void chat_join_cancel_registration(struct chat_join_registration* registration);
/* Connection-узел: найти инвайтера по ключу. 0 = нет/истёк. */
uint64_t chat_join_lookup_inviter(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key);
/* Connection-узел: форварднуть join-запрос инвайтеру по etcp_router. 0=ок, -1=ошибка. */
int chat_join_forward_request(struct UTUN_INSTANCE* inst, uint64_t channel_id,
uint64_t inviter_id, uint64_t join_key,
const struct join_member_data* m);
/* Инвайтер (локально, когда connection==инвайтер, или из recv REQUEST_FWD):
* верификация join_sig + добавление мембера + подпись дерева. */
void chat_join_process_request(struct UTUN_INSTANCE* inst, uint64_t group_id,
uint64_t from_node, uint64_t join_key,
const uint8_t* member_data, size_t member_len);
#ifdef __cplusplus
}
#endif
#endif /* CHAT_JOIN_H */