You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
7.8 KiB
129 lines
7.8 KiB
/* INVITE_INFO получает подписанное описание канала, но не добавляет нового мембера. |
|
* Фиксированный X25519-ключ ответчика начинается с 00: это допустимый ключ. */ |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include "../lib/platform_compat.h" |
|
#include "../lib/socket_compat.h" |
|
#include "../lib/debug_config.h" |
|
#include "../src/utun_instance.h" |
|
#include "../src/chat/chat_core.h" |
|
#include "../src/chat/invite_link.h" |
|
#include "../src/routing_layer/topo_group.h" |
|
#include "../src/routing_layer/topo_node_sqlite.h" |
|
#include "../src/routing_layer/topo_group_invite.h" |
|
#include "../src/transport_layer/etcp_connections.h" |
|
|
|
#define REQUIRE(expr) do { if (!(expr)) { \ |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "test_invite_group_create: line=%d failed: %s", __LINE__, #expr); \ |
|
goto cleanup; \ |
|
} } while (0) |
|
|
|
struct invite_result { |
|
int calls, event; |
|
uint64_t node_id, group_id; |
|
}; |
|
|
|
/* Сохранить результат; жизненный цикл экземпляров меняется после выхода из callback. */ |
|
static void invite_done(uint64_t node_id, uint64_t group_id, int event, void* arg) { |
|
struct invite_result* result = arg; |
|
result->calls++; result->event = event; result->node_id = node_id; result->group_id = group_id; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "test invite result: peer=%016llx group=%016llx event=%d calls=%d", |
|
(unsigned long long)node_id, (unsigned long long)group_id, event, result->calls); |
|
} |
|
|
|
int main(void) { |
|
int result = 1; |
|
struct UASYNC* ua = NULL; |
|
struct UTUN_INSTANCE *a = NULL, *b = NULL; |
|
struct invite_result reply = {0}; |
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); |
|
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_INFO); |
|
debug_set_category_level(DEBUG_CATEGORY_CONNECTION, DEBUG_LEVEL_INFO); |
|
debug_set_category_level(DEBUG_CATEGORY_BGP, DEBUG_LEVEL_INFO); |
|
|
|
ua = uasync_create(); REQUIRE(ua); |
|
/* У каждого узла своя in-memory БД и настоящий UDP-сокет на свободном порту. */ |
|
a = utun_instance_create_from_str(ua, |
|
"[global]\nmy_node_name=invite-A\n" |
|
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n" |
|
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n" |
|
"[server: udp]\naddr=127.0.0.1:0\ntype=public\n[allowed_keys]\nallow_all=1\n" |
|
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n"); |
|
b = utun_instance_create_from_str(ua, |
|
"[global]\nmy_node_name=invite-B\n" |
|
"my_private_key=a00a000000000000000000000000000000000000000000000000000000000000\n" |
|
"my_public_key=00b607cef42a8324d990aaa6f19d190d3ee9120369ade4d0f94b539edb050b64\n" |
|
"[server: udp]\naddr=127.0.0.1:0\ntype=public\n[allowed_keys]\nallow_all=1\n" |
|
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n"); |
|
REQUIRE(a && b); |
|
REQUIRE(utun_core_start(a) == 0 && chat_service_start(a) == 0); |
|
REQUIRE(utun_core_start(b) == 0 && chat_service_start(b) == 0); |
|
topo_group_invite_init(a); topo_group_invite_init(b); |
|
REQUIRE(a->topo_sqlite_db != b->topo_sqlite_db && !a->utun_started && !b->utun_started); |
|
REQUIRE(b->my_keys.public_key[0] == 0 && b->my_keys.public_key[1] != 0); |
|
|
|
chat_core_create_channel_auto(b, "TestCh"); |
|
REQUIRE(b->topo_groups->group_list->head); |
|
struct TOPO_GROUP* group_b = (struct TOPO_GROUP*)b->topo_groups->group_list->head; |
|
REQUIRE(group_b->group_type == TOPO_GROUP_TYPE_CHAT); |
|
uint64_t gid = group_b->group_id; |
|
REQUIRE(!topo_groups_find(a->topo_groups, gid)); |
|
|
|
struct sockaddr_in bound = {0}; socklen_t bound_len = sizeof(bound); |
|
REQUIRE(b->etcp_sockets && !b->etcp_sockets->is_tcp); |
|
REQUIRE(getsockname(b->etcp_sockets->fd, (struct sockaddr*)&bound, &bound_len) == 0); |
|
REQUIRE(bound.sin_family == AF_INET && bound.sin_port != 0); |
|
struct TOPO_ADDR4 address = { .addr = {127, 0, 0, 1}, .protocol = TOPO_PROTO_UDP, .type = TOPO_ADDR_INTERFACE }; |
|
address.port = ntohs(bound.sin_port); |
|
struct TOPO_NODE node = { .node_id = b->node_id, .v4_addrs = &address }; |
|
/* Пустой ключ отклоняется до создания группы и без callback. */ |
|
REQUIRE(topo_group_invite_join(a, gid, &node, b->node_id, invite_done, &reply) == -1); |
|
REQUIRE(!reply.calls && !topo_groups_find(a->topo_groups, gid)); |
|
memcpy(node.public_key, b->my_keys.public_key, sizeof(node.public_key)); |
|
|
|
/* Создание ссылки тоже не должно отбрасывать допустимый нулевой префикс ключа. */ |
|
struct InviteData invite = { .channelId = gid, .join_key = 123, .addrCount = 1 }, decoded; |
|
invite.addrs[0].family = 4; invite.addrs[0].proto = INVITE_PROTO_UDP; invite.addrs[0].port = address.port; |
|
memcpy(invite.addrs[0].address, address.addr, 4); |
|
char link[1024], error[128]; |
|
REQUIRE(invite_link_encode(&invite, NULL, link, sizeof(link)) == -1); |
|
memcpy(invite.pubkey, node.public_key, sizeof(invite.pubkey)); |
|
REQUIRE(invite_link_encode(&invite, "test-password", link, sizeof(link)) > 0); |
|
REQUIRE(invite_link_decode(link, strlen(link), &decoded, error, sizeof(error)) == 0); |
|
REQUIRE(decoded.nodeId == b->node_id && decoded.channelId == gid && decoded.join_key == invite.join_key); |
|
REQUIRE(!memcmp(decoded.pubkey, node.public_key, sizeof(decoded.pubkey))); |
|
REQUIRE(decoded.addrCount == 1 && decoded.addrs[0].port == address.port && decoded.addrs[0].proto == INVITE_PROTO_UDP); |
|
REQUIRE(!memcmp(decoded.addrs[0].address, address.addr, 4) && !strcmp(decoded.password, "test-password")); |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "test invite start: peer=%016llx key_prefix=%02x%02x port=%u", |
|
(unsigned long long)b->node_id, node.public_key[0], node.public_key[1], address.port); |
|
REQUIRE(topo_group_invite_join(a, gid, &node, b->node_id, invite_done, &reply) == 0); |
|
uint64_t deadline = get_time_tb() + 50000; |
|
while (!reply.calls && get_time_tb() < deadline) uasync_poll(ua, 20); |
|
REQUIRE(reply.calls == 1 && reply.event == TGI_EVENT_JOIN); |
|
REQUIRE(reply.node_id == b->node_id && reply.group_id == gid); |
|
|
|
struct TOPO_GROUP* group_a = topo_groups_find(a->topo_groups, gid); |
|
REQUIRE(group_a && group_a->group_type == TOPO_GROUP_TYPE_CHAT); |
|
char name[128]; uint64_t owner = 0; |
|
uint8_t x25519[32], ed25519[32], signature[64]; |
|
REQUIRE(topo_node_sqlite_channel_get(a->topo_sqlite_db, group_a->channel_id, name, sizeof(name), |
|
&owner, x25519, ed25519, signature) == 0); |
|
REQUIRE(strcmp(name, "TestCh") == 0 && owner == b->node_id); |
|
uint8_t expected_x[32], expected_ed[32], expected_signature[64]; |
|
REQUIRE(topo_node_sqlite_channel_get(b->topo_sqlite_db, group_b->channel_id, name, sizeof(name), |
|
&owner, expected_x, expected_ed, expected_signature) == 0); |
|
REQUIRE(!memcmp(x25519, expected_x, 32) && !memcmp(ed25519, expected_ed, 32)); |
|
REQUIRE(!memcmp(signature, expected_signature, 64)); |
|
/* Получение описания не заменяет протокол добавления: B ещё не дал членство A. */ |
|
REQUIRE(!topo_node_sqlite_member_in_channel(b->topo_sqlite_db, group_b->channel_id, a->node_id)); |
|
REQUIRE(!topo_group_peer_ready(group_b, a->node_id)); |
|
result = 0; |
|
cleanup: |
|
if (a) utun_instance_destroy(a); |
|
if (b) utun_instance_destroy(b); |
|
/* Дать event loop освободить отменённые таймеры перед проверкой ресурсов. */ |
|
if (ua) uasync_poll(ua, 0); |
|
if (ua) uasync_destroy(ua, 0); |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "test_invite_group_create: %s", result ? "FAIL" : "PASS"); |
|
return result; |
|
}
|
|
|