157 lines
12 KiB
C
157 lines
12 KiB
C
/*
|
||
* 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 */
|