diff --git a/src/config_parser_doc.md b/src/config_parser_doc.md index 5b822d5c..686d0260 100644 --- a/src/config_parser_doc.md +++ b/src/config_parser_doc.md @@ -93,7 +93,7 @@ auto_sockets принимает no/yes/android, auto_socket_protocols — tcp/ud `print_config` выводит снимок в config/INFO. Private/signing keys заменены признаками наличия; сети выводятся независимо от количества firewall rules. -`[chatserver] add_key` — 32 ненулевых случайных байта (64 HEX-символа), разрешающие запросить +`[chatserver] add_key` — ненулевой ключ из 32 случайных байтов (64 HEX-символа), разрешающий запросить добавление сервера в произвольный канал приглашающего. При загрузке из файла отсутствующий ключ создаёт и атомарно сохраняет config_updater; заданный ошибочный/нулевой ключ отклоняется. Ключ постоянный: перезапуск сохраняет ссылку, явная замена запрещает новые операции по старой ссылке. diff --git a/tests/test_chat_headless_control.c b/tests/test_chat_headless_control.c index 1e17d1d3..e88a15e7 100644 --- a/tests/test_chat_headless_control.c +++ b/tests/test_chat_headless_control.c @@ -89,6 +89,7 @@ int main(void) { struct UASYNC* ua = uasync_create(); assert(ua); char directory[] = "/tmp/utun-headless-XXXXXX", config[700]; assert(mkdtemp(directory)); snprintf(config, sizeof(config), "[global]\nmy_node_name=headless\ndb_path=%s\n[ntp]\nenabled=no\n" + "[server: udp]\naddr=127.0.0.1:0\ntype=public\n" "[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n", directory); struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); assert(inst && !utun_core_start(inst) && !chat_service_start(inst)); chat_event_set_handler(inst, hc_observe); size_t before = u_get_allocated_count(); @@ -117,6 +118,25 @@ int main(void) { unsigned old_observed = observed; chat_event_post(inst, CHAT_EVT_STATUS_REFRESH, NULL, 0); assert(observed == old_observed + 1); hc_request_line(inst, peer, "{\"id\":11,\"cmd\":\"status\"}\n", response); assert(hc_response_ok(inst->topo_sqlite_db, response, 11)); uint64_t channel = chat_core_create_channel_auto(inst, "media"); assert(channel); + hc_request_line(inst, peer, "{\"id\":20,\"cmd\":\"server_link\"}\n", response); + assert(hc_response_ok(inst->topo_sqlite_db, response, 20)); + sqlite3_stmt* server_link = NULL; + assert(sqlite3_prepare_v2(inst->topo_sqlite_db, "SELECT json_extract(?1,'$.data.link')", -1, &server_link, NULL) == SQLITE_OK); + assert(sqlite3_bind_text(server_link, 1, response, -1, SQLITE_STATIC) == SQLITE_OK && sqlite3_step(server_link) == SQLITE_ROW); + const char* link = (const char*)sqlite3_column_text(server_link, 0); struct InviteData server_target; + assert(link && !invite_link_decode(link, strlen(link), &server_target, NULL, 0)); + assert(server_target.kind == INVITE_KIND_SERVER && server_target.nodeId == inst->node_id); + char add[INVITE_LINK_MAX + 128]; + snprintf(add, sizeof(add), "{\"id\":21,\"cmd\":\"server_add\",\"ch\":\"%llu\",\"link\":\"%s\"}\n", + (unsigned long long)channel, link); + assert(sqlite3_finalize(server_link) == SQLITE_OK); + /* Нельзя добавить свой узел самому себе; команда обязана вернуть отказ запуска. */ + hc_request_line(inst, peer, add, response); assert(!hc_response_ok(inst->topo_sqlite_db, response, 21)); + hc_request_line(inst, peer, "{\"id\":22,\"cmd\":\"server_add\",\"ch\":\"1\"}\n", response); + assert(!hc_response_ok(inst->topo_sqlite_db, response, 22)); + inst->config->global.chatserver_has_add_key = 0; + hc_request_line(inst, peer, "{\"id\":23,\"cmd\":\"server_link\"}\n", response); + assert(!hc_response_ok(inst->topo_sqlite_db, response, 23)); inst->config->global.chatserver_has_add_key = 1; char poll[1024]; snprintf(poll, sizeof(poll), "{\"id\":17,\"cmd\":\"poll_create\",\"ch\":\"%llu\",\"question\":\"Q?\"," "\"mode\":\"single\",\"allow_custom_answer\":true,\"options\":[\"yes\",\"no\"]}\n", (unsigned long long)channel); hc_request_line(inst, peer, poll, response); assert(hc_response_ok(inst->topo_sqlite_db, response, 17)); diff --git a/tools/chatcli b/tools/chatcli index a602bb71..15b30023 100755 --- a/tools/chatcli +++ b/tools/chatcli @@ -22,6 +22,8 @@ # attach [--video] send a file (or video) as attachment # invite [node_id] create invite link (auto best node by default) # invite_nodes list reachable nodes for invite +# server_link get this server's link with its persistent access key +# server_add add our channel on the server identified by its link # connect join channel via invite link # create create new channel # invite_to [proto]