#include #include #include #include "utun_instance.h" #include "routing_layer/topo_group.h" #include "routing_layer/topo_node_sqlite.h" #include "transport_layer/secure_channel.h" #include "transport_layer/etcp.h" #include "config_parser.h" #include "../lib/mem.h" #include "../lib/memory_pool.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" static void sign_record(struct UTUN_INSTANCE* inst, struct TOPO_NODE* ni) { uint8_t msg[TOPO_SIG_MSG_MAX_SIZE]; int len = topo_node_build_sig_msg(ni, msg, sizeof(msg)); assert(len > 0); assert(sc_ed25519_sign(inst->my_ed25519_privkey, msg, (size_t)len, ni->x25519_self_sig) == SC_OK); } static struct TOPO_NODE* record(struct UTUN_INSTANCE* inst, uint64_t timestamp, const char* name, int address) { struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni)); assert(ni); ni->node_id = inst->node_id; ni->timestamp = timestamp; ni->node_name = u_strdup(name); memcpy(ni->public_key, inst->my_keys.public_key, 32); memcpy(ni->ed25519_public_key, inst->my_ed25519_pubkey, 32); if (address) { ni->v4_addrs = memory_pool_alloc(inst->topo_groups->v4_addr_pool); assert(ni->v4_addrs); *ni->v4_addrs = (struct TOPO_ADDR4){ .addr = {127,0,0,1}, .port = 9000, .protocol = TOPO_PROTO_UDP }; } sign_record(inst, ni); return ni; } /* Старая БД запрещала port=0; перестройка CHECK сохраняет адреса и восстанавливает индексы. */ static void test_zero_port_schema(void) { sqlite3 *db = NULL; assert(sqlite3_open(":memory:", &db) == SQLITE_OK); assert(sqlite3_exec(db, "CREATE TABLE node_addresses(" "id INTEGER PRIMARY KEY,node_id INTEGER,family INTEGER,protocol INTEGER,address BLOB," "port INTEGER CHECK(port > 0 AND port <= 65535),rtt INTEGER,addr_type INTEGER,config_type INTEGER," "nat_mapping INTEGER,nat_inbound INTEGER,nat_source INTEGER,address_kind INTEGER,socket_id INTEGER," "options BLOB,created_at INTEGER);" "CREATE UNIQUE INDEX idx_na_unique ON node_addresses(node_id,family,socket_id,protocol,address,port,address_kind);" "INSERT INTO node_addresses VALUES(7,1,4,2,X'CB00712C',443,15,4,2,0,0,1,1,3,X'0102',123)", NULL, NULL, NULL) == SQLITE_OK); assert(!topo_node_sqlite_init(db)); sqlite3_stmt *statement = NULL; assert(sqlite3_prepare_v2(db, "SELECT port,rtt,nat_source,length(options),created_at FROM node_addresses WHERE id=7", -1, &statement, NULL) == SQLITE_OK); assert(sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_int(statement, 0) == 443); assert(sqlite3_column_int(statement, 1) == 15 && sqlite3_column_int(statement, 2) == ADDRESS_SOURCE_ETCP); assert(sqlite3_column_int(statement, 3) == 2 && sqlite3_column_int(statement, 4) == 123); sqlite3_finalize(statement); assert(sqlite3_exec(db, "UPDATE node_addresses SET port=0 WHERE id=7", NULL, NULL, NULL) == SQLITE_OK); assert(sqlite3_exec(db, "INSERT INTO node_addresses SELECT 8,node_id,family,protocol,address,port,rtt," "addr_type,config_type,nat_mapping,nat_inbound,nat_source,address_kind,socket_id,options,created_at" " FROM node_addresses WHERE id=7", NULL, NULL, NULL) == SQLITE_CONSTRAINT); assert(!topo_node_sqlite_init(db)); sqlite3_close(db); } /* Отказ INSERT/UPDATE не должен выглядеть успешным сохранением уже проверенного узла. */ static int interrupt_sql(void* arg) { (void)arg; return 1; } static void test_verified_write(void) { sqlite3* db = NULL; uint8_t key[32] = {1}; assert(sqlite3_open(":memory:", &db) == SQLITE_OK && !topo_node_sqlite_init(db)); assert(!topo_node_sqlite_node_update_verified(db, 7, "before", key, key, 100, 1)); assert(sqlite3_exec(db, "CREATE TRIGGER reject_node BEFORE INSERT ON nodes " "BEGIN SELECT RAISE(ABORT,'test insert rejection'); END", NULL, NULL, NULL) == SQLITE_OK); assert(topo_node_sqlite_node_update_verified(db, 8, "rejected", key, key, 101, 1) < 0); assert(sqlite3_exec(db, "DROP TRIGGER reject_node; CREATE TRIGGER reject_node BEFORE UPDATE ON nodes " "BEGIN SELECT RAISE(ABORT,'test update rejection'); END", NULL, NULL, NULL) == SQLITE_OK); assert(topo_node_sqlite_node_update_verified(db, 7, "rejected", key, key, 101, 1) < 0); assert(topo_node_sqlite_node_set_online(db, 7, 0) < 0 && topo_node_sqlite_node_get_online(db, 7) == 1); sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(db, "SELECT name,update_ts FROM nodes ORDER BY node_id", -1, &st, NULL) == SQLITE_OK); assert(sqlite3_step(st) == SQLITE_ROW && !strcmp((const char*)sqlite3_column_text(st, 0), "before")); assert(sqlite3_column_int64(st, 1) == 100 && sqlite3_step(st) == SQLITE_DONE); sqlite3_finalize(st); assert(sqlite3_exec(db, "DROP TRIGGER reject_node", NULL, NULL, NULL) == SQLITE_OK); assert(!topo_node_sqlite_node_update_verified(db, 7, "after", key, key, 101, 1)); assert(sqlite3_prepare_v2(db, "SELECT name,update_ts FROM nodes WHERE node_id=7", -1, &st, NULL) == SQLITE_OK); assert(sqlite3_step(st) == SQLITE_ROW && !strcmp((const char*)sqlite3_column_text(st, 0), "after")); assert(sqlite3_column_int64(st, 1) == 101); sqlite3_finalize(st); uint8_t signature[64] = {0}; assert(!topo_node_sqlite_channel_put(db, "123", "test", 7, key, NULL, key, NULL, signature)); assert(!topo_node_sqlite_member_placeholder_put(db, "123", 7, key, key)); assert(!topo_node_sqlite_channel_put(db, "124", "test", 7, key, NULL, key, NULL, signature)); assert(!topo_node_sqlite_member_placeholder_put(db, "124", 7, key, key)); assert(!topo_node_sqlite_set_connected(db, "123", 7, 1)); uint64_t* ids = NULL; int count = 0; assert(!topo_node_sqlite_get_connected_peers(db, "123", &ids, &count) && count == 1 && ids[0] == 7); u_free(ids); for (int query = 0; query < 4; query++) { ids = (void*)(uintptr_t)1; count = 99; sqlite3_progress_handler(db, 1, interrupt_sql, NULL); int rc = query == 0 ? topo_node_sqlite_get_connected_peers(db, "123", &ids, &count) : query == 1 ? topo_node_sqlite_get_supernode_peers(db, "123", &ids, &count) : query == 2 ? topo_node_sqlite_get_public_peers(db, "123", &ids, &count, 0) : topo_node_sqlite_get_local_peers(db, "123", &ids, &count, 0); sqlite3_progress_handler(db, 0, NULL, NULL); assert(rc < 0 && !ids && !count && !sqlite3_next_stmt(db, NULL)); } assert(sqlite3_exec(db, "UPDATE peers_123 SET node_type=2; UPDATE peers_124 SET node_type=4;" "CREATE TRIGGER reject_classification BEFORE UPDATE ON peers_124 " "BEGIN SELECT RAISE(ABORT,'test classification rejection'); END", NULL, NULL, NULL) == SQLITE_OK); assert(topo_node_sqlite_nodeinfo_updated(db, 7) < 0); assert(topo_node_sqlite_get_node_type(db, "123", 7) == 2 && topo_node_sqlite_get_node_type(db, "124", 7) == 4); assert(sqlite3_exec(db, "DROP TRIGGER reject_classification; CREATE TRIGGER reject_channel BEFORE DELETE ON channels " "BEGIN SELECT RAISE(ABORT,'test channel deletion rejection'); END", NULL, NULL, NULL) == SQLITE_OK); assert(topo_node_sqlite_channel_delete(db, "124") < 0 && topo_node_sqlite_member_in_channel(db, "124", 7)); assert(!topo_node_sqlite_channel_get(db, "124", NULL, 0, NULL, NULL, NULL, NULL)); assert(sqlite3_exec(db, "DROP TRIGGER reject_channel", NULL, NULL, NULL) == SQLITE_OK); assert(!topo_node_sqlite_nodeinfo_updated(db, 7) && topo_node_sqlite_get_node_type(db, "123", 7) == 0); assert(!topo_node_sqlite_channel_delete(db, "124")); assert(topo_node_sqlite_channel_get(db, "124", NULL, 0, NULL, NULL, NULL, NULL) < 0); assert(sqlite3_exec(db, "CREATE TRIGGER reject_member BEFORE DELETE ON peers_123 " "BEGIN SELECT RAISE(ABORT,'test member deletion rejection'); END", NULL, NULL, NULL) == SQLITE_OK); assert(topo_node_sqlite_member_del(db, "123", 7) < 0 && topo_node_sqlite_member_in_channel(db, "123", 7)); assert(sqlite3_exec(db, "DROP TRIGGER reject_member; UPDATE peers_123 SET join_sig=X'01'; " "UPDATE channels SET x25519_pubkey=X'01'", NULL, NULL, NULL) == SQLITE_OK); uint64_t timestamp = 77; memset(signature, 0xa5, sizeof(signature)); assert(topo_node_sqlite_member_get_join(db, "123", 7, signature, ×tamp) < 0); assert(timestamp == 77 && signature[0] == 0xa5); memset(key, 0xa5, sizeof(key)); assert(topo_node_sqlite_channel_get(db, "123", NULL, 0, NULL, key, NULL, NULL) < 0 && key[0] == 0xa5); assert(!topo_node_sqlite_member_del(db, "123", 7) && !topo_node_sqlite_member_in_channel(db, "123", 7)); assert(!topo_node_sqlite_node_set_online(db, 7, 0) && !topo_node_sqlite_node_get_online(db, 7)); assert(sqlite3_exec(db, "DROP TABLE nodes", NULL, NULL, NULL) == SQLITE_OK); assert(topo_node_sqlite_node_update_verified(db, 7, "no table", key, key, 102, 1) < 0); sqlite3_close(db); DEBUG_INFO(DEBUG_CATEGORY_BGP, "[PASS] verified node writes: insert/update, rejected writes, prepare failure and stored state"); } /* Новый wire и SQL сохраняют адреса, протоколы, классификацию и REALITY; смена формата оставляет bootstrap. */ static void test_compact_addresses(struct UTUN_INSTANCE *inst) { test_zero_port_schema(); sqlite3 *saved_db = inst->topo_sqlite_db; assert(sqlite3_open(":memory:", &inst->topo_sqlite_db) == SQLITE_OK); sqlite3 *db = inst->topo_sqlite_db; assert(!topo_node_sqlite_init(db)); assert(!topo_node_sqlite_node_update_verified(db, inst->node_id, "address fixture", inst->my_keys.public_key, inst->my_ed25519_pubkey, 100, 100)); struct TOPO_NODE *node = record(inst, INT64_MAX - 100, "address fixture", 1); memcpy(node->v4_addrs->addr, (uint8_t[]){194,63,142,140}, 4); node->v4_addrs->port = 1333; node->v4_addrs->socket_id = 5; node->v4_sock_meta = memory_pool_alloc(inst->topo_groups->v4_sock_meta_pool); node->v6_sock_meta = memory_pool_alloc(inst->topo_groups->v6_sock_meta_pool); node->v6_addrs = memory_pool_alloc(inst->topo_groups->v6_addr_pool); node->reality_socks = u_calloc(1, sizeof(*node->reality_socks)); assert(node->v4_sock_meta && node->v6_sock_meta && node->v6_addrs && node->reality_socks); *node->v4_sock_meta = (struct TOPO_SOCKMETA4){.id = 5, .config_type = CFG_SERVER_TYPE_PUBLIC, .address_profile = {ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN, ADDRESS_SOURCE_ETCP}}; *node->v6_sock_meta = (struct TOPO_SOCKMETA6){.id = 6, .config_type = CFG_SERVER_TYPE_PUBLIC, .address_profile = {ADDRESS_MAP_UNCHANGED, ADDRESS_IN_CONTACT, ADDRESS_SOURCE_ETCP}}; *node->v6_addrs = (struct TOPO_ADDR6){.addr = {0x2a,0x04,0x52,0,0,0,0,0,0,0,0,0,0,0,0,0xf}, .port = 443, .socket_id = 6, .protocol = TOPO_PROTO_TCP | TOPO_PROTO_REALITY}; node->reality_socks->socket_id = 6; strcpy(node->reality_socks->server_name, "example.org"); sign_record(inst, node); assert(sizeof(struct TOPOMSG_SOCKMETA4) == 5 && sizeof(struct TOPOMSG_SOCKMETA6) == 5); assert(!topo_node_sqlite_snapshot_put(db, node) && !topo_node_sqlite_addrs_put(db, inst->node_id, node)); struct TOPO_NODE *loaded = topo_node_sqlite_snapshot_load(db, inst->topo_groups, inst->node_id); assert(loaded && !topo_node_verify(loaded)); assert(!memcmp(loaded->v4_addrs->addr, node->v4_addrs->addr, 4) && loaded->v4_addrs->port == 1333); assert(loaded->v4_sock_meta->address_profile.inbound == ADDRESS_IN_OPEN); assert(!memcmp(loaded->v6_addrs->addr, node->v6_addrs->addr, 16) && loaded->v6_addrs->port == 443); assert(loaded->v6_addrs->protocol == (TOPO_PROTO_TCP | TOPO_PROTO_REALITY)); assert(loaded->v6_sock_meta->address_profile.inbound == ADDRESS_IN_CONTACT); assert(!strcmp(loaded->reality_socks->server_name, "example.org")); topo_node_destroy(inst->topo_groups, loaded); sqlite3_stmt *statement = NULL; assert(sqlite3_prepare_v2(db, "SELECT family,protocol,port,socket_id,addr_type,length(options)" " FROM node_addresses ORDER BY family", -1, &statement, NULL) == SQLITE_OK); assert(sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_int(statement, 0) == 4); assert(sqlite3_column_int(statement, 1) == TOPO_PROTO_UDP && sqlite3_column_int(statement, 2) == 1333); assert(sqlite3_column_int(statement, 3) == 5 && sqlite3_column_int(statement, 4) == ADDR_TYPE_DIRECT); assert(sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_int(statement, 0) == 6); assert(sqlite3_column_int(statement, 1) == (TOPO_PROTO_TCP | TOPO_PROTO_REALITY)); assert(sqlite3_column_int(statement, 2) == 443 && sqlite3_column_int(statement, 3) == 6); assert(sqlite3_column_int(statement, 4) == ADDR_TYPE_CONTACT && sqlite3_column_int(statement, 5) == TOPO_ADDR_REALITY_OPTS_SIZE); assert(sqlite3_step(statement) == SQLITE_DONE); sqlite3_finalize(statement); /* Публичный IP интерфейса не становится NAT UNKNOWN из-за отсутствия измерения входящих. */ struct address_profile known = node->v4_sock_meta->address_profile; node->v4_sock_meta->address_profile = (struct address_profile){0}; assert(!topo_node_sqlite_addrs_put(db, inst->node_id, node)); assert(sqlite3_prepare_v2(db, "SELECT addr_type FROM node_addresses WHERE family=4", -1, &statement, NULL) == SQLITE_OK); assert(sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_int(statement, 0) == ADDR_TYPE_NETIF); sqlite3_finalize(statement); node->v4_sock_meta->address_profile = known; assert(!topo_node_sqlite_addrs_put(db, inst->node_id, node)); /* Две старые колонки отмечают прежний формат. Миграция не теряет IP/port, REALITY и максимум timestamp. */ assert(sqlite3_exec(db, "ALTER TABLE node_addresses ADD COLUMN nat_evidence INTEGER DEFAULT 1;" "ALTER TABLE node_addresses ADD COLUMN nat_checked INTEGER DEFAULT 1", NULL, NULL, NULL) == SQLITE_OK); assert(!topo_node_sqlite_init(db)); assert(topo_node_sqlite_snapshot_timestamp(db, inst->node_id) == node->timestamp); assert(!topo_node_sqlite_snapshot_load(db, inst->topo_groups, inst->node_id)); loaded = topo_node_sqlite_node_load(db, inst->topo_groups, inst->node_id); assert(loaded && !loaded->timestamp && loaded->v4_addrs && loaded->v6_addrs && loaded->reality_socks); assert(!loaded->v4_sock_meta->address_profile.source && !loaded->v6_sock_meta->address_profile.source); assert(loaded->v4_addrs->port == 1333 && loaded->v6_addrs->port == 443); assert(!strcmp(loaded->reality_socks->server_name, "example.org")); topo_node_destroy(inst->topo_groups, loaded); node->timestamp = 0; assert(!topo_node_sign_self(inst, node) && node->timestamp == INT64_MAX - 99); assert(!topo_node_sqlite_init(db)); loaded = topo_node_sqlite_snapshot_load(db, inst->topo_groups, inst->node_id); assert(loaded && loaded->timestamp == node->timestamp && !topo_node_verify(loaded)); topo_node_destroy(inst->topo_groups, loaded); topo_node_destroy(inst->topo_groups, node); sqlite3_close(db); inst->topo_sqlite_db = saved_db; } /* Каждый размер буфера: длина, NUL, защитные байты, сохранённый префикс и маркер усечения. */ static void test_node_format(struct TOPO_GROUP* group) { char full[8192]; int full_len = topo_node_format_all(group, full, sizeof(full)); assert(full_len > 0 && (size_t)full_len == strlen(full) && !strstr(full, "[TRUNCATED]")); assert(strstr(full, "=== BGP Nodes dump:")); if (group->nodes->head) assert(strstr(full, " v4addr[0]:") && strstr(full, " path[0]:")); const char marker[] = "\n[TRUNCATED]\n"; debug_set_category_level(DEBUG_CATEGORY_BGP, DEBUG_LEVEL_ERROR); for (size_t capacity = 0; capacity <= (size_t)full_len + 2; capacity++) { uint8_t* storage = u_malloc(capacity + 2); assert(storage); memset(storage, 0xa5, capacity + 2); char* buf = (char*)storage + 1; int length = topo_node_format_all(group, buf, capacity); assert(storage[0] == 0xa5 && storage[capacity + 1] == 0xa5); if (!capacity) { assert(!length); u_free(storage); continue; } size_t expected = (size_t)full_len < capacity ? (size_t)full_len : capacity - 1; assert(length >= 0 && (size_t)length == expected); assert(memchr(buf, '\0', capacity) == buf + length); if (capacity > (size_t)full_len) assert(!memcmp(buf, full, full_len + 1)); else { size_t marker_len = expected < sizeof(marker) - 1 ? expected : sizeof(marker) - 1; assert(!memcmp(buf, full, expected - marker_len)); assert(!memcmp(buf + expected - marker_len, marker, marker_len)); } u_free(storage); } char empty[] = "old"; assert(!topo_node_format_all(NULL, empty, sizeof(empty)) && !empty[0]); assert(!topo_node_format_all(group, NULL, sizeof(empty))); debug_set_category_level(DEBUG_CATEGORY_BGP, DEBUG_LEVEL_DEBUG); DEBUG_INFO(DEBUG_CATEGORY_BGP, "[PASS] node dump: nodes=%d capacities=0..%d, lengths, guards, NUL and truncation", queue_entry_count(group->nodes), full_len + 2); } static void test_bgp(struct UTUN_INSTANCE* signer) { struct UTUN_INSTANCE receiver = { .ua = signer->ua, .topo_groups = signer->topo_groups }; struct TOPO_GROUP group = { .instance = &receiver, .group_id = 10, .group_type = TOPO_GROUP_TYPE_CHAT }; struct ETCP_CONN from = { .instance = &receiver, .peer_node_id = 7 }; group.nodes = queue_new(receiver.ua, 16, 0, 8, "snapshot_bgp"); test_node_format(&group); struct TOPO_NODE* ni = record(signer, INT64_MAX - 7, "BGP", 1); from.peer_node_id = ni->node_id; from.links_up = 1; struct TOPO_GROUP_NODE nq = {0}; uint8_t packet[4096] = {0}; int len = topo_node_serialize(ni, &nq, group.group_id, 0, packet + sizeof(struct TOPOMSG_HEADER), sizeof(packet) - sizeof(struct TOPOMSG_HEADER), 0); assert(len > 0); assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) == 0); struct TOPO_GROUP_NODE* peer = topo_node_find_by_id(&group, ni->node_id); assert(peer && peer->last_timestamp == ni->timestamp && queue_entry_count(peer->paths) == 1); assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) == 0); assert(queue_entry_count(peer->paths) == 1); test_node_format(&group); struct TOPOMSG_NODEINFO_PKT* wire = (struct TOPOMSG_NODEINFO_PKT*)packet; wire->node.local_v4_subnets = 1; /* malformed CHAT не меняет уже принятую запись и маршрут */ assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) < 0); assert(topo_node_find_by_id(&group, ni->node_id) == peer && queue_entry_count(peer->paths) == 1); assert(peer->last_timestamp == ni->timestamp && topo_node_registry_find(signer->topo_groups, ni->node_id)->timestamp == ni->timestamp); wire->node.local_v4_subnets = 0; wire->node.timestamp++; /* подделка не удаляет узел и ранее принятый маршрут */ assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) < 0); assert(topo_node_find_by_id(&group, ni->node_id) == peer && queue_entry_count(peer->paths) == 1); wire->node.timestamp--; ni->timestamp--; sign_record(signer, ni); len = topo_node_serialize(ni, &nq, group.group_id, 0, packet + sizeof(struct TOPOMSG_HEADER), sizeof(packet) - sizeof(struct TOPOMSG_HEADER), 0); assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) == 0); assert(peer->last_timestamp == INT64_MAX - 7 && queue_entry_count(peer->paths) == 1); topo_group_remove_path(peer, &from); topo_nodeq_remove_node(&group, peer); topo_node_destroy(signer->topo_groups, ni); queue_free(group.nodes); } int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG); struct UTUN_INSTANCE inst = {0}; struct TOPO_GROUPS groups = { .instance = &inst }; inst.topo_groups = &groups; inst.ua = uasync_create(); assert(inst.ua); groups.node_registry = queue_new(inst.ua, 16, 0, 8, "snapshot_test"); groups.v4_addr_pool = memory_pool_init(sizeof(struct TOPO_ADDR4), "snapshot_v4"); groups.v4_sock_meta_pool = memory_pool_init(sizeof(struct TOPO_SOCKMETA4), "snapshot_meta4"); groups.v6_sock_meta_pool = memory_pool_init(sizeof(struct TOPO_SOCKMETA6), "snapshot_meta6"); groups.v6_addr_pool = memory_pool_init(sizeof(struct TOPO_ADDR6), "snapshot_v6"); assert(groups.v4_addr_pool && groups.v4_sock_meta_pool && groups.v6_sock_meta_pool && groups.v6_addr_pool); assert(sc_generate_keypair(&inst.my_keys) == SC_OK); memcpy(inst.my_ed25519_privkey, inst.my_keys.private_key, 32); EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, inst.my_ed25519_privkey, 32); size_t key_len = 32; assert(key && EVP_PKEY_get_raw_public_key(key, inst.my_ed25519_pubkey, &key_len) == 1); EVP_PKEY_free(key); inst.node_id = sc_derive_node_id_from_pubkey(inst.my_keys.public_key); assert(sqlite3_open(":memory:", &inst.topo_sqlite_db) == SQLITE_OK); assert(topo_node_sqlite_init(inst.topo_sqlite_db) == 0); test_verified_write(); test_compact_addresses(&inst); struct TOPO_NODE* first = record(&inst, 100, "old", 1); assert(topo_node_verify(first) == 0); first->timestamp++; assert(topo_node_verify(first) < 0); /* timestamp защищён подписью */ first->timestamp--; assert(topo_node_registry_store(&groups, first) == first); struct TOPO_NODE* stale = record(&inst, 99, "stale", 0); assert(topo_node_registry_store(&groups, stale) == first); topo_node_registry_unref(&groups, inst.node_id); assert(first->timestamp == 100 && first->v4_addrs && !strcmp(first->node_name, "old")); struct TOPO_NODE* conflict = record(&inst, 100, "conflict", 0); assert(topo_node_registry_store(&groups, conflict) == first); topo_node_registry_unref(&groups, inst.node_id); assert(first->v4_addrs && !strcmp(first->node_name, "old")); assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0); struct TOPO_NODE* loaded = topo_node_sqlite_snapshot_load(inst.topo_sqlite_db, &groups, inst.node_id); assert(loaded && loaded->timestamp == 100 && loaded->v4_addrs && topo_node_verify(loaded) == 0); topo_node_destroy(&groups, loaded); struct TOPO_NODE* fresh = record(&inst, 101, "new", 0); assert(topo_node_registry_store(&groups, fresh) == first); topo_node_registry_unref(&groups, inst.node_id); assert(first->timestamp == 101 && !first->v4_addrs && !strcmp(first->node_name, "new")); assert(topo_node_verify(first) == 0); /* удаление адресов не оставляет частей старой записи */ assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0); loaded = record(&inst, 100, "old", 1); assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, loaded) == 0); topo_node_destroy(&groups, loaded); loaded = topo_node_sqlite_snapshot_load(inst.topo_sqlite_db, &groups, inst.node_id); assert(loaded && loaded->timestamp == 101 && !loaded->v4_addrs && topo_node_verify(loaded) == 0); topo_node_destroy(&groups, loaded); /* Сохранённая версия из будущего: локальные часы/перезапуск не откатывают timestamp. */ topo_node_registry_unref(&groups, inst.node_id); first = topo_node_registry_store(&groups, record(&inst, 99, "stale after restart", 1)); assert(first && first->timestamp == 101 && !first->v4_addrs && topo_node_verify(first) == 0); first->timestamp = INT64_MAX - 10; sign_record(&inst, first); assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0); first->timestamp = 0; assert(topo_node_sign_self(&inst, first) == 0); assert(first->timestamp == INT64_MAX - 9 && topo_node_verify(first) == 0); assert(topo_node_sign_self(&inst, first) == 0); assert(first->timestamp == INT64_MAX - 8 && topo_node_verify(first) == 0); test_bgp(&inst); uint64_t before = first->timestamp; assert(sqlite3_exec(inst.topo_sqlite_db, "CREATE TRIGGER fail_snapshot BEFORE UPDATE ON node_snapshots BEGIN SELECT RAISE(ABORT,'test write failure'); END", NULL, NULL, NULL) == SQLITE_OK); assert(topo_node_sign_self(&inst, first) < 0); assert(first->timestamp == before && topo_node_verify(first) == 0); topo_node_registry_unref(&groups, inst.node_id); assert(!groups.node_registry->head); queue_free(groups.node_registry); memory_pool_destroy(groups.v4_addr_pool); memory_pool_destroy(groups.v4_sock_meta_pool); memory_pool_destroy(groups.v6_sock_meta_pool); memory_pool_destroy(groups.v6_addr_pool); sqlite3_close(inst.topo_sqlite_db); uasync_destroy(inst.ua, 0); return 0; }