#include #include #include #include #include "utun_instance.h" #include "routing_layer/topo_group.h" #include "routing_layer/topo_node_sqlite.h" #include "chat/chat_devgroup.h" #include "../lib/u_async.h" #include "../lib/mem.h" 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, "devgroup_nodes"); groups.group_list = queue_new(inst.ua, 0, 0, 0, "devgroup_groups"); assert(groups.node_registry && groups.group_list); assert(sc_generate_keypair(&inst.my_keys) == SC_OK); uint8_t xpub[32]; memcpy(inst.my_ed25519_privkey, inst.my_keys.private_key, 32); assert(!devgroup_public_keys(inst.my_ed25519_privkey, inst.my_keys.private_key, inst.my_ed25519_pubkey, xpub)); inst.node_id = sc_derive_node_id_from_pubkey(inst.my_keys.public_key); strcpy(inst.name, "device"); assert(sqlite3_open(":memory:", &inst.topo_sqlite_db) == SQLITE_OK); assert(!topo_node_sqlite_init(inst.topo_sqlite_db)); assert(!topo_node_init_self(&inst)); assert(!chat_devgroup_rename(&inst, "Мои устройства")); struct devgroup_record r, loaded; uint8_t ep[32], xp[32]; struct devgroup_profile p; assert(devgroup_load_local(&inst, &r, ep, xp) == 1); assert(memcmp(ep, xp, 32) && !devgroup_profile_read(&r, &p) && p.version == 1); struct TOPO_NODE* node = topo_node_registry_find(&groups, inst.node_id); uint64_t before = node->timestamp; assert(!chat_devgroup_rename(&inst, "Мои устройства") && node->timestamp == before); assert(!devgroup_verify(&r, inst.node_id, inst.my_keys.public_key, inst.my_ed25519_pubkey)); assert(devgroup_verify(&r, inst.node_id + 1, inst.my_keys.public_key, inst.my_ed25519_pubkey) < 0); r.json[10] ^= 1; assert(devgroup_profile_read(&r, &p) < 0); r.json[10] ^= 1; struct devgroup_record conflict = r; struct devgroup_profile original, changed; assert(!devgroup_profile_read(&r, &original)); assert(!devgroup_sign(&conflict, ep, "Concurrent name", original.version, original.timestamp, inst.node_id, inst.my_keys.public_key, inst.my_ed25519_pubkey)); assert(!devgroup_profile_read(&conflict, &changed)); assert((devgroup_profile_compare(&original, &changed) > 0) == (memcmp(original.hash, changed.hash, 32) > 0)); changed.version++; assert(devgroup_profile_compare(&changed, &original) > 0); changed.version--; changed.timestamp++; assert(devgroup_profile_compare(&changed, &original) > 0); assert(devgroup_decode(&conflict, (const uint8_t*)"", 192) < 0); uint8_t wire[TOPO_NODE_WIRE_MAX_SIZE]; struct TOPO_GROUP_NODE nq = {0}; int bytes = topo_node_serialize(node, &nq, 0, 0, wire, sizeof(wire), 0); assert(bytes > 0); struct TOPO_GROUP group = {.instance = &inst}; struct TOPO_NODE* parsed = NULL; assert(!topo_node_deserialize(&group, wire, bytes, &parsed, NULL, NULL, NULL, NULL)); assert(!topo_node_verify(parsed) && !memcmp(&parsed->devgroup, &r, sizeof(r))); topo_node_destroy(&groups, parsed); assert(devgroup_latest(inst.topo_sqlite_db, r.ed, &loaded, &p) == 1 && p.version == 1); assert(sqlite3_exec(inst.topo_sqlite_db, "CREATE TRIGGER deny_devgroup BEFORE UPDATE ON nodes BEGIN SELECT RAISE(ABORT,'test'); END", NULL, NULL, NULL) == SQLITE_OK); assert(chat_devgroup_rename(&inst, "Rejected") < 0 && node->timestamp == before); assert(devgroup_load_local(&inst, &loaded, ep, xp) == 1 && !strcmp(loaded.json, r.json)); assert(sqlite3_exec(inst.topo_sqlite_db, "DROP TRIGGER deny_devgroup", NULL, NULL, NULL) == SQLITE_OK); assert(!chat_devgroup_rename(&inst, "New name")); assert(devgroup_load_local(&inst, &loaded, ep, xp) == 1 && !devgroup_profile_read(&loaded, &p) && p.version == 2); /* Новый runtime восстанавливает обе пары и принадлежность до первой публикации. */ topo_node_registry_unref(&groups, inst.node_id); assert(!topo_node_init_self(&inst)); node = topo_node_registry_find(&groups, inst.node_id); assert(!strcmp(node->devgroup.json, loaded.json)); uint8_t receipt[16] = {1}; assert(!chat_devgroup_import(&inst, &loaded, ep, xp, receipt, 42)); assert(chat_devgroup_receipt(&inst, receipt, 42) == 1); assert(!chat_devgroup_leave(&inst) && !node->devgroup.json[0]); assert(!chat_devgroup_import(&inst, &loaded, ep, xp, receipt, 42) && !node->devgroup.json[0]); assert(devgroup_latest(inst.topo_sqlite_db, r.ed, &loaded, &p) == 0); assert(!chat_devgroup_leave(&inst)); topo_node_registry_unref(&groups, inst.node_id); assert(!topo_node_init_self(&inst)); node = topo_node_registry_find(&groups, inst.node_id); assert(!node->devgroup.json[0]); topo_node_registry_unref(&groups, inst.node_id); queue_free(groups.node_registry); queue_free(groups.group_list); assert(sqlite3_close(inst.topo_sqlite_db) == SQLITE_OK); uasync_destroy(inst.ua, 0); puts("PASS devgroup: signatures, wire, transactional projection, restart, rename, leave and late import replay"); return 0; }