From 3dda701c67f7e8277e8197a32fe2f4ccfe016a6f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 21 Jul 2026 00:11:50 +0300 Subject: [PATCH] =?UTF-8?q?schema:=20rm=20is=5Fdm,=20rename=20name?= =?UTF-8?q?=E2=86=92userinfo=20(json=5Fflat),=20fix=20join=5Fmsg=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove is_dm column from channels table (dm detection by member count) - Rename member name column to userinfo — extensible json_flat format - Add json_flat.c/h library for compact JSON serialization - Fix join_sig message format: use ch_x25519+ch_ed25519 instead of channel_id string - Remove name from join_msg body (now in userinfo via json_flat) - Update chat_core, chat_sync, member_sync, db_manager to new format - Add topo_strategy.txt — архитектурные заметки по топологии --- lib/Makefile.am | 2 + lib/json_flat.c | 87 +++++++++++++ lib/json_flat.h | 25 ++++ src/conn_mgr.c | 25 ---- src/conn_mgr.h | 21 +-- src/conn_mgr_doc.md | 11 +- src/route_ping.h | 23 ++++ src/topo_group.c | 4 +- src/topo_group_doc.md | 176 ++++++++++++++------------ src/topo_node.c | 8 +- src/topo_node.h | 1 - src/topo_node_sqlite.c | 102 +++++++-------- src/topo_node_sqlite.h | 6 +- src/topo_strategy.txt | 89 +++++++++++++ tests/test_conn_mgr.c | 12 -- tools/chatgui/db/db_manager.cpp | 19 ++- tools/chatgui/db/db_manager.h | 2 - tools/chatgui/doc/db_schema.md | 2 - tools/chatgui/libutun/CMakeLists.txt | 1 + tools/chatgui/src/mainwindow.cpp | 1 - tools/chatgui/transport/chat_core.c | 28 ++-- tools/chatgui/transport/chat_core.h | 1 - tools/chatgui/transport/chat_sync.c | 147 +++++++++++---------- tools/chatgui/transport/member_sync.c | 18 ++- tools/chatgui/transport/member_sync.h | 6 +- tools/chatgui/transport/merkle_sync.h | 2 +- 26 files changed, 494 insertions(+), 325 deletions(-) create mode 100644 lib/json_flat.c create mode 100644 lib/json_flat.h create mode 100644 src/topo_strategy.txt diff --git a/lib/Makefile.am b/lib/Makefile.am index 1e976184..d1d0d951 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -27,6 +27,8 @@ libuasync_a_SOURCES = \ radix.h \ serialize.c \ serialize.h \ + json_flat.c \ + json_flat.h \ tcp_io.c \ tcp_io.h \ sqlite3.c \ diff --git a/lib/json_flat.c b/lib/json_flat.c new file mode 100644 index 00000000..ace4925e --- /dev/null +++ b/lib/json_flat.c @@ -0,0 +1,87 @@ +#include "json_flat.h" +#include + +#define SKIP_WS(p, end) while ((p) < (end) && (*(p) == ' ' || *(p) == '\t' || *(p) == '\n' || *(p) == '\r')) (p)++ +#define CHECK(p, end) if ((p) >= (end)) return -1 + +static int json_read_string(const char** pp, const char* end, char* out, size_t out_sz) { + const char* p = *pp; + CHECK(p, end); + if (*p != '"') return -1; p++; + size_t n = 0; + while (p < end && *p != '"') { + char ch = *p; + if (*p == '\\') { p++; CHECK(p, end); + switch (*p) { + case '"': ch = '"'; break; + case '\\': ch = '\\'; break; + case '/': ch = '/'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + default: return -1; + } + } + if (out && n < out_sz - 1) out[n++] = ch; + p++; + } + CHECK(p, end); /* closing quote */ + p++; + if (out && n < out_sz) out[n] = '\0'; + else if (out && out_sz > 0) out[out_sz - 1] = '\0'; + *pp = p; + return 0; +} + +static int json_parse_pair(const char** pp, const char* end, + json_flat_cb cb, void* arg) { + const char* p = *pp; + SKIP_WS(p, end); CHECK(p, end); + if (*p == '}') { *pp = p; return 0; } + + char key[128], val[256]; + if (json_read_string(&p, end, key, sizeof(key)) != 0) return -1; + SKIP_WS(p, end); CHECK(p, end); + if (*p != ':') return -1; p++; + SKIP_WS(p, end); CHECK(p, end); + if (json_read_string(&p, end, val, sizeof(val)) != 0) return -1; + + if (cb && cb(key, val, arg) != 0) { *pp = p; return 1; } + + SKIP_WS(p, end); CHECK(p, end); + if (*p == ',') p++; + + *pp = p; + return 0; +} + +int json_flat_parse(const char* json, json_flat_cb cb, void* arg) { + if (!json) return -1; + const char* p = json, *end = json + strlen(json); + SKIP_WS(p, end); CHECK(p, end); + if (*p != '{') return -1; p++; + + for (;;) { + int rc = json_parse_pair(&p, end, cb, arg); + if (rc < 0) return -1; + if (rc > 0) return 0; /* callback stopped */ + SKIP_WS(p, end); CHECK(p, end); + if (*p == '}') return 0; + } +} + +struct get_ctx { const char* key; char* out; size_t sz; int found; }; + +static int get_cb(const char* k, const char* v, void* arg) { + struct get_ctx* g = (struct get_ctx*)arg; + if (strcmp(k, g->key) == 0) { if (g->out) { size_t vl = strlen(v); + size_t cp = vl < g->sz - 1 ? vl : g->sz - 1; memcpy(g->out, v, cp); g->out[cp] = '\0'; } + g->found = 1; return 1; } + return 0; +} + +int json_flat_get(const char* json, const char* key, char* value_out, size_t value_sz) { + struct get_ctx g = { key, value_out, value_sz, 0 }; + if (json_flat_parse(json, get_cb, &g) != 0) return -1; + return g.found ? 0 : -1; +} diff --git a/lib/json_flat.h b/lib/json_flat.h new file mode 100644 index 00000000..5e636497 --- /dev/null +++ b/lib/json_flat.h @@ -0,0 +1,25 @@ +/** + * @file json_flat.h + * @brief Парсер плоского JSON без вложенности: {"key":"value",...} + * + * Только строковые значения. Без malloc, без зависимостей. + */ + +#ifndef JSON_FLAT_H +#define JSON_FLAT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*json_flat_cb)(const char* key, const char* value, void* arg); + +int json_flat_parse(const char* json, json_flat_cb cb, void* arg); +int json_flat_get(const char* json, const char* key, char* value_out, size_t value_sz); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/conn_mgr.c b/src/conn_mgr.c index d60d50b2..7b97c217 100644 --- a/src/conn_mgr.c +++ b/src/conn_mgr.c @@ -201,7 +201,6 @@ int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, uint32_t idle_ entry->state = CONN_MGR_STATE_CONNECTED; entry->conn_type = CONN_TYPE_DIRECT; entry->idle_timeout_ms = idle_timeout_ms; - entry->alien = target->alien; entry->last_traffic_tb = get_time_tb(); { struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node)); if (cn) { cn->cb = cb; cn->arg = cb_arg; entry->cb_list = cn; } } @@ -213,7 +212,6 @@ int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, uint32_t idle_ entry = cm_ensure_entry(mgr, node_id); if (!entry) return CONN_MGR_ERR_INTERNAL; entry->state = CONN_MGR_STATE_CONNECTING; - entry->alien = target->alien; { struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node)); if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; } } entry->idle_timeout_ms = idle_timeout_ms; @@ -269,29 +267,6 @@ int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id, uint8_t* out_sta return CONN_MGR_OK; } -int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len) { - if (!mgr || !nodeinfo_data || len < sizeof(struct TOPO_NODE)) return CONN_MGR_ERR_INTERNAL; - struct TOPO_GROUP* group = topo_groups_get_default(mgr->instance->topo_groups); - if (!group) return CONN_MGR_ERR_INTERNAL; - const struct TOPO_NODE* src = (const struct TOPO_NODE*)nodeinfo_data; - uint64_t node_id = src->node_id; - struct TOPO_NODEQ* existing = topo_node_find_by_id(group, node_id); - if (existing) { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node 0x%016llx already exists, ignoring", (unsigned long long)node_id); return CONN_MGR_OK; } - struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); - if (!ni) return CONN_MGR_ERR_INTERNAL; - memcpy(ni, nodeinfo_data, len < sizeof(*ni) ? len : sizeof(*ni)); - ni->ref_count = 1; - struct TOPO_NODEQ* nq = (struct TOPO_NODEQ*)queue_entry_new(sizeof(struct TOPO_NODEQ)); - if (!nq) { u_free(ni); return CONN_MGR_ERR_INTERNAL; } - nq->node = ni; topo_node_ref(ni); - nq->hash_node_id = ni->node_id; - nq->alien = 1; nq->dirty = 0; nq->last_ver = ni->ver; - nq->connectivity.ping_req_time = 0; - if (group->nodes) queue_data_put_with_index(group->nodes, &nq->ll); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: added alien node 0x%016llx", (unsigned long long)node_id); - return CONN_MGR_OK; -} - int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry) { if (!mgr || !entry) return -1; struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id); diff --git a/src/conn_mgr.h b/src/conn_mgr.h index 2e68e80c..63231bd5 100644 --- a/src/conn_mgr.h +++ b/src/conn_mgr.h @@ -49,9 +49,7 @@ struct TOPO_GROUP; * дополнительный callback в список * 4. conn_mgr_disconnect_node() отправляет DISCONNECT-пакет целевой ноде и * сбрасывает conn_mgr_type в NODEINFO_Q - * 5. Для alien-нод (подключившихся к нам самостоятельно) используется - * conn_mgr_add_alien_node() вместо conn_mgr_connect_node() - * 6. conn_mgr_update_best_candidates() вызывается извне (обычно из route_connectivity) + * 5. conn_mgr_update_best_candidates() вызывается извне (обычно из route_connectivity) * для обновления RTT кандидатов * * @note Внутренние таймеры (всё в timebase-единицах, 1 tb = 0.1 мс): @@ -266,7 +264,6 @@ struct CONN_MGR_ENTRY { uint64_t node_id; ///< Идентификатор целевой ноды uint8_t state; ///< Текущее состояние (enum CONN_MGR_STATE) uint8_t conn_type; ///< Тип соединения (CONN_TYPE_DIRECT/REVERSE/INDIRECT) - uint8_t alien; ///< 1 — alien-нода (сама подключилась к нам), 0 — мы инициаторы uint32_t idle_timeout_ms; ///< Таймаут неактивности (мс), 0 — без таймаута uint64_t last_traffic_tb; ///< Время последнего трафика (tb) для idle-проверки void* idle_timer; ///< Хендл таймера idle-проверки @@ -355,7 +352,6 @@ void conn_mgr_destroy(struct CONN_MGR* mgr); * @note Если нода уже подключена — сразу вызывает callback с CONN_MGR_OK и возвращает CONN_MGR_OK. * @note Если нода в процессе подключения — добавляет callback в список ожидания. * @note Автоматически определяет наличие BGP-соединения (уже существующий ETCP-линк). - * @note Для alien-нод используйте conn_mgr_add_alien_node(). */ int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, uint32_t idle_timeout_ms, @@ -397,21 +393,6 @@ int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t t int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id, uint8_t* out_state, uint8_t* out_conn_type); -/* ==================== Alien-ноды ==================== */ - -/** - * @brief Регистрирует alien-ноду (подключившуюся к нам самостоятельно). - * @param mgr менеджер - * @param nodeinfo_data сырые данные NODEINFO (BGP-пакет) - * @param len длина данных - * @return CONN_MGR_OK или CONN_MGR_ERR_INTERNAL - * - * Alien-нода — это нода, которая сама инициировала INIT-подключение к нам. - * Для неё не запускаются фазы подключения, сразу устанавливается CONN_MGR_STATE_CONNECTED. - * Создаёт entry с флагом alien=1. - */ -int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len); - /** * @brief Отправляет данные подключённой ноде через etcp_router. * @param mgr менеджер diff --git a/src/conn_mgr_doc.md b/src/conn_mgr_doc.md index 6680a756..a69e3d35 100644 --- a/src/conn_mgr_doc.md +++ b/src/conn_mgr_doc.md @@ -40,9 +40,6 @@ entry->dgram = data; entry->len = len; conn_mgr_send(mgr, node_id, entry); // entry освобождается внутри ``` -### Alien-ноды -Для нод, которые сами подключились к нам, используется `conn_mgr_add_alien_node()` — не запускает фазы подключения, сразу устанавливает `CONNECTED`. - ### Кандидаты-посредники Внешний код (обычно `route_connectivity`) вызывает `conn_mgr_update_best_candidates()` для заполнения списка лучших посредников (топ-3 по RTT). Список используется фазой INDIRECT. @@ -59,7 +56,7 @@ conn_mgr_send(mgr, node_id, entry); // entry освобождается внут | Структура | Описание | |-----------|----------| | `CONN_MGR` | Корневая структура менеджера. Хранит массив `entries[]`, топ-3 `best_candidates`, таймеры (`bg_ping_timer`, `candidate_ping_timer`), списки `reverse_pending`/`exchange_pending`, `next_request_id` | -| `CONN_MGR_ENTRY` | Запись о подключении к одной ноде: `state`, `conn_type`, `alien`, `idle_timeout_ms`, список коллбэков `cb_list`, массив `intermediaries[]`, состояния `local_scan_state`/`main_connect_state`, текущая `main.phase` | +| `CONN_MGR_ENTRY` | Запись о подключении к одной ноде: `state`, `conn_type`, `idle_timeout_ms`, список коллбэков `cb_list`, массив `intermediaries[]`, состояния `local_scan_state`/`main_connect_state`, текущая `main.phase` | | `CONN_MGR_CANDIDATE` | Пара `{node_id, rtt}` — запись о кандидате-посреднике (packed) | | `cm_cb_node` | Узел связного списка коллбэков для асинхронного результата подключения | | `cm_reverse_pending` | Запись об ожидании REVERSE-подключения (связный список) | @@ -82,12 +79,6 @@ conn_mgr_send(mgr, node_id, entry); // entry освобождается внут | `conn_mgr_set_idle_timeout(mgr, id, ms)` | Устанавливает/снимает idle-таймер для ноды. При неактивности дольше `ms` — автоотключение | | `conn_mgr_get_status(mgr, id, &state, &type)` | Возвращает текущее состояние и тип соединения | -### Alien-ноды - -| Функция | Описание | -|---------|----------| -| `conn_mgr_add_alien_node(mgr, data, len)` | Регистрирует чужую ноду (сама подключилась к нам): копирует `TOPO_NODE`, добавляет в группу, помечает `alien=1` | - ### Кандидаты-посредники | Функция | Описание | diff --git a/src/route_ping.h b/src/route_ping.h index a038b2c5..f1b83bcc 100644 --- a/src/route_ping.h +++ b/src/route_ping.h @@ -1,3 +1,15 @@ +/** + * @file route_ping.h + * @brief Удалённый пинг через BGP-посредника. + * + * A просит B пропинговать C и вернуть сводную статистику: + * A --PING_REQ--> B --ETCP ping--> C + * A <--PING_RESP-- B (sent/ok/avg_rtt) + * + * Промежуточный узел задаётся ЯВНО в to_conn — вызывающий сам выбирает, + * через кого слать запрос. Автоподбор посредника (nat_check_find_third_node) + * находится в nat_detection.c, не здесь. + */ #ifndef ROUTE_PING_H #define ROUTE_PING_H @@ -9,6 +21,11 @@ extern "C" { #include #include "nat_detection.h" +/** Запросить у to_conn серию пингов на target_ip:target_port. + * cb вызывается всегда ровно 1 раз: + * success=1 если хоть один пинг успешен (PONG получен) + * success=0 при общем таймауте или все пинги провалились + * @param to_conn промежуточный узел — выбирается вызывающим явно */ int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, struct ETCP_CONN* to_conn, uint32_t target_ip, uint16_t target_port, @@ -16,11 +33,17 @@ int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg, const uint8_t* pubkey); +/** Отменить все ожидающие ответы (при остановке NAT_DETECTION). */ void route_ping_destroy_pending(struct NAT_DETECTION* nd); + +/** Отменить pending для conn (при разрыве/реините соединения). */ void route_ping_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn); +/** Входящий PING_REQ — мы посредник: пингуем цель, шлём PING_RESP. */ void route_ping_handle_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); + +/** Входящий PING_RESP — мы инициатор: находим pending по request_id, вызываем cb. */ void route_ping_handle_resp(struct NAT_DETECTION* nd, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); diff --git a/src/topo_group.c b/src/topo_group.c index cd755ed0..7cb12297 100644 --- a/src/topo_group.c +++ b/src/topo_group.c @@ -356,9 +356,8 @@ void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { struct TOPO_NODEQ* peer_nq = topo_node_find_by_id(group, conn->peer_node_id); topo_group_add_to_senders(group, conn); - if (peer_nq && peer_nq->alien) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "peer 0x%016llx is alien, skipping route exchange", (unsigned long long)conn->peer_node_id); DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "topo_group_new_conn: peer=%016llx group=%016llx type=%d ch=%s ALIEN=1 — SKIP", (unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id); return; } - DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "topo_group_new_conn: peer=%016llx group=%016llx type=%d ch=%s alien=%d", (unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id, peer_nq ? peer_nq->alien : -1); + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "topo_group_new_conn: peer=%016llx group=%016llx type=%d ch=%s", (unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id); topo_group_send_table_request(group, conn); } @@ -561,7 +560,6 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from nodeinfo1->connectivity.nat_status = PROBE_RESULT_UNKNOWN; nodeinfo1->connectivity.real_status = PROBE_RESULT_UNKNOWN; nodeinfo1->connectivity.ping_req_time = 0; - nodeinfo1->alien = 0; queue_data_put_with_index(group->nodes, &nodeinfo1->ll); } diff --git a/src/topo_group_doc.md b/src/topo_group_doc.md index 24bf56c2..bd2e2f37 100644 --- a/src/topo_group_doc.md +++ b/src/topo_group_doc.md @@ -11,7 +11,8 @@ BGP-подобный обмен топологией узлов между пи Группа по умолчанию (UTUN) создаётся автоматически при `topo_groups_init()`. Дополнительные группы создаются через `topo_groups_create_group()` (для chatgui). Узлы разных групп изолированы: peer из utun-группы не видит узлы чат-группы и наоборот. Помимо обмена топологией, модуль выполняет: -- **NAT-детекцию** (NAT_INFO / NAT_CHECK_REQ): клиент запрашивает проверку своего NAT у сервера; сервер через третий узел пингует клиента и сообщает тип NAT (EIM/STRICT) +- **Сборку local_node** (`topo_group_update_my_nodeinfo`) — при старте и при изменениях конфигурации сокетов собирает информацию о себе (адреса, подсети, ключи) +- **NAT-детекцию** — выделена в отдельный самодостаточный модуль `nat_detection.h` (свой ETCP_ID 0x02, своя обработка пакетов); topo_group предоставляет делегат `topo_group_set_nat_check_local` для тестовых нужд - **Поиск оптимального маршрута** до узла (`topo_group_find_conn_for_node`) — по min hop_count среди live-путей (ETCP соединений) - **Зондирование связности** — для новых узлов запускает `route_connectivity_probe_node()` - **Персистентность в SQLite** — синхронизация узлов и membership в БД (таблицы nodes, node_addresses, peers_*) @@ -26,47 +27,49 @@ struct TOPO_GROUPS* g = topo_groups_init(instance); // создаёт конт Здесь же: - `etcp_bind(instance, ETCP_ID_TOPO_ENTRY, topo_group_receive_cbk)` — регистрирует приёмник пакетов топологии - `etcp_add_new_conn_cbk(instance, topo_group_etcp_conn_cbk, NULL)` — подписывается на события on_up/on_down всех новых ETCP-соединений -- Открывается SQLite (если указан `db_path` в конфиге) +- Открывается SQLite (если указан `db_path` в конфиге), БД сохраняется в `instance->topo_sqlite_db` +- NAT-детекция инициализируется отдельно в `utun_instance.c` через `nat_detection_create(instance)` ### 2.2. Новое ETCP-соединение При ETCP on_up автоматически вызывается `topo_group_new_conn(group, conn)`: 1. Добавляет conn в `senders_list` (через `topo_group_add_to_senders`) -2. Запускает NAT check для всех линков всех соединений -3. Отправляет `TOPO_SUBCMD_REQUEST_TABLE` — запрос полной таблицы +2. Отправляет `TOPO_SUBCMD_REQUEST_TABLE` — запрос полной таблицы При приёме `TOPO_SUBCMD_REQUEST_TABLE` peer отправляет: - NODEINFO c локальным узлом (local_node) -- Full table sync — все известные узлы, кроме тех, чей путь уже включает этого пира (защита от петель) -- `TOPO_SUBCMD_TABLE_COMPLETE` — метка завершения начальной синхронизации +- Full table sync — все известные узлы (с hop_count > 0), кроме тех, чей путь уже включает этого пира (защита от петель) +- `TOPO_SUBCMD_TABLE_COMPLETE` — метка завершения начальной синхронизации; при получении вызывает `etcp_set_routing_exchange_state(from_conn, 3)` ### 2.3. NODEINFO (обновление/создание узла) `topo_group_process_nodeinfo(group, from, data, len)`: -1. Проверка соответствия типа группы по флагу `TOPO_FLAG_SEND_SUBNETS` +1. Проверка соответствия типа группы по флагу `TOPO_FLAG_SEND_SUBNETS`; при несовпадении — отправка `TOPO_SUBCMD_ERR_GROUP_MISMATCH` 2. Проверка `hop_count < MAX_HOPS(16)` 3. Проверка версии узла — если версия не новее (stale), только обновляет пути (hop_list) и выходит -4. Десериализация wire-формата → обновление/создание `TOPO_NODEQ` -5. Добавление пути (`topo_group_add_path`) через текущее соединение -6. Вставка в `routing` (`route_insert`) — только для UTUN-групп -7. Сохранение в SQLite (если есть) -8. Callback `node_updated_cb` (для chatgui member_sync) -9. Broadcast всем пирам, кроме hop_list (защита от петель) -10. Запуск зондирования связности для новых узлов с адресами +4. Десериализация wire-формата → обновление/создание `TOPO_NODEQ` (через `topo_node_deserialize`) +5. При создании нового узла: инициализация `connectivity` (PROBE_STATUS_NONE, PROBE_RESULT_UNKNOWN) +6. Добавление пути (`topo_group_add_path`) через текущее соединение, с очисткой старых путей содержащих peer_node_id (`topo_group_remove_path_by_hop`) +7. Вставка в `routing` (`route_insert`) — только для UTUN-групп +8. Сохранение в SQLite (если есть): `topo_node_sqlite_node_put` + для CHAT групп — `topo_node_sqlite_member_put` +9. Уведомление control_server (`control_server_notify_node_change`) +10. Callback `node_updated_cb` (для chatgui member_sync) — вызывается и при создании, и при обновлении +11. Broadcast всем пирам, кроме hop_list (защита от петель) +12. Запуск зондирования связности для новых узлов с адресами (`route_connectivity_probe_node`) ### 2.4. WITHDRAW (удаление узла) `topo_group_process_withdraw(group, sender, data, len)`: 1. Поиск узла по `node_id` 2. `topo_group_remove_path_by_hop(nq, wd_source)` — удаление всех путей, содержащих wd_source в hop_list -3. Если путей не осталось (ret == 1): удаление из routing, уведомление control_server, broadcast withdraw дальше -4. Если группа CHAT — удаление member из SQLite +3. Если путей не осталось (ret == 1): удаление из routing, уведомление control_server, установка `dirty = 1`, отмена connectivity probe (`route_connectivity_cancel_node`), освобождение paths, `topo_node_free_lists`, закрытие виртуальных каналов (`etcp_router_conn_close_all_for_node`), broadcast withdraw дальше +4. Если группа CHAT — удаление member из SQLite через `topo_node_sqlite_member_del` ### 2.5. Отключение соединения При ETCP on_down вызывается `topo_group_remove_conn(group, conn)`: -1. Отмена pending ping-запросов для этого conn -2. Для всех узлов с путями через этот conn — `topo_group_remove_path`. Если узел стал полностью недоступен (нет путей) — удаляется из routing и nodes, с broadcast withdraw +1. Проверка наличия conn в senders_list (если уже удалён — выход) +2. Для всех узлов с путями через этот conn — `topo_group_remove_path`. Если узел стал полностью недоступен (нет путей) — удаляется из routing и nodes, устанавливается `dirty = 1`, отменяется connectivity probe, закрываются виртуальные каналы, broadcast withdraw 3. Удаление conn из `senders_list` ### 2.6. Поиск пути до узла @@ -74,17 +77,19 @@ struct TOPO_GROUPS* g = topo_groups_init(instance); // создаёт конт `topo_group_find_conn_for_node(group, node_id)` — перебирает `path`'ы узла, выбирает с минимальным `hop_count`. Предпочитает live-пути (links_up > 0). Если live-путей нет, fallback на любой путь с минимальным hop_count. Интенсивно используется `etcp_router`: -- При маршрутизации пакетов к удалённому узлу (`src/etcp_router.c:262`) +- При маршрутизации пакетов к удалённому узлу - При поиске next-hop для multihop соединений ### 2.7. NAT-детекция -1. При новом соединении вызывается `topo_group_start_link_nat_check` для каждого линка -2. Сервер ищет «третий узел» через `topo_group_find_third_node` — произвольный conn кроме проверяемого -3. Отправляет `PING_REQ` через третий узел на IP:port линка проверяемого клиента -4. По результату пинга: `NAT_TYPE_EIM` (если успех) или `NAT_TYPE_STRICT` -5. Результат сохраняется в `link->nat_type` и отправляется клиенту через `topo_group_send_nat_info` -6. Клиент обновляет свой `local_node` через `topo_group_handle_nat_info` и broadcast-ит изменения +NAT-детекция выделена в отдельный самодостаточный модуль `nat_detection.h`: +- Свой ETCP ID: `ETCP_ID_NAT_DETECTION` (0x02) +- Сам подписывается на conn_up/conn_down, сам принимает свои пакеты +- При поднятии линка (INIT handshake) вызывается `nat_detection_link_ready()` +- Ищет третий узел напрямую в `instance->connections`, обновляет `ETCP_SOCKET` и broadcast-ит NODEINFO через `topo_group_update_my_nodeinfo()` + +Из topo_group в NAT-детекцию остался только делегат: +- `topo_group_set_nat_check_local(group, allow)` → вызывает `nat_detection_set_allow_local(instance->nat_det, allow)` ### 2.8. Широковещательная рассылка @@ -98,26 +103,25 @@ struct TOPO_GROUPS* g = topo_groups_init(instance); // создаёт конт - CHAT-группа ожидает флаг = 0 (без подсетей) - При несовпадении отправляется `TOPO_SUBCMD_ERR_GROUP_MISMATCH` и пакет игнорируется -### 2.10. Alien-узлы - -Узлы, помеченные `alien=1`, не участвуют в обмене маршрутами: при `topo_group_new_conn` для alien-пира обмен пропускается. - ## 3. API ### 3.1. Структуры | Структура | Назначение | |---|---| -| `TOPO_GROUPS` | Контейнер всех групп топологии экземпляра. Хранит `group_list` (ll_queue), memory_pool'ы для сериализации, sqlite3, callback `node_updated_cb` | -| `TOPO_GROUP` | Одна группа топологии. `group_id`, `group_type`, `nodes` (ll_queue узлов), `senders_list` (ll_queue conn'ов), `local_node` (TOPO_NODEQ) | +| `TOPO_GROUPS` | Контейнер всех групп топологии экземпляра. Хранит `group_list` (ll_queue), memory_pool'ы для сериализации (v4_sock_meta, v4_addr, v6_sock_meta, v6_addr, v4_subnet, v6_subnet), callback `node_updated_cb` | +| `TOPO_GROUP` | Одна группа топологии. `group_id`, `group_type`, `nodes` (ll_queue узлов), `senders_list` (ll_queue conn'ов), `local_node` (TOPO_NODEQ), `ed25519_public_key` (производный из X25519), `channel_id[64]` (для CHAT-групп) | | `TOPO_GROUP_CONN_ITEM` | Элемент списка senders: conn + ll_entry — связка с senders_list | +| `TOPO_NODE` | Идентичность узла: pubkeys, имя, версия (ver), списки адресов (v4/v6 sock_meta, addrs). Разделяется между группами через ref_count | +| `TOPO_NODEQ` | Запись в очереди группы: указатель на TOPO_NODE, пути (paths), hop_list, tranzit_data, subnets, connectivity, conn_mgr (тип + intermediaries), best_socket, dirty, last_ver | +| `TOPO_NODEPATH` | Путь до узла через конкретное ETCP_CONN: conn + hop_count | +| `TOPO_NODESUBNETS` | Подсети узла: списки TOPO_SUBNET4 / TOPO_SUBNET6 | +| `TOPO_CONNECTIVITY` | Состояние связности с узлом: probe_status, pending_count, rtt-метрики, статусы (interface/nat/real), probe_list | | `TOPOMSG_NODEINFO_PKT` | Wire-формат пакета NODEINFO: cmd + subcmd + TOPOMSG_NODE + переменная часть | | `TOPOMSG_WITHDRAW_PKT` | Wire-формат WITHDRAW: node_id (удаляемый) + wd_source (инициатор) | -| `TOPOMSG_NAT_INFO` | Wire-формат NAT_INFO: socket_id, IP, port (network byte order), nat_type | -| `TOPOMSG_NAT_CHECK_REQ` | Запрос проверки NAT от клиента к серверу: socket_id + interface IP:port | | `TOPOMSG_TABLE_REQ` | Wire-формат запроса/завершения таблицы: cmd + subcmd | | `TOPOMSG_ERR_GROUP_MISMATCH` | Wire-формат ошибки несоответствия типа группы: expected_type + received_flags | -| `nat_check_arg` | Контекст callback NAT-проверки: link + nat_ip + nat_port | +| `NAT_DETECTION` | Контекст NAT-детекции (отдельный модуль): ping_pending, next_ping_req_id, allow_nat_check_local | ### 3.2. Константы @@ -127,22 +131,29 @@ struct TOPO_GROUPS* g = topo_groups_init(instance); // создаёт конт | `TOPO_SUBCMD_NODEINFO` | 0x04 | Полная информация об узле + подсети | | `TOPO_SUBCMD_REQUEST_TABLE` | 0x05 | Запрос полной таблицы | | `TOPO_SUBCMD_WITHDRAW` | 0x06 | Узел стал недоступен | -| `TOPO_SUBCMD_NAT_INFO` | 0x09 | Информация о типе NAT клиента | -| `TOPO_SUBCMD_NAT_CHECK_REQ` | 0x0A | Запрос проверки NAT для сокета | | `TOPO_SUBCMD_TABLE_COMPLETE` | 0x0B | Завершение начальной синхронизации | | `TOPO_SUBCMD_ERR_GROUP_MISMATCH` | 0x0C | Ошибка несоответствия типа группы | | `MAX_HOPS` | 16 | Максимальная длина hop-листа | | `BGP_NODES_HASH_SIZE` | 256 | Размер хеш-таблицы nodes | | `TOPO_GROUP_UTUN` | 0x8000000000000000ULL | group_id по умолчанию для utun-группы | +| `TOPO_GROUP_TYPE_UTUN` | 1 | Тип группы: VPN-сеть (подсети, маршруты) | +| `TOPO_GROUP_TYPE_CHAT` | 2 | Тип группы: чат (без подсетей) | +| `TOPO_FLAG_SEND_SUBNETS` | 0x01 | Флаг NODEINFO: узел передаёт подсети | +| `PROBE_STATUS_NONE` | 0 | Связность: не проверялась | +| `PROBE_STATUS_IN_PROGRESS` | 1 | Связность: проверка идёт | +| `PROBE_STATUS_DONE` | 2 | Связность: проверка завершена | +| `PROBE_RESULT_UNKNOWN` | 0 | Результат проверки: неизвестно | +| `PROBE_RESULT_REACHABLE` | 1 | Результат проверки: доступен | +| `PROBE_RESULT_UNREACHABLE` | 2 | Результат проверки: недоступен | ### 3.3. Функции инициализации/завершения | Функция | Назначение | |---|---| -| `topo_groups_init(instance)` | Создаёт TOPO_GROUPS, memory_pool'ы, открывает SQLite, создаёт utun-группу по умолчанию, регистрирует ETCP-коллбэки | -| `topo_groups_destroy(instance)` | Освобождает все группы, memory_pool'ы, закрывает SQLite; снимает ETCP-регистрацию | -| `topo_group_create(instance, group_id, group_type)` | Создаёт TOPO_GROUP: nodes (hash queue), senders_list, local_node; вызывается из topo_groups_init / topo_groups_create_group | -| `topo_group_destroy(group)` | Освобождает senders_list, local_node, nodes очереди; узлы должны быть удалены заранее | +| `topo_groups_init(instance)` | Создаёт TOPO_GROUPS, memory_pool'ы, открывает SQLite (если db_path в конфиге), создаёт utun-группу по умолчанию, регистрирует ETCP-коллбэки | +| `topo_groups_destroy(instance)` | Освобождает все группы, memory_pool'ы, закрывает SQLite, отменяет connectivity probes, снимает ETCP-регистрацию | +| `topo_group_create(instance, group_id, group_type)` | Создаёт TOPO_GROUP: nodes (hash queue), senders_list, local_node (через update_my_nodeinfo), ed25519_pubkey; вызывается из topo_groups_init / topo_groups_create_group | +| `topo_group_destroy(group)` | Освобождает senders_list, local_node (free_lists), nodes очереди | | `topo_groups_create_group(g, group_id, group_type, channel_id)` | Создаёт новую группу и добавляет в group_list; для CHAT устанавливает channel_id | | `topo_groups_find(g, group_id)` | Поиск группы по group_id через хеш-индекс group_list | | `topo_groups_get_default(g)` | Возвращает utun-группу по умолчанию (group_id=TOPO_GROUP_UTUN) | @@ -151,8 +162,8 @@ struct TOPO_GROUPS* g = topo_groups_init(instance); // создаёт конт | Функция | Назначение | |---|---| -| `topo_group_new_conn(group, conn)` | Добавляет conn в senders_list, запускает NAT check для всех линков, отправляет REQUEST_TABLE; alien-пиры пропускаются | -| `topo_group_remove_conn(group, conn)` | Отменяет NAT check и ping pending, удаляет все пути через conn, удаляет недоступные узлы, broadcast withdraw, убирает conn из senders_list | +| `topo_group_new_conn(group, conn)` | Добавляет conn в senders_list, отправляет REQUEST_TABLE | +| `topo_group_remove_conn(group, conn)` | Удаляет все пути через conn, удаляет недоступные узлы (route_delete, cancel_connectivity, etcp_router_conn_close_all_for_node, broadcast withdraw), убирает conn из senders_list | | `topo_group_etcp_conn_cbk(conn, arg)` | Callback при создании нового ETCP-соединения: подписывается на on_up/on_down через etcp_conn_add_up_cbk/down_cbk | | `topo_group_on_conn_up(conn, arg)` | При on_up: вызывает topo_group_new_conn | | `topo_group_on_conn_down(conn, arg)` | При on_down: вызывает topo_group_remove_conn | @@ -161,41 +172,41 @@ struct TOPO_GROUPS* g = topo_groups_init(instance); // создаёт конт | Функция | Назначение | |---|---| -| `topo_group_receive_cbk(from_conn, entry)` | Точка входа приёма пакетов топологии: диспетчеризация по subcmd (NODEINFO/WITHDRAW/REQUEST_TABLE/PING_REQ/PING_RESP/NAT_INFO/NAT_CHECK_REQ/TABLE_COMPLETE/ERR_GROUP_MISMATCH) | -| `topo_group_process_nodeinfo(group, from, data, len)` | Обработка NODEINFO: проверка группы/версии/hops, десериализация, обновление/создание TOPO_NODEQ, пути, routing, SQLite, broadcast, зондирование связности | -| `topo_group_process_withdraw(group, sender, data, len)` | Обработка WITHDRAW: удаление путей с wd_source, если узел недоступен — удаление из routing, broadcast | -| `topo_group_handle_request_table(group, conn)` | Обработка REQUEST_TABLE: отправляет NODEINFO (local_node) + full table sync + TABLE_COMPLETE | -| `topo_group_handle_nat_info(group, from_conn, data, len)` | Обработка NAT_INFO: обновляет nat_type в local_node (TOPO_SOCKMETA4), ETCP_SOCKET, при изменениях broadcast NODEINFO | -| `topo_group_handle_nat_check_req(group, from_conn, data, len)` | Обработка NAT_CHECK_REQ от клиента: находит линк и запускает NAT-проверку | +| `topo_group_receive_cbk(from_conn, entry)` | Точка входа приёма пакетов топологии: работает только с default-группой, диспетчеризация по subcmd (NODEINFO/WITHDRAW/REQUEST_TABLE/TABLE_COMPLETE/ERR_GROUP_MISMATCH) | +| `topo_group_process_nodeinfo(group, from, data, len)` | Обработка NODEINFO: проверка группы/версии/hops, десериализация, обновление/создание TOPO_NODEQ, пути, routing, SQLite, broadcast, зондирование связности, node_updated_cb | +| `topo_group_process_withdraw(group, sender, data, len)` | Обработка WITHDRAW: удаление путей с wd_source, если узел недоступен — полная очистка (routing, connectivity, paths, etcp_router), broadcast | +| `topo_group_handle_request_table(group, conn)` | Обработка REQUEST_TABLE: отправляет NODEINFO (local_node) + full table sync + add_to_senders + TABLE_COMPLETE | ### 3.6. Функции поиска | Функция | Назначение | |---|---| | `topo_group_find_conn_for_node(group, node_id)` | Поиск оптимального ETCP_CONN до узла: min hop_count, предпочтение live-путям | -| `topo_group_find_third_node(group, exclude)` | Поиск произвольного conn'а (не exclude) для использования как посредника при NAT-детекции; исключает private/local conn'ы | +| `topo_node_find_by_id(group, node_id)` | Поиск TOPO_NODEQ в nodes по node_id через хеш-индекс | +| `topo_groups_find(g, group_id)` | Поиск группы по group_id | ### 3.7. Функции broadcast | Функция | Назначение | |---|---| | `topo_group_send_nodeinfo(group, node, conn)` | Сериализует TOPO_NODEQ и отправляет NODEINFO одному conn | -| `topo_group_send_withdraw(group, node_id)` | Broadcast WITHDRAW всем пирам (without exclude) | +| `topo_group_send_withdraw(group, node_id)` | Broadcast WITHDRAW всем пирам (без exclude) | | `topo_group_broadcast_withdraw(group, node_id, wd_source, exclude)` | Рассылает WITHDRAW всем conn в senders_list, кроме exclude | -| `topo_group_send_full_table(group, conn)` | Отправляет все известные узлы (кроме тех, чей путь включает target) указанному conn | +| `topo_group_send_full_table(group, conn)` | Отправляет все известные узлы (с hop_count > 0), кроме тех, чей путь включает target, указанному conn | | `topo_group_send_table_request(group, conn)` | Отправляет запрос полной таблицы (TOPO_SUBCMD_REQUEST_TABLE) | | `topo_group_send_table_complete(group, conn)` | Отправляет метку завершения синхронизации (TOPO_SUBCMD_TABLE_COMPLETE) | | `topo_group_send_err_group_mismatch(group, conn, expected_type, received_flags)` | Отправляет ошибку несоответствия типа группы | -### 3.8. Функции NAT +### 3.8. Функции NAT (делегаты в nat_detection) | Функция | Назначение | |---|---| -| `topo_group_send_nat_info(conn, socket_id, nat_ip, nat_port, nat_type)` | Отправляет клиенту информацию о его NAT (результат проверки) | -| `topo_group_send_nat_check_req(conn, socket_id)` | Отправляет серверу запрос на проверку NAT для указанного socket_id | -| `topo_group_start_link_nat_check(group, link)` | Запускает NAT-проверку для одного линка через третий узел; проверяет is_local_subnet, пропускает private/local линки | -| `topo_group_request_nat_check_all(group)` | Запускает NAT check для всех линков всех соединений | -| `topo_group_set_nat_check_local(group, allow)` | Разрешить/запретить NAT check для локальных подсетей (127.x, 10.x, 192.168.x, 172.16-31.x) | +| `topo_group_set_nat_check_local(group, allow)` | Делегат в `nat_detection_set_allow_local(instance->nat_det, allow)` — разрешить/запретить NAT check для локальных подсетей (тесты) | +| `nat_detection_create(inst)` | Создаёт модуль NAT-детекции: etcp_bind на 0x02, подписка на conn_up/conn_down | +| `nat_detection_destroy(nd)` | Уничтожает модуль NAT-детекции | +| `nat_detection_link_ready(nd, link)` | Запускает NAT-чек для линка после INIT handshake | +| `nat_detection_cancel_for_conn(nd, conn)` | Отменяет все pending NAT-пинги для соединения | +| `nat_detection_request_check_all(nd)` | Принудительная перепроверка NAT для всех линков | ### 3.9. Функции путей @@ -205,15 +216,14 @@ struct TOPO_GROUPS* g = topo_groups_init(instance); // создаёт конт | `topo_group_remove_path(nq, conn)` | Удаляет путь через указанный conn; возвращает 1 если путей не осталось (unreachable) | | `topo_group_remove_path_by_hop(nq, wd_source)` | Удаляет все пути, содержащие wd_source в hop_list (используется в WITHDRAW) | | `topo_group_add_to_senders(group, conn)` | Добавляет conn в senders_list (с проверкой дубликатов) | -| `topo_group_should_send_to(nq, target_id)` | Проверяет, нужно ли отправлять NODEINFO узла указанному пиру (target_id не в hop_list) | +| `topo_group_should_send_to(nq, target_id)` | Проверяет, нужно ли отправлять NODEINFO узла указанному пиру (target_id не во всех путях) | ### 3.10. Callbacks/вспомогательные | Функция | Назначение | |---|---| -| `topo_groups_set_node_updated_cb(groups, fn)` | Устанавливает callback при обновлении узла (используется chatgui для member_sync) | -| `topo_groups_set_sqlite_db(g, db)` | Устанавливает внешнюю БД SQLite | -| `nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)` | Callback завершения NAT-проверки: устанавливает nat_type линка и отправляет результат клиенту | +| `topo_groups_set_node_updated_cb(groups, fn)` | Устанавливает callback при создании/обновлении узла (используется chatgui для member_sync) | +| `topo_group_update_my_nodeinfo(instance, group)` | Собирает информацию о локальном узле из конфига и ключей: адреса сокетов, подсети, pubkeys, имя; обновляет group->local_node; вызывается при старте и при изменениях сокетов | | `nodeinfo_dump_log(data, len)` | Логирует краткую сводку NODEINFO (node_id, ver, счётчики, hops) | | `nodeinfo_format(data, len)` | Форматирует полный NODEINFO с именем, подсетями в читаемую строку (аллокация через u_malloc) | | `group_subcmd_name(subcmd)` | Возвращает строковое имя subcmd для логов | @@ -230,36 +240,39 @@ struct TOPO_GROUPS* g = topo_groups_init(instance); // создаёт конт | Модуль | Как используется | |---|---| -| `topo_node.c/h` | Модель данных узла: TOPO_NODE, TOPO_NODEQ, ser/deserialize, find_by_id, update_my_nodeinfo, free_lists | +| `topo_node.c/h` | Модель данных узла: TOPO_NODE, TOPO_NODEQ, ser/deserialize, find_by_id, update_my_nodeinfo, free_lists, dump_all, format_all | | `topo_node_sqlite.h` | Персистентность в SQLite: topo_node_sqlite_node_put, member_put, member_del, init | +| `nat_detection.h` | NAT-детекция: отдельный модуль с собственным ETCP_ID 0x02; делегат set_nat_check_local | | `route_lib.h` | Таблица маршрутов: route_insert, route_delete | -| `route_ping.h` | NAT-детекция и проверка связности: route_ping_send_req_addr, handle_req, handle_resp, cancel_for_conn | | `route_connectivity.h` | Зондирование связности узла: route_connectivity_probe_node, cancel_node, cancel_all | | `etcp_api.h` | Регистрация приёмника: etcp_bind, etcp_add_new_conn_cbk, etcp_unbind | | `etcp_connections.h` | Управление соединениями: etcp_conn_add_up_cbk, etcp_conn_add_down_cbk, ETCP_CONN, ETCP_LINK | -| `etcp.h` | Отправка пакетов: etcp_send | +| `etcp.h` | Отправка пакетов: etcp_send, etcp_set_routing_exchange_state | +| `etcp_debug.h` | Логирование ETCP: log_name | | `etcp_router.h` | Закрытие виртуальных каналов: etcp_router_conn_close_all_for_node | | `control_server.h` | Уведомления мониторинга: control_server_notify_node_change, control_server_notify_node_removed | | `conn_mgr.h` | Менеджер соединений: использует topo_group_find_conn_for_node для поиска путей | -| `utun_instance.h` | UTUN_INSTANCE: конфиг, node_id, ключи, rt, topo_groups | +| `utun_instance.h` | UTUN_INSTANCE: конфиг, node_id, ключи, rt, topo_groups, nat_det, topo_sqlite_db, control_srv | | `secure_channel.h` | Ключи: sc_derive_ed25519_pubkey | | `memory_pool.h` | Память для сериализации: memory_pool_init / destroy | | `debug_config.h` | Логирование: DEBUG_INFO/WARN/ERROR/DEBUG с категорией BGP | | `mem.h` | Аллокация: u_malloc, u_free, u_calloc | +| `platform_compat.h` | Кроссплатформенная совместимость | ### 4.2. Кто вызывает topo_group | Вызывающий | Что вызывает | Для чего | |---|---|---| | `utun_instance.c` | `topo_groups_init`, `topo_groups_destroy` | Инициализация/завершение при старте/остановке utun | -| `etcp_connections.c` | `topo_group_send_nodeinfo` | При изменении конфигурации сокетов — broadcast обновлённого local_node | +| `etcp_connections.c` | `topo_group_update_my_nodeinfo` | При изменении конфигурации сокетов — обновление local_node | | `etcp_router.c` | `topo_group_find_conn_for_node` | Поиск next-hop для маршрутов и multihop-соединений (~15 вызовов) | | `conn_mgr.c` | `topo_group_find_conn_for_node` | Поиск пути для установки прямого/обратного/indirect соединения | +| `nat_detection.c` | `topo_group_update_my_nodeinfo` | При обновлении NAT-типа сокета — обновление local_node и broadcast | | Тесты | `topo_groups_get_default`, `topo_group_find_conn_for_node`, `topo_node_find_by_id`, `topo_group_set_nat_check_local` | 9 тестовых файлов | ### 4.3. ETCP-коллбэки (верхний уровень) -Точка входа для пакетов топологии — `etcp_bind(instance, ETCP_ID_TOPO_ENTRY, topo_group_receive_cbk)` в `topo_groups_init`. Единственный обработчик на весь ETCP ID 0x01. Диспетчеризация внутри по subcmd (byte data[1]). +Точка входа для пакетов топологии — `etcp_bind(instance, ETCP_ID_TOPO_ENTRY, topo_group_receive_cbk)` в `topo_groups_init`. Единственный обработчик на весь ETCP ID 0x01. Диспетчеризация внутри по subcmd (byte data[1]). Обработка всегда идёт через default-группу (TOPO_GROUP_UTUN). Коллбэки on_up/on_down новых соединений регистрируются через `etcp_add_new_conn_cbk(instance, topo_group_etcp_conn_cbk, NULL)`, которая при создании каждого нового ETCP_CONN подписывается на его события. @@ -280,26 +293,21 @@ cmd(1) | subcmd(1) | node_id(8) | wd_source(8) = 18 байт cmd(1) | subcmd(1) = 2 байта ``` -### 5.4. NAT_INFO -``` -cmd(1) | subcmd(1) | socket_id(1) | nat_ip(4) | nat_port(2) | nat_type(1) = 10 байт -``` - -### 5.5. NAT_CHECK_REQ -``` -cmd(1) | subcmd(1) | socket_id(1) | interface_ip(4) | interface_port(2) = 9 байт -``` - -### 5.6. ERR_GROUP_MISMATCH +### 5.4. ERR_GROUP_MISMATCH ``` cmd(1) | subcmd(1) | expected_type(1) | received_flags(1) = 4 байта ``` ## 6. Примечания -- **Хранилище памяти**: В отличие от STCP, модуль не имеет отдельной БД. Использует внешний SQLite3 pointer (`topo_groups->topo_sqlite_db`), который может быть открыт самим модулем (если указан `db_path`) или передан из chatgui через `topo_groups_set_sqlite_db`. -- **Версионирование NODEINFO**: Версия (ver, uint8_t 1-255) сравнивается через циклическую арифметику `(int8_t)(cur_ver - new_ver) >= 0`. Версия 0 не используется (нет данных). -- **Защита от петель**: NODEINFO рассылается только тем пирам, чей node_id отсутствует в hop_list. WITHDRAW broadcast всем кроме отправителя. -- **NAT check для локальных сетей**: По умолчанию запрещён (`allow_nat_check_local=0`), можно включить через `topo_group_set_nat_check_local(group, 1)` для тестов. -- **Aliens**: Узлы с `alien=1` хранятся в nodes, имеют пути, но не инициируют обмен маршрутами при подключении (topo_group_new_conn пропускает). -- **Обновление при NAT-детекции**: При получении NAT_INFO, если изменился nat_type сокета с не-EIM на EIM (или наоборот) — обновляется `local_node`, увеличивается версия и broadcast-ится всем пирам. +- **Хранилище SQLite**: Открывается в `topo_groups_init` если в конфиге задан `db_path`. БД хранится как `instance->topo_sqlite_db` (sqlite3*), используется во всех группах. Файл: `{db_path}/chats.db`. Режим: WAL, foreign_keys=ON, synchronous=NORMAL. +- **Версионирование NODEINFO**: Версия (ver, uint8_t 1-255) сравнивается через циклическую арифметику `(int8_t)(cur_ver - new_ver) >= 0`. Версия 0 не используется (нет данных). Если версия stale — обновляются только пути без замены данных узла. +- **Защита от петель**: NODEINFO рассылается только тем пирам, чей node_id отсутствует во всех путях (hop_list). WITHDRAW broadcast всем кроме отправителя. +- **Связность узлов**: Для новых/обновлённых узлов с адресами автоматически запускается зондирование (`route_connectivity_probe_node`). При удалении узла (withdraw/remove_conn) пробы отменяются (`route_connectivity_cancel_node`). +- **Виртуальные каналы**: При удалении узла закрываются все виртуальные ETCP-каналы через `etcp_router_conn_close_all_for_node`. +- **Dirty-флаг**: При withdraw/remove_conn узлы помечаются `dirty = 1` перед удалением. +- **NAT check для локальных сетей**: По умолчанию запрещён (`allow_nat_check_local=0`), можно включить через `topo_group_set_nat_check_local(group, 1)` для тестов (делегирует в `nat_detection_set_allow_local`). +- **Conn_mgr поля**: `conn_mgr_type`, `conn_mgr_intermediaries[]`, `conn_mgr_intermediariy_count` — используются менеджером соединений для стратегии установки связи с узлом (direct/reverse/indirect). +- **Обновление local_node при NAT-детекции**: Модуль `nat_detection` после определения типа NAT вызывает `topo_group_update_my_nodeinfo()` для обновления local_node с новыми данными сокетов — это автоматически увеличивает версию, и NODEINFO broadcast-ится всем пирам через механизм изменения конфигурации сокетов. + +(End of file - total 302 lines) diff --git a/src/topo_node.c b/src/topo_node.c index e73acc11..41dc34ca 100644 --- a/src/topo_node.c +++ b/src/topo_node.c @@ -297,8 +297,8 @@ void topo_node_dump_all(struct TOPO_GROUP* group) { int is_local = (group->local_node == nq); const char* name_s = ni->node_name ? ni->node_name : ""; - DEBUG_INFO(DEBUG_CATEGORY_BGP, "--- Node #%d: id=%016llx name=\"%s\" ver=%u dirty=%u alien=%u grp=%llu%s ---", - idx, (unsigned long long)ni->node_id, name_s, ni->ver, nq->dirty, nq->alien, + DEBUG_INFO(DEBUG_CATEGORY_BGP, "--- Node #%d: id=%016llx name=\"%s\" ver=%u dirty=%u grp=%llu%s ---", + idx, (unsigned long long)ni->node_id, name_s, ni->ver, nq->dirty, (unsigned long long)ni->group_id, is_local ? " [LOCAL]" : ""); if (nq->best_socket) DEBUG_INFO(DEBUG_CATEGORY_BGP, " best_socket=%p", (void*)nq->best_socket); @@ -395,8 +395,8 @@ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) { #define FMT_ADD(fmt, ...) pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, fmt, ##__VA_ARGS__) - FMT_ADD("--- Node #%d: id=%016llx name=\"%s\" ver=%u dirty=%u alien=%u grp=%llu%s ---\n", - idx, (unsigned long long)ni->node_id, name_s, ni->ver, nq->dirty, nq->alien, + FMT_ADD("--- Node #%d: id=%016llx name=\"%s\" ver=%u dirty=%u grp=%llu%s ---\n", + idx, (unsigned long long)ni->node_id, name_s, ni->ver, nq->dirty, (unsigned long long)ni->group_id, is_local ? " [LOCAL]" : ""); if (nq->best_socket) FMT_ADD(" best_socket=%p\n", (void*)nq->best_socket); diff --git a/src/topo_node.h b/src/topo_node.h index a2e123f0..2c16e780 100644 --- a/src/topo_node.h +++ b/src/topo_node.h @@ -234,7 +234,6 @@ struct TOPO_NODEQ { struct ll_queue* paths; uint8_t dirty; uint8_t last_ver; - uint8_t alien; uint8_t conn_mgr_type; uint64_t conn_mgr_intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; uint8_t conn_mgr_intermediariy_count; diff --git a/src/topo_node_sqlite.c b/src/topo_node_sqlite.c index 5f886ecb..20e7fb36 100644 --- a/src/topo_node_sqlite.c +++ b/src/topo_node_sqlite.c @@ -57,18 +57,17 @@ int topo_node_sqlite_init(sqlite3* db) { "CREATE UNIQUE INDEX IF NOT EXISTS idx_na_unique ON node_addresses(node_id, family, socket_id, addr_type);" "CREATE TABLE IF NOT EXISTS channels (" - " channel_id TEXT PRIMARY KEY," - " name TEXT NOT NULL," - " owner_node_id INTEGER," - " is_dm INTEGER DEFAULT 0," - " x25519_pubkey BLOB NOT NULL," - " x25519_privkey BLOB," - " ed25519_pubkey BLOB NOT NULL," - " ed25519_privkey BLOB," - " signature BLOB NOT NULL," - " last_read_msg_id INTEGER," - " last_pos_msg_id INTEGER," - " created_at INTEGER DEFAULT (unixepoch())" + " channel_id TEXT PRIMARY KEY," // ID канала + " name TEXT NOT NULL," // Название канала + " owner_node_id INTEGER," // node id создателя (надо выпилить) + " x25519_pubkey BLOB NOT NULL," // ключ для шифрования канала + " x25519_privkey BLOB," // если админ - то ключ канала + " ed25519_pubkey BLOB NOT NULL," // ключ для проверки подписи + " ed25519_privkey BLOB," // ключ для создания подписи + " signature BLOB NOT NULL," // подпись при создании канал приватным ключом канала channel_id(NUL-terminated) || name(NUL-terminated) || owner_node_id(8 байт, LE uint64) || x25519_pubkey(32 байта) || ed25519_pubkey(32 байта) + " last_read_msg_id INTEGER," // для скроллинга чата + " last_pos_msg_id INTEGER," // для скроллинга чата + " created_at INTEGER DEFAULT (unixepoch())" // когда создан ");"; char* err = NULL; @@ -79,14 +78,6 @@ int topo_node_sqlite_init(sqlite3* db) { return -1; } - /* migration: add socket_id column if not exists (ignore error for existing DBs) */ - sqlite3_exec(db, - "ALTER TABLE node_addresses ADD COLUMN socket_id INTEGER NOT NULL DEFAULT 0", - NULL, NULL, NULL); - sqlite3_exec(db, - "CREATE UNIQUE INDEX IF NOT EXISTS idx_na_unique ON node_addresses(node_id, family, socket_id, addr_type)", - NULL, NULL, NULL); - DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_node_sqlite tables initialized"); return 0; } @@ -234,7 +225,7 @@ int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq) { } int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, - const char* name, int is_dm, uint64_t owner_node_id, + const char* name, uint64_t owner_node_id, const uint8_t* x25519_pub, const uint8_t* x25519_priv, const uint8_t* ed25519_pub, const uint8_t* ed25519_priv, const uint8_t* signature) { @@ -243,18 +234,19 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); char sql[512]; snprintf(sql, sizeof(sql), - "CREATE TABLE IF NOT EXISTS \"%s\" (" - " node_id INTEGER NOT NULL," - " x25519_pubkey BLOB NOT NULL," - " ed25519_pubkey BLOB NOT NULL," - " join_sig BLOB NOT NULL," - " join_ts INTEGER NOT NULL," - " update_sig BLOB," - " update_ts INTEGER DEFAULT 0," - " name TEXT NOT NULL DEFAULT ''," - " creator_sig BLOB," - " comment TEXT," - " joined_at INTEGER DEFAULT (unixepoch())," + "CREATE TABLE IF NOT EXISTS \"%s\" (" // таблица мемберов канала + " node_id INTEGER NOT NULL," // ид меббера группы + " x25519_pubkey BLOB NOT NULL," // ключ мембера для шифрования + " ed25519_pubkey BLOB NOT NULL," // ключ мембера для подписи + " join_sig BLOB NOT NULL," // подпись мембера неизменяемая (при присоединении) сh_x25519_pubkey(32) || ch_ed25519_pubkey(32) || node_id(8 LE) || node_x25519_pubkey(32) || join_ts(8 LE) + " join_ts INTEGER NOT NULL," // время присоединения + " update_sig BLOB," // подпись мембера изменяемая join_sig(64) || update_ts(8) || userinfo (NUL-terminated) + " update_ts INTEGER DEFAULT 0,"// время подписи + " userinfo TEXT NOT NULL DEFAULT ''," // имя/метаданные узла в канале (json_flat style) + " creator_sig BLOB," // валидация мембера админом канала + " adm_tags TEXT," // тэги которые может менять админ (json_flat style) + " adm_tags_sig BLOB," // подпись тэгов adm_tags (может быть подписана ключом суперадмина или админов - проверяем перебором) + " node_type INTEGER NOT NULL," // ENUM(0-'PRIV',1-'NAT',2-'EIM-NAT',3-'PUBLIC',4-SUPERNODE) тип ноды - private (только нода подключается), nat (односторонние подключения), eim-nat (доступны подключения но надо запрашивать ip), public - на прямых ip (но может быть недоступна в чебурнетах), supernode - на прямых ip, рекомендуемая для подключения " PRIMARY KEY (node_id))", peers_tbl); char* err = NULL; sqlite3_exec(db, sql, NULL, NULL, &err); @@ -262,20 +254,19 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, - "INSERT OR REPLACE INTO channels(channel_id, name, owner_node_id, is_dm," + "INSERT OR REPLACE INTO channels(channel_id, name, owner_node_id," " x25519_pubkey, x25519_privkey, ed25519_pubkey, ed25519_privkey, signature)" - " VALUES(?,?,?,?,?,?,?,?,?)", -1, &stmt, NULL) != SQLITE_OK) return -1; + " VALUES(?,?,?,?,?,?,?,?)", -1, &stmt, NULL) != SQLITE_OK) return -1; sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, name, -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 3, (sqlite3_int64)owner_node_id); - sqlite3_bind_int(stmt, 4, is_dm); - sqlite3_bind_blob(stmt, 5, x25519_pub, 32, SQLITE_STATIC); - if (x25519_priv) sqlite3_bind_blob(stmt, 6, x25519_priv, 32, SQLITE_STATIC); - else sqlite3_bind_null(stmt, 6); - sqlite3_bind_blob(stmt, 7, ed25519_pub, 32, SQLITE_STATIC); - if (ed25519_priv) sqlite3_bind_blob(stmt, 8, ed25519_priv, 32, SQLITE_STATIC); - else sqlite3_bind_null(stmt, 8); - sqlite3_bind_blob(stmt, 9, signature, 64, SQLITE_STATIC); + sqlite3_bind_blob(stmt, 4, x25519_pub, 32, SQLITE_STATIC); + if (x25519_priv) sqlite3_bind_blob(stmt, 5, x25519_priv, 32, SQLITE_STATIC); + else sqlite3_bind_null(stmt, 5); + sqlite3_bind_blob(stmt, 6, ed25519_pub, 32, SQLITE_STATIC); + if (ed25519_priv) sqlite3_bind_blob(stmt, 7, ed25519_priv, 32, SQLITE_STATIC); + else sqlite3_bind_null(stmt, 7); + sqlite3_bind_blob(stmt, 8, signature, 64, SQLITE_STATIC); int rc = sqlite3_step(stmt); sqlite3_finalize(stmt); return rc == SQLITE_DONE ? 0 : -1; } @@ -283,14 +274,14 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t node_id, const uint8_t* join_sig, uint64_t join_ts, const uint8_t* update_sig, uint64_t update_ts, - const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey, const char* name, + const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey, const char* userinfo, const uint8_t* creator_sig) { if (!db || !channel_id || !x25519_pubkey || !ed25519_pubkey) return -1; char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); char sql[256]; snprintf(sql, sizeof(sql), "INSERT OR REPLACE INTO \"%s\"(node_id, x25519_pubkey, ed25519_pubkey," - " join_sig, join_ts, update_sig, update_ts, name, creator_sig)" + " join_sig, join_ts, update_sig, update_ts, userinfo, creator_sig)" " VALUES(?,?,?,?,?,?,?,?,?)", peers_tbl); sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1; @@ -303,7 +294,7 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no if (update_sig) sqlite3_bind_blob(stmt, 6, update_sig, PEERS_UPDATE_SIG_SIZE, SQLITE_STATIC); else sqlite3_bind_null(stmt, 6); sqlite3_bind_int64(stmt, 7, (sqlite3_int64)update_ts); - sqlite3_bind_text(stmt, 8, name ? name : "", -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 8, userinfo ? userinfo : "", -1, SQLITE_STATIC); if (creator_sig) sqlite3_bind_blob(stmt, 9, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); else sqlite3_bind_null(stmt, 9); int rc = sqlite3_step(stmt); sqlite3_finalize(stmt); @@ -359,12 +350,12 @@ int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t no } int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id, - char* name_out, int name_sz, int* is_dm, uint64_t* owner_node_id, + char* name_out, int name_sz, uint64_t* owner_node_id, uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature) { if (!db || !channel_id) return -1; sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, - "SELECT name, owner_node_id, is_dm, x25519_pubkey, ed25519_pubkey, signature" + "SELECT name, owner_node_id, x25519_pubkey, ed25519_pubkey, signature" " FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; } @@ -373,10 +364,9 @@ int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id, if (txt) snprintf(name_out, name_sz, "%s", txt); else name_out[0] = '\0'; } if (owner_node_id) *owner_node_id = (uint64_t)sqlite3_column_int64(stmt, 1); - if (is_dm) *is_dm = sqlite3_column_int(stmt, 2); - if (x25519_pub) memcpy(x25519_pub, sqlite3_column_blob(stmt, 3), 32); - if (ed25519_pub) memcpy(ed25519_pub, sqlite3_column_blob(stmt, 4), 32); - if (signature) memcpy(signature, sqlite3_column_blob(stmt, 5), 64); + if (x25519_pub) memcpy(x25519_pub, sqlite3_column_blob(stmt, 2), 32); + if (ed25519_pub) memcpy(ed25519_pub, sqlite3_column_blob(stmt, 3), 32); + if (signature) memcpy(signature, sqlite3_column_blob(stmt, 4), 64); sqlite3_finalize(stmt); return 0; } @@ -390,7 +380,7 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id, char sql[512]; snprintf(sql, sizeof(sql), "SELECT node_id, x25519_pubkey, ed25519_pubkey," - " join_sig, join_ts, update_sig, update_ts, name" + " join_sig, join_ts, update_sig, update_ts, userinfo" " FROM \"%s\" ORDER BY node_id ASC", peers_tbl); if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1; @@ -407,9 +397,9 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id, uint64_t join_ts = (uint64_t)sqlite3_column_int64(stmt, 4); const uint8_t* update_sig = (const uint8_t*)sqlite3_column_blob(stmt, 5); uint64_t update_ts = (uint64_t)sqlite3_column_int64(stmt, 6); - const char* pname = (const char*)sqlite3_column_text(stmt, 7); + const char* puserinfo = (const char*)sqlite3_column_text(stmt, 7); if (!x25519 || !ed_pub) continue; - uint8_t nl = pname ? (uint8_t)strnlen(pname, 255) : 0; + uint8_t nl = puserinfo ? (uint8_t)strnlen(puserinfo, 255) : 0; sqlite3_stmt* ast = NULL; snprintf(sql, sizeof(sql), @@ -464,7 +454,7 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id, uint64_t z = 0; memcpy(buf + off, &z, 8); off += 8; } buf[off++] = nl; - if (nl) { memcpy(buf + off, pname, nl); off += nl; } + if (nl) { memcpy(buf + off, puserinfo, nl); off += nl; } buf[off++] = addr_cnt; off += addr_sz; (*cnt_ptr)++; diff --git a/src/topo_node_sqlite.h b/src/topo_node_sqlite.h index 0ce3c1ed..c1f21b65 100644 --- a/src/topo_node_sqlite.h +++ b/src/topo_node_sqlite.h @@ -19,7 +19,7 @@ int topo_node_sqlite_init(sqlite3* db); int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq); int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, - const char* name, int is_dm, uint64_t owner_node_id, + const char* name, uint64_t owner_node_id, const uint8_t* x25519_pub, const uint8_t* x25519_priv, const uint8_t* ed25519_pub, const uint8_t* ed25519_priv, const uint8_t* signature); @@ -27,7 +27,7 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t node_id, const uint8_t* join_sig, uint64_t join_ts, const uint8_t* update_sig, uint64_t update_ts, - const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey, const char* name, + const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey, const char* userinfo, const uint8_t* creator_sig); int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64_t node_id, @@ -39,7 +39,7 @@ int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id, const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts); int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id, - char* name_out, int name_sz, int* is_dm, uint64_t* owner_node_id, + char* name_out, int name_sz, uint64_t* owner_node_id, uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature); int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id, diff --git a/src/topo_strategy.txt b/src/topo_strategy.txt new file mode 100644 index 00000000..941ce1e7 --- /dev/null +++ b/src/topo_strategy.txt @@ -0,0 +1,89 @@ +++ OOP: +class Animal { +public: // доступен всем +//private: // только внутри класса +//protected: // внутри класса и наследников + virtual void sound() {play(...);}// virtual — разрешает переопределение. + + virtual void sound2() = 0;// если =0 - метод обязательно должен быть реализован в наследнике, а сам класс становится абстрактным и его экземпляры создавать нельзя. + + Animal() {// конструктор - совпадает с именем класса + cout << "Created"; + } + ~Animal() {// деструктор + cout << "Deleted"; + } + +}; + +class Dog : public Animal {// наследование +public: + void sound() override {}// override — говорит компилятору, что метод действительно переопределяет базовый. +}; + + + + +Стратегия обмена + +узлы которые online живут только в пемяти. и синхронизируемся с ними используя topo_group. + +routing layer: + - topo_group - обеспечивает корректное построение оптимальной таблицы роутинга (per group) между узлами в рамках установленных подключений. И корректировку этой таблицы при перестроении подключений. + + - topo_group_discovery - обеспечивает оптимизацию подключений, а именно: + - периодически пингует узлы проверяя их доступность + + +для каждой группы админ назначает суперузлы: a b c ... стать усуперузлом - получить разрешение с админской подписью. + +суперузел активирует bgp обмен с остаьлными суперузлами. +как: + +суперузел - это по сути сервер для группы. который также синхронизируется с другими суперузлами + +клиенты приоритетно подключаются к суперузлам: + - подключаются к кому-то + - запрашивают таблицу маршрутизации + + + + + +1. Узлы за nat сами выбирают подходящие для себя точки подключения к сети. + 2 шт с минимальным rtt + 2 шт с минимальной загруженностью + + в чате (gui) можно выбрать предпочитаемые узлы для подключения + + +2. узлы с eim nat + ??? если не хватает узлов прямым адресом + +3. узлы с прямыми ип + суть - выстраивают линки между собой для надежного обмена конфигурацией сети + как: + - в таблице узлов для каждой группы + +У каждого узла проставляется доступность (можем ли мы каким-либо образом подключиться): acessible, reverse (при подключении с другой стороны), no + + +Единое обновление инфы об узле (per-group): + - обновляем тип узла в каждой группе где есть узел при обовлении типа нат узла. прямая доступность узла: yes/no. + + + + +======================================= +функционал группы. + +Есть суперадмины группы - владеют приватным ключом суперадмина (один на группу). +Есть админы - владеют сгенерированным приватным ключом админа и подписанным суперадмином. +суперадмины могут делать revoke админу. + +админские действия также распространяются по чату как и обычные сообщения. +ид группы = пубкей группы + + +юзер - join по ссылке (приватный ключ пира). получает ключи других пиров. Может только читать. +админ подписывает разрешение на писать (как и другие разрешения). подпись храним в таблице юзеров diff --git a/tests/test_conn_mgr.c b/tests/test_conn_mgr.c index c8e77fcd..aa5e7557 100644 --- a/tests/test_conn_mgr.c +++ b/tests/test_conn_mgr.c @@ -4,7 +4,6 @@ * * Test 1: Direct via existing ETCP conn → CONN_TYPE_DIRECT * Test 2: Status check → state/type - * Test 3: Alien node → TOPO_NODEQ.alien=1 * * Pending: * Test 4: New link UP callback (INIT to new peer via conn_mgr) @@ -89,17 +88,6 @@ static void test2(void* arg) { fprintf(stderr, "Test 2: status state=%d type=%d\n", st, ty); fflush(stderr); if (st == CONN_MGR_STATE_CONNECTED && ty == CONN_TYPE_DIRECT) fprintf(stderr, " OK: DIRECT\n"); else { fail("status mismatch"); return; } - test3(NULL); -} -static void test3(void* arg) { - (void)arg; - uint64_t aid = 0xEEEE000000000001ULL; - struct TOPO_NODE ni; memset(&ni, 0, sizeof(ni)); ni.node_id = aid; ni.ver = 1; - conn_mgr_add_alien_node(g_a->conn_mgr, (uint8_t*)&ni, sizeof(ni)); - struct TOPO_NODEQ* nq = topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), aid); - fprintf(stderr, "Test 3: alien alien=%d\n", nq ? nq->alien : -1); fflush(stderr); - if (nq && nq->alien == 1) fprintf(stderr, " OK: alien flag set\n"); - else { fail("alien flag not set"); return; } fprintf(stderr, "=== ALL DONE ===\n"); fflush(stderr); result = (result == 0) ? 1 : 2; } diff --git a/tools/chatgui/db/db_manager.cpp b/tools/chatgui/db/db_manager.cpp index 975d66cb..d4fa35d0 100644 --- a/tools/chatgui/db/db_manager.cpp +++ b/tools/chatgui/db/db_manager.cpp @@ -95,7 +95,7 @@ QByteArray DbManager::colBlob(sqlite3_stmt* stmt, int col) { QList DbManager::getChannels() const { QList list; sqlite3_stmt* stmt = prepareOrNull( - "SELECT channel_id, name, owner_node_id, is_dm," + "SELECT channel_id, name, owner_node_id," " x25519_pubkey, x25519_privkey, ed25519_pubkey, ed25519_privkey, signature," " last_read_msg_id, last_pos_msg_id, created_at" " FROM channels ORDER BY created_at ASC"); @@ -105,15 +105,14 @@ QList DbManager::getChannels() const { r.channelId = colText(stmt, 0); r.name = colText(stmt, 1); r.ownerNodeId = (quint64)sqlite3_column_int64(stmt, 2); - r.isDm = sqlite3_column_int(stmt, 3) != 0; - r.x25519_pubkey = colBlob(stmt, 4); - r.x25519_privkey = colBlob(stmt, 5); - r.ed25519_pubkey = colBlob(stmt, 6); - r.ed25519_privkey = colBlob(stmt, 7); - r.signature = colBlob(stmt, 8); - r.lastReadMsgId = sqlite3_column_int64(stmt, 9); - r.lastPosMsgId = sqlite3_column_int64(stmt, 10); - r.createdAt = sqlite3_column_int64(stmt, 11); + r.x25519_pubkey = colBlob(stmt, 3); + r.x25519_privkey = colBlob(stmt, 4); + r.ed25519_pubkey = colBlob(stmt, 5); + r.ed25519_privkey = colBlob(stmt, 6); + r.signature = colBlob(stmt, 7); + r.lastReadMsgId = sqlite3_column_int64(stmt, 8); + r.lastPosMsgId = sqlite3_column_int64(stmt, 9); + r.createdAt = sqlite3_column_int64(stmt, 10); list.append(r); } sqlite3_finalize(stmt); diff --git a/tools/chatgui/db/db_manager.h b/tools/chatgui/db/db_manager.h index 37a99d42..4b570f4b 100644 --- a/tools/chatgui/db/db_manager.h +++ b/tools/chatgui/db/db_manager.h @@ -12,8 +12,6 @@ struct ChannelRow { QString channelId; QString name; quint64 ownerNodeId = 0; - bool isDm = false; - QByteArray x25519_pubkey; QByteArray x25519_privkey; QByteArray ed25519_pubkey; diff --git a/tools/chatgui/doc/db_schema.md b/tools/chatgui/doc/db_schema.md index 5b195025..c859dec1 100644 --- a/tools/chatgui/doc/db_schema.md +++ b/tools/chatgui/doc/db_schema.md @@ -109,7 +109,6 @@ CREATE TABLE IF NOT EXISTS channels ( channel_id TEXT PRIMARY KEY, name TEXT NOT NULL, owner_node_id INTEGER, - is_dm INTEGER DEFAULT 0, x25519_pubkey BLOB NOT NULL, x25519_privkey BLOB, ed25519_pubkey BLOB NOT NULL, @@ -126,7 +125,6 @@ CREATE TABLE IF NOT EXISTS channels ( | `channel_id` | Уникальный ID канала (напр. `"ch:general"`) | | `name` | Отображаемое имя (напр. `"#general"`) | | `owner_node_id` | ID создателя канала | -| `is_dm` | 1 = личная переписка (direct message) | | `x25519_pubkey` | Публичный ключ канала (32 байта) | | `x25519_privkey` | Приватный ключ канала, NULL если локальный узел не админ | | `ed25519_pubkey` | Публичный ключ подписи канала (32 байта) | diff --git a/tools/chatgui/libutun/CMakeLists.txt b/tools/chatgui/libutun/CMakeLists.txt index e92eeaa1..62857b05 100644 --- a/tools/chatgui/libutun/CMakeLists.txt +++ b/tools/chatgui/libutun/CMakeLists.txt @@ -22,6 +22,7 @@ set(UASYNC_SOURCES ${LIB_DIR}/mem.c ${LIB_DIR}/radix.c ${LIB_DIR}/serialize.c + ${LIB_DIR}/json_flat.c ${LIB_DIR}/tcp_io.c ${LIB_DIR}/sqlite3.c ${LIB_DIR}/liblmdb/mdb.c diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index 8797692e..a9c30b8e 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -477,7 +477,6 @@ void MainWindow::onCreateGroupRequested() { snprintf(req->channel_id, sizeof(req->channel_id), "%s", qPrintable(channelId)); snprintf(req->name, sizeof(req->name), "%s", qPrintable(name)); req->owner_node_id = ownerNodeId; - req->is_dm = 0; memcpy(req->x25519_pubkey, x25519_pub, 32); memcpy(req->x25519_privkey, x25519_priv, 32); memcpy(req->ed25519_pubkey, ed25519_pub, 32); diff --git a/tools/chatgui/transport/chat_core.c b/tools/chatgui/transport/chat_core.c index 312abd68..40773669 100644 --- a/tools/chatgui/transport/chat_core.c +++ b/tools/chatgui/transport/chat_core.c @@ -9,6 +9,7 @@ #include "chat_conn_mgr.h" #include "db_sync.h" #include "gui_bridge.h" +#include "../../../lib/json_flat.h" #include "topo_node_sqlite.h" #include "member_sync.h" @@ -275,11 +276,13 @@ void chat_core_update_my_name(const char* name) { /* recompute join_sigs for all channels where I'm a member */ uint64_t myid = g_cc.my_node_id; sqlite3_stmt* cs = NULL; - sqlite3_prepare_v2(g_cc.db, "SELECT channel_id FROM channels", -1, &cs, NULL); + sqlite3_prepare_v2(g_cc.db, "SELECT channel_id, x25519_pubkey, ed25519_pubkey FROM channels", -1, &cs, NULL); if (cs) { while (sqlite3_step(cs) == SQLITE_ROW) { const char* ch = (const char*)sqlite3_column_text(cs, 0); - if (!ch) continue; + const uint8_t* ch_x25519 = sqlite3_column_blob(cs, 1); + const uint8_t* ch_ed25519 = sqlite3_column_blob(cs, 2); + if (!ch || !ch_x25519 || !ch_ed25519) continue; char tbl[80]; peers_table_name(ch, tbl, sizeof(tbl)); char buf[256]; snprintf(buf, sizeof(buf), "SELECT 1 FROM \"%s\" WHERE node_id=?", tbl); sqlite3_stmt* ps = NULL; @@ -287,11 +290,10 @@ void chat_core_update_my_name(const char* name) { sqlite3_bind_int64(ps, 1, (sqlite3_int64)myid); if (sqlite3_step(ps) == SQLITE_ROW) { uint8_t join_msg[256]; size_t mlen = 0; - mlen += snprintf((char*)join_msg + mlen, sizeof(join_msg) - mlen, "%s", ch) + 1; + memcpy(join_msg + mlen, ch_x25519, 32); mlen += 32; + memcpy(join_msg + mlen, ch_ed25519, 32); mlen += 32; memcpy(join_msg + mlen, &myid, 8); mlen += 8; memcpy(join_msg + mlen, g_cc.inst->my_keys.public_key, 32); mlen += 32; - { const char* nm = name[0] ? name : ""; - size_t nl = strlen(nm); memcpy(join_msg + mlen, nm, nl); mlen += nl; join_msg[mlen++] = '\0'; } uint64_t join_ts = (uint64_t)ntp_time_get_seconds(g_cc.inst); memcpy(join_msg + mlen, &join_ts, 8); mlen += 8; uint8_t new_sig[64]; memset(new_sig, 0, 64); @@ -306,11 +308,12 @@ void chat_core_update_my_name(const char* name) { } EVP_PKEY_free(pkey); } + char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", name); int r1 = topo_node_sqlite_member_put(g_cc.db, ch, myid, new_sig, join_ts, NULL, 0, - g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey, name, NULL); + g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey, juser, NULL); if (r1 != 0) DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: member_put(my_name) FAILED ch=%s rc=%d", CC_ID, ch, r1); int r2 = member_sync_put(g_cc.inst, ch, myid, g_cc.inst->my_keys.public_key, - g_cc.inst->my_ed25519_pubkey, new_sig, join_ts, NULL, 0, name, NULL, 0); + g_cc.inst->my_ed25519_pubkey, new_sig, join_ts, NULL, 0, juser, NULL, 0); if (r2 != 0) DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: member_sync_put(my_name) FAILED ch=%s rc=%d", CC_ID, ch, r2); uint8_t evt[65]; uint8_t cl = (uint8_t)strlen(ch); evt[0] = cl; memcpy(evt + 1, ch, cl); @@ -695,7 +698,7 @@ void chat_core_create_channel(struct chat_channel_create* req) { /* записываем канал в БД ПЕРВЫМ — создаст полную схему peers_ (11 колонок) */ int rc = topo_node_sqlite_channel_put(g_cc.db, - req->channel_id, req->name, req->is_dm, req->owner_node_id, + req->channel_id, req->name, req->owner_node_id, req->x25519_pubkey, req->x25519_privkey, req->ed25519_pubkey, req->ed25519_privkey, req->signature); @@ -719,11 +722,10 @@ void chat_core_create_channel(struct chat_channel_create* req) { { uint64_t myid = g_cc.inst->node_id; uint8_t join_msg[256]; size_t mlen = 0; - mlen += snprintf((char*)join_msg + mlen, sizeof(join_msg) - mlen, "%s", req->channel_id) + 1; + memcpy(join_msg + mlen, req->x25519_pubkey, 32); mlen += 32; + memcpy(join_msg + mlen, req->ed25519_pubkey, 32); mlen += 32; memcpy(join_msg + mlen, &myid, 8); mlen += 8; memcpy(join_msg + mlen, g_cc.inst->my_keys.public_key, 32); mlen += 32; - { const char* nm = g_cc.inst->name[0] ? g_cc.inst->name : ""; - size_t nl = strlen(nm); memcpy(join_msg + mlen, nm, nl); mlen += nl; join_msg[mlen++] = '\0'; } uint64_t join_ts = (uint64_t)ntp_time_get_seconds(g_cc.inst); memcpy(join_msg + mlen, &join_ts, 8); mlen += 8; uint8_t join_sig[64]; @@ -760,9 +762,11 @@ void chat_core_create_channel(struct chat_channel_create* req) { s = s->next; } } + char juser3[256]; snprintf(juser3, sizeof(juser3), "{\"name\":\"%s\"}", + g_cc.inst->name[0] ? g_cc.inst->name : ""); int mrc = member_sync_put(g_cc.inst, req->channel_id, myid, g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey, - join_sig, join_ts, NULL, 0, g_cc.inst->name, my_addrs, my_addr_cnt); + join_sig, join_ts, NULL, 0, juser3, my_addrs, my_addr_cnt); if (mrc != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: member_sync_put(self) FAILED ch=%s rc=%d", CC_ID, req->channel_id, mrc); diff --git a/tools/chatgui/transport/chat_core.h b/tools/chatgui/transport/chat_core.h index 3dbb06ca..dc45860a 100644 --- a/tools/chatgui/transport/chat_core.h +++ b/tools/chatgui/transport/chat_core.h @@ -51,7 +51,6 @@ struct chat_channel_create { char channel_id[64]; char name[128]; uint64_t owner_node_id; - int is_dm; uint8_t x25519_pubkey[32]; uint8_t x25519_privkey[32]; uint8_t ed25519_pubkey[32]; diff --git a/tools/chatgui/transport/chat_sync.c b/tools/chatgui/transport/chat_sync.c index b0abc785..527b60cf 100644 --- a/tools/chatgui/transport/chat_sync.c +++ b/tools/chatgui/transport/chat_sync.c @@ -3,6 +3,7 @@ #include "chat_conn_mgr.h" #include "gui_bridge.h" #include "topo_node_sqlite.h" +#include "../../../lib/json_flat.h" #include "member_sync.h" #include "merkle_sync.h" @@ -916,9 +917,9 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, const char* ch_id, const uint8_t* pl, size_t len) { (void)pl; (void)len; DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: INFO_REQ ch=%s from=%016llx", CS_ID, ch_id, (unsigned long long)peer); - char name[128]; int is_dm; uint64_t owner; uint8_t x25519[32], ed_pub[32], ch_sig[64]; + char name[128]; uint64_t owner; uint8_t x25519[32], ed_pub[32], ch_sig[64]; if (topo_node_sqlite_channel_get(cs->inst->topo_sqlite_db, - ch_id, name, (int)sizeof(name), &is_dm, &owner, x25519, ed_pub, ch_sig) != 0) { + ch_id, name, (int)sizeof(name), &owner, x25519, ed_pub, ch_sig) != 0) { DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INFO_REQ unknown ch=%s from=%016llx", CS_ID, ch_id, (unsigned long long)peer); uint8_t err[1] = { CS_MSG_ERROR }; cs_send(cs, ch_id, peer, err, 1); return; @@ -933,7 +934,8 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, sqlite3* vdb = cs->inst->topo_sqlite_db; if (topo_node_sqlite_member_get_join(vdb, ch_id, myid, my_join_sig, &my_join_ts) != 0) { uint8_t join_msg[256]; size_t mlen = 0; - mlen += snprintf((char*)join_msg + mlen, sizeof(join_msg) - mlen, "%s", ch_id) + 1; + memcpy(join_msg + mlen, x25519, 32); mlen += 32; + memcpy(join_msg + mlen, ed_pub, 32); mlen += 32; memcpy(join_msg + mlen, &myid, 8); mlen += 8; memcpy(join_msg + mlen, cs->inst->my_keys.public_key, 32); mlen += 32; my_join_ts = (uint64_t)ntp_time_get_seconds(cs->inst); @@ -946,13 +948,13 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, /* generate update_sig */ uint8_t my_update_sig[64] = {0}; uint64_t my_update_ts; { + char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", + cs->inst->name[0] ? cs->inst->name : ""); uint8_t umsg[256]; size_t ulen = 0; - ulen += snprintf((char*)umsg + ulen, sizeof(umsg) - ulen, "%s", ch_id) + 1; - memcpy(umsg + ulen, &myid, 8); ulen += 8; - memcpy(umsg + ulen, cs->inst->my_keys.public_key, 32); ulen += 32; + memcpy(umsg + ulen, my_join_sig, 64); ulen += 64; my_update_ts = (uint64_t)ntp_time_get_seconds(cs->inst); memcpy(umsg + ulen, &my_update_ts, 8); ulen += 8; - memcpy(umsg + ulen, my_join_sig, 64); ulen += 64; + size_t jl = strlen(juser); memcpy(umsg + ulen, juser, jl); umsg[ulen + jl] = '\0'; ulen += jl + 1; cs_ed25519_sign(cs->inst->my_ed25519_privkey, umsg, ulen, my_update_sig); } @@ -961,7 +963,6 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, uint8_t nl = (uint8_t)strlen(name); buf[boff++] = nl; memcpy(buf + boff, name, nl); boff += nl; memcpy(buf + boff, &owner, 8); boff += 8; - buf[boff++] = (uint8_t)is_dm; memcpy(buf + boff, x25519, 32); boff += 32; memcpy(buf + boff, ed_pub, 32); boff += 32; memcpy(buf + boff, ch_sig, 64); boff += 64; @@ -973,9 +974,10 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, } memcpy(buf + boff, my_update_sig, 64); boff += 64; memcpy(buf + boff, &my_update_ts, 8); boff += 8; - { const char* iname = cs->inst->name[0] ? cs->inst->name : ""; - uint8_t il = (uint8_t)strlen(iname); - buf[boff++] = il; memcpy(buf + boff, iname, il); boff += il; } + char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", + cs->inst->name[0] ? cs->inst->name : ""); + uint8_t il = (uint8_t)strlen(juser); + buf[boff++] = il; memcpy(buf + boff, juser, il); boff += il; cs_send(cs, ch_id, peer, buf, boff); } @@ -987,11 +989,10 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, if (cs->info_req_timer) { uasync_cancel_timeout(cs->inst->ua, cs->info_req_timer); cs->info_req_timer = NULL; } if (len < 1) { DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INFO_RESP too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; } uint8_t nl = pl[0]; - if (1 + nl + 8 + 1 + 32 + 32 + 64 + 1 + 64 + 8 + 1 > len) { DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INFO_RESP truncated len=%zu", CS_ID, len); return; } + if (1 + nl + 8 + 32 + 32 + 64 + 1 + 64 + 8 + 1 > len) { DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INFO_RESP truncated len=%zu", CS_ID, len); return; } const uint8_t* p = pl + 1; char name[128]; memcpy(name, p, nl); name[nl] = '\0'; p += nl; uint64_t owner; memcpy(&owner, p, 8); p += 8; - uint8_t is_dm = *p++; const uint8_t* x25519 = p; p += 32; const uint8_t* ed_pub = p; p += 32; const uint8_t* ch_sig = p; p += 64; @@ -1001,9 +1002,9 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, if (p + 72 > pl + len) return; inviter_join_sig = p; p += 64; memcpy(&inviter_join_ts, p, 8); p += 8; } const uint8_t* inviter_update_sig = p; p += 64; uint64_t inviter_update_ts; memcpy(&inviter_update_ts, p, 8); p += 8; - uint8_t inv_name_len = *p++; - char inv_name[128] = ""; - if (p + inv_name_len <= pl + len) { memcpy(inv_name, p, inv_name_len); inv_name[inv_name_len] = '\0'; } + uint8_t inv_userinfo_len = *p++; + char inv_userinfo[128] = ""; + if (p + inv_userinfo_len <= pl + len) { memcpy(inv_userinfo, p, inv_userinfo_len); inv_userinfo[inv_userinfo_len] = '\0'; } DEBUG_DEBUG(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INFO_RESP from peer=%016llx owner=%016llx ch_x25519=%016llx ch_ed=%016llx name=%s", CS_ID, (unsigned long long)peer, (unsigned long long)owner, @@ -1023,7 +1024,7 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, /* save channel to local DB */ topo_node_sqlite_channel_put(cs->inst->topo_sqlite_db, - ch_id, name, (int)is_dm, owner, x25519, NULL, ed_pub, NULL, ch_sig); + ch_id, name, owner, x25519, NULL, ed_pub, NULL, ch_sig); chat_core_ensure_channel_ready(ch_id); DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: channel ready for sync ch=%s, db_sync will pick up via periodic check or active conn", CS_ID, ch_id); @@ -1037,21 +1038,18 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, const uint8_t* inv_x25519 = inv_conn->crypto_ctx.peer_public_key; const uint8_t* inv_ed = inv_conn->peer_ed25519_pubkey; uint8_t ivmsg[256]; size_t ilen = 0; - ilen += snprintf((char*)ivmsg + ilen, sizeof(ivmsg) - ilen, "%s", ch_id) + 1; - memcpy(ivmsg + ilen, &peer, 8); ilen += 8; - memcpy(ivmsg + ilen, inv_x25519, 32); ilen += 32; - memcpy(ivmsg + ilen, &inviter_update_ts, 8); ilen += 8; - { const char* nm = inv_name[0] ? inv_name : ""; - size_t nl2 = strlen(nm); (void)nl2; } if (inviter_join_sig) memcpy(ivmsg + ilen, inviter_join_sig, 64); else memset(ivmsg + ilen, 0, 64); ilen += 64; + memcpy(ivmsg + ilen, &inviter_update_ts, 8); ilen += 8; + { const char* nm = inv_userinfo[0] ? inv_userinfo : ""; + size_t nl2 = strlen(nm); memcpy(ivmsg + ilen, nm, nl2 + 1); ilen += nl2 + 1; } if (cs_ed25519_verify(inv_ed, ivmsg, ilen, inviter_update_sig) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INFO_RESP invalid inviter_update_sig peer=%016llx ts=%llu", CS_ID, (unsigned long long)peer, (unsigned long long)inviter_update_ts); } /* save inviter node_info to local DB */ sqlite3* vdb = cs->inst->topo_sqlite_db; - if (vdb && inv_name[0]) { + if (vdb && inv_userinfo[0]) { sqlite3_stmt* ns = NULL; sqlite3_prepare_v2(vdb, "INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)" @@ -1059,7 +1057,7 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey", -1, &ns, NULL); if (ns) { sqlite3_bind_int64(ns, 1, (sqlite3_int64)peer); - sqlite3_bind_text(ns, 2, inv_name, -1, SQLITE_STATIC); + sqlite3_bind_text(ns, 2, inv_userinfo, -1, SQLITE_STATIC); sqlite3_bind_blob(ns, 3, inv_x25519, 32, SQLITE_STATIC); sqlite3_bind_blob(ns, 4, inv_ed, 32, SQLITE_STATIC); sqlite3_step(ns); sqlite3_finalize(ns); } @@ -1093,8 +1091,9 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] INFO_RESP DONE peer=%016llx: %d links, %d written", CS_ID, (unsigned long long)peer, lk_count, lk_written); } - int rc = topo_node_sqlite_member_put(vdb, ch_id, peer, inviter_join_sig, inviter_join_ts, inviter_update_sig, inviter_update_ts, inv_x25519, inv_ed, inv_name, inviter_join_sig); + int rc = topo_node_sqlite_member_put(vdb, ch_id, peer, inviter_join_sig, inviter_join_ts, inviter_update_sig, inviter_update_ts, inv_x25519, inv_ed, inv_userinfo, inviter_join_sig); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(inviter) FAILED ch=%s peer=%016llx rc=%d", CS_ID, ch_id, (unsigned long long)peer, rc); + char inv_name[128]; json_flat_get(inv_userinfo, "name", inv_name, sizeof(inv_name)); topo_node_sqlite_node_update_verified(vdb, peer, inv_name, inv_x25519, inv_ed, inviter_join_ts); } @@ -1105,11 +1104,10 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, uint8_t join_sig[64]; uint64_t join_ts; { uint8_t msg[256]; size_t mlen = 0; - mlen += snprintf((char*)msg + mlen, sizeof(msg) - mlen, "%s", ch_id) + 1; + memcpy(msg + mlen, x25519, 32); mlen += 32; + memcpy(msg + mlen, ed_pub, 32); mlen += 32; memcpy(msg + mlen, &myid, 8); mlen += 8; memcpy(msg + mlen, my_x25519, 32); mlen += 32; - { const char* nm = cs->inst->name[0] ? cs->inst->name : ""; - size_t nl = strlen(nm); memcpy(msg + mlen, nm, nl); mlen += nl; msg[mlen++] = '\0'; } join_ts = (uint64_t)ntp_time_get_seconds(cs->inst); memcpy(msg + mlen, &join_ts, 8); mlen += 8; cs_ed25519_sign(cs->inst->my_ed25519_privkey, msg, mlen, join_sig); @@ -1123,9 +1121,10 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, memcpy(jbuf + joff, cs->inst->my_ed25519_pubkey, 32); joff += 32; memcpy(jbuf + joff, join_sig, 64); joff += 64; memcpy(jbuf + joff, &join_ts, 8); joff += 8; - { const char* nm = cs->inst->name[0] ? cs->inst->name : ""; - uint8_t nml = (uint8_t)strlen(nm); - jbuf[joff++] = nml; memcpy(jbuf + joff, nm, nml); joff += nml; } + char juser2[256]; snprintf(juser2, sizeof(juser2), "{\"name\":\"%s\"}", + cs->inst->name[0] ? cs->inst->name : ""); + uint8_t nml = (uint8_t)strlen(juser2); + jbuf[joff++] = nml; memcpy(jbuf + joff, juser2, nml); joff += nml; /* add local addresses */ uint8_t addr_cnt = 0; size_t ac_pos = joff; jbuf[joff++] = 0; @@ -1178,33 +1177,39 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, const uint8_t* ed_pub = p; p += 32; const uint8_t* join_sig = p; p += 64; uint64_t join_ts; memcpy(&join_ts, p, 8); p += 8; - uint8_t name_len = *p++; - char joiner_name[128] = ""; - if (p + name_len <= pl + len) { memcpy(joiner_name, p, name_len); joiner_name[name_len] = '\0'; p += name_len; } + uint8_t userinfo_len = *p++; + char joiner_userinfo[128] = ""; + if (p + userinfo_len <= pl + len) { memcpy(joiner_userinfo, p, userinfo_len); joiner_userinfo[userinfo_len] = '\0'; p += userinfo_len; } uint8_t addr_cnt = *p++; /* verify join_sig */ + uint8_t ch_x25519[32], ch_ed25519[32]; + if (topo_node_sqlite_channel_get(cs->inst->topo_sqlite_db, ch_id, + NULL, 0, NULL, ch_x25519, ch_ed25519, NULL) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: JOIN channel not in DB ch=%s", CS_ID, ch_id); + return; + } uint8_t vmsg[256]; size_t vlen = 0; - vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_id) + 1; + memcpy(vmsg + vlen, ch_x25519, 32); vlen += 32; + memcpy(vmsg + vlen, ch_ed25519, 32); vlen += 32; memcpy(vmsg + vlen, &node_id, 8); vlen += 8; memcpy(vmsg + vlen, x25519, 32); vlen += 32; - { const char* nm = joiner_name[0] ? joiner_name : ""; - size_t nl = strlen(nm); memcpy(vmsg + vlen, nm, nl); vlen += nl; vmsg[vlen++] = '\0'; } memcpy(vmsg + vlen, &join_ts, 8); vlen += 8; if (cs_ed25519_verify(ed_pub, vmsg, vlen, join_sig) != 0) { - DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: JOIN invalid sig node=0x%016llx ch=%s — signed(node=%016llx x25519=%016llx ed=%016llx name=%s ts=%llu)", CS_ID, + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: JOIN invalid sig node=0x%016llx ch=%s — signed(node=%016llx x25519=%016llx ed=%016llx ts=%llu)", CS_ID, (unsigned long long)node_id, ch_id, - (unsigned long long)node_id, *(const uint64_t*)x25519, *(const uint64_t*)ed_pub, joiner_name, (unsigned long long)join_ts); + (unsigned long long)node_id, *(const uint64_t*)x25519, *(const uint64_t*)ed_pub, (unsigned long long)join_ts); return; } sqlite3* db = cs->inst->topo_sqlite_db; - int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, joiner_name, NULL); + int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, joiner_userinfo, NULL); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(joiner) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc); - topo_node_sqlite_node_update_verified(db, node_id, joiner_name, x25519, ed_pub, join_ts); + { char j_name[128]; json_flat_get(joiner_userinfo, "name", j_name, sizeof(j_name)); + topo_node_sqlite_node_update_verified(db, node_id, j_name, x25519, ed_pub, join_ts); } chat_conn_mgr_add_node(chat_conn_mgr_get(), node_id); /* save joiner node_info to local DB */ - if (db && joiner_name[0]) { + if (db && joiner_userinfo[0]) { sqlite3_stmt* ns = NULL; sqlite3_prepare_v2(db, "INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)" @@ -1212,7 +1217,7 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey", -1, &ns, NULL); if (ns) { sqlite3_bind_int64(ns, 1, (sqlite3_int64)node_id); - sqlite3_bind_text(ns, 2, joiner_name, -1, SQLITE_STATIC); + sqlite3_bind_text(ns, 2, joiner_userinfo, -1, SQLITE_STATIC); sqlite3_bind_blob(ns, 3, x25519, 32, SQLITE_STATIC); sqlite3_bind_blob(ns, 4, ed_pub, 32, SQLITE_STATIC); sqlite3_step(ns); sqlite3_finalize(ns); } @@ -1271,12 +1276,12 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, memcpy(ubuf + uoff, ed_pub, 32); uoff += 32; memcpy(ubuf + uoff, join_sig, 64); uoff += 64; memcpy(ubuf + uoff, &join_ts, 8); uoff += 8; - ubuf[uoff++] = name_len; - memcpy(ubuf + uoff, joiner_name, name_len); uoff += name_len; + ubuf[uoff++] = userinfo_len; + memcpy(ubuf + uoff, joiner_userinfo, userinfo_len); uoff += userinfo_len; ubuf[uoff++] = addr_cnt; - size_t addr_data_sz = (size_t)(p - (pl + 8 + 32 + 32 + 64 + 8 + 1 + name_len + 1)); + size_t addr_data_sz = (size_t)(p - (pl + 8 + 32 + 32 + 64 + 8 + 1 + userinfo_len + 1)); if (uoff + addr_data_sz <= sizeof(ubuf)) { - memcpy(ubuf + uoff, pl + 8 + 32 + 32 + 64 + 8 + 1 + name_len + 1, addr_data_sz); + memcpy(ubuf + uoff, pl + 8 + 32 + 32 + 64 + 8 + 1 + userinfo_len + 1, addr_data_sz); uoff += addr_data_sz; } cs_propagate(cs, ch_id, peer, ubuf, uoff); @@ -1336,18 +1341,23 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, uint64_t update_ts; memcpy(&update_ts, p, 8); p += 8; (void)update_sig; (void)update_ts; uint8_t nl = *p++; - char peer_name[256] = ""; - if (nl && p + nl <= pl + len) { memcpy(peer_name, p, nl); peer_name[nl] = '\0'; p += nl; } + char peer_userinfo[256] = ""; + if (nl && p + nl <= pl + len) { memcpy(peer_userinfo, p, nl); peer_userinfo[nl] = '\0'; p += nl; } uint8_t ac = *p++; /* verify join_sig if present */ if (join_sig) { + uint8_t ch_x2[32], ch_ed2[32]; + if (topo_node_sqlite_channel_get(db, ch_id, + NULL, 0, NULL, ch_x2, ch_ed2, NULL) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: WELCOME channel not in DB ch=%s", CS_ID, ch_id); + continue; + } uint8_t vmsg[256]; size_t vlen = 0; - vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_id) + 1; + memcpy(vmsg + vlen, ch_x2, 32); vlen += 32; + memcpy(vmsg + vlen, ch_ed2, 32); vlen += 32; memcpy(vmsg + vlen, &node_id, 8); vlen += 8; memcpy(vmsg + vlen, x25519, 32); vlen += 32; - { const char* nm = peer_name[0] ? peer_name : ""; - size_t nls = strlen(nm); memcpy(vmsg + vlen, nm, nls); vlen += nls; vmsg[vlen++] = '\0'; } memcpy(vmsg + vlen, &join_ts, 8); vlen += 8; EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ed_pub, 32); if (pkey) { @@ -1365,9 +1375,10 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, } } - int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_name, NULL); + int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(welcome) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc); - topo_node_sqlite_node_update_verified(db, node_id, peer_name, x25519, ed_pub, join_ts); + { char pn[256]; json_flat_get(peer_userinfo, "name", pn, sizeof(pn)); + topo_node_sqlite_node_update_verified(db, node_id, pn, x25519, ed_pub, join_ts); } chat_conn_mgr_add_node(chat_conn_mgr_get(), node_id); @@ -1442,18 +1453,23 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, const uint8_t* ed_pub = p; p += 32; const uint8_t* join_sig = p; p += 64; uint64_t join_ts; memcpy(&join_ts, p, 8); p += 8; - uint8_t name_len = *p++; - char peer_name[128] = ""; - if (p + name_len <= pl + len) { memcpy(peer_name, p, name_len); peer_name[name_len] = '\0'; p += name_len; } + uint8_t userinfo_len = *p++; + char peer_userinfo[128] = ""; + if (p + userinfo_len <= pl + len) { memcpy(peer_userinfo, p, userinfo_len); peer_userinfo[userinfo_len] = '\0'; p += userinfo_len; } uint8_t ac = *p++; /* verify join_sig */ + uint8_t ch_x3[32], ch_ed3[32]; + if (topo_node_sqlite_channel_get(cs->inst->topo_sqlite_db, ch_id, + NULL, 0, NULL, ch_x3, ch_ed3, NULL) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: PEER_UPSERT channel not in DB ch=%s", CS_ID, ch_id); + return; + } uint8_t vmsg[256]; size_t vlen = 0; - vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_id) + 1; + memcpy(vmsg + vlen, ch_x3, 32); vlen += 32; + memcpy(vmsg + vlen, ch_ed3, 32); vlen += 32; memcpy(vmsg + vlen, &node_id, 8); vlen += 8; memcpy(vmsg + vlen, x25519, 32); vlen += 32; - { const char* nm = peer_name[0] ? peer_name : ""; - size_t nl = strlen(nm); memcpy(vmsg + vlen, nm, nl); vlen += nl; vmsg[vlen++] = '\0'; } memcpy(vmsg + vlen, &join_ts, 8); vlen += 8; if (cs_ed25519_verify(ed_pub, vmsg, vlen, join_sig) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: PEER_UPSERT invalid sig node=0x%016llx", CS_ID, @@ -1463,11 +1479,12 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, sqlite3* db = cs->inst->topo_sqlite_db; - int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_name, NULL); + int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(peer_upsert) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc); - topo_node_sqlite_node_update_verified(db, node_id, peer_name, x25519, ed_pub, join_ts); + { char pn2[128]; json_flat_get(peer_userinfo, "name", pn2, sizeof(pn2)); + topo_node_sqlite_node_update_verified(db, node_id, pn2, x25519, ed_pub, join_ts); } chat_conn_mgr_add_node(chat_conn_mgr_get(), node_id); - if (db && peer_name[0]) { + if (db && peer_userinfo[0]) { sqlite3_stmt* ns = NULL; sqlite3_prepare_v2(db, "INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)" @@ -1475,7 +1492,7 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey", -1, &ns, NULL); if (ns) { sqlite3_bind_int64(ns, 1, (sqlite3_int64)node_id); - sqlite3_bind_text(ns, 2, peer_name, -1, SQLITE_STATIC); + sqlite3_bind_text(ns, 2, peer_userinfo, -1, SQLITE_STATIC); sqlite3_bind_blob(ns, 3, x25519, 32, SQLITE_STATIC); sqlite3_bind_blob(ns, 4, ed_pub, 32, SQLITE_STATIC); sqlite3_step(ns); sqlite3_finalize(ns); } diff --git a/tools/chatgui/transport/member_sync.c b/tools/chatgui/transport/member_sync.c index 691bbd9d..fe7a3414 100644 --- a/tools/chatgui/transport/member_sync.c +++ b/tools/chatgui/transport/member_sync.c @@ -139,7 +139,7 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level, char sql[512]; snprintf(sql, sizeof(sql), "SELECT node_id, x25519_pubkey, ed25519_pubkey," - " join_sig, join_ts, update_sig, update_ts, name" + " join_sig, join_ts, update_sig, update_ts, userinfo" " FROM \"%s\"" " WHERE (node_id & %lld) == %lld ORDER BY node_id ASC", peers_tbl, (long long)mask, (long long)prefix); @@ -254,11 +254,9 @@ static int _member_apply_items(void* ctx, const char* ns, /* verify update_sig if available */ if (usig && uts && jsig && ed) { uint8_t vmsg[256]; size_t vlen = 0; - vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ns) + 1; - memcpy(vmsg + vlen, &nid, 8); vlen += 8; - memcpy(vmsg + vlen, x25, 32); vlen += 32; - memcpy(vmsg + vlen, &uts, 8); vlen += 8; memcpy(vmsg + vlen, jsig, 64); vlen += 64; + memcpy(vmsg + vlen, &uts, 8); vlen += 8; + size_t nl2 = nm ? strlen(nm) : 0; memcpy(vmsg + vlen, nm ? nm : "", nl2 + 1); vlen += nl2 + 1; { EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ed, 32); if (pkey) { EVP_MD_CTX* ver = EVP_MD_CTX_new(); @@ -332,18 +330,18 @@ int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id, const uint8_t* ed25519, const uint8_t* join_sig, uint64_t join_ts, const uint8_t* update_sig, uint64_t update_ts, - const char* name, + const char* userinfo, const uint8_t* addrs_data, int addr_count) { if (!inst || !ch_id) return -1; - DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: put ch=%s nid=%016llx name=%s ac=%d", MS_ID, ch_id, (unsigned long long)member_id, name ? name : "", addr_count); + DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: put ch=%s nid=%016llx userinfo=%s ac=%d", MS_ID, ch_id, (unsigned long long)member_id, userinfo ? userinfo : "", addr_count); sqlite3* db = _db(inst); if (!db) return -1; int rc = topo_node_sqlite_member_put(db, ch_id, member_id, join_sig, join_ts, update_sig, update_ts, - x25519, ed25519, name, NULL); + x25519, ed25519, userinfo, NULL); if (rc != 0) { - DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put FAILED ch=%s id=0x%016llx name=%s rc=%d", - MS_ID, ch_id, (unsigned long long)member_id, name ? name : "", rc); + DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put FAILED ch=%s id=0x%016llx userinfo=%s rc=%d", + MS_ID, ch_id, (unsigned long long)member_id, userinfo ? userinfo : "", rc); return -1; } diff --git a/tools/chatgui/transport/member_sync.h b/tools/chatgui/transport/member_sync.h index c7f0b6e7..01ea6b06 100644 --- a/tools/chatgui/transport/member_sync.h +++ b/tools/chatgui/transport/member_sync.h @@ -84,9 +84,9 @@ void member_sync_cancel(struct UTUN_INSTANCE* inst, uint64_t peer, const char* c * x25519 — X25519 публичный ключ (32 байта) * ed25519 — Ed25519 публичный ключ (32 байта) * join_sig — Ed25519 подпись join-сообщения (64 байта) - * сообщение: ch_id || '\0' || node_id(8LE) || x25519(32) || name || '\0' || join_ts(8LE) + * сообщение: ch_x25519(32) || ch_ed25519(32) || node_id(8LE) || x25519(32) || join_ts(8LE) * join_ts — NTP-синхронизированное время подписи (unix epoch seconds) - * name — имя узла (из подписанного сообщения) + * userinfo — метаданные узла (имя) в канале * addrs_data — адреса в wire-формате: [family:1][ip:4|16][port:2]... * addr_count — количество адресов (0 если нет) * @@ -101,7 +101,7 @@ int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id, const uint8_t* ed25519, const uint8_t* join_sig, uint64_t join_ts, const uint8_t* update_sig, uint64_t update_ts, - const char* name, + const char* userinfo, const uint8_t* addrs_data, int addr_count); /* Удалить мембера из канала и пересчитать дерево. */ diff --git a/tools/chatgui/transport/merkle_sync.h b/tools/chatgui/transport/merkle_sync.h index dcf9f692..8aa88fe9 100644 --- a/tools/chatgui/transport/merkle_sync.h +++ b/tools/chatgui/transport/merkle_sync.h @@ -120,7 +120,7 @@ struct merkle_sync_data_ops { * *len — [in] размер буфера, [out] записанный размер * * Wire-формат для мемберов: - * [count:2][node_id:8][x25519:32][ed25519:32][flags:1]([join_sig:64][join_ts:8])[update_sig:64][update_ts:8][name_len:1][name:var][addr_cnt:1][addrs:var]... + * [count:2][node_id:8][x25519:32][ed25519:32][flags:1]([join_sig:64][join_ts:8])[update_sig:64][update_ts:8][userinfo_len:1][userinfo:var][addr_cnt:1][addrs:var]... * * Возвращает 0 при успехе, <0 при ошибке, -2 если буфер мал. */