/* Реальные core/SQLite/локальная регистрация invite;проверяем отказы каждого SQL-этапа чтения. */ #define _GNU_SOURCE #include "../src/chat/invite_build.c" #include enum fault { NONE, PREPARE, BIND, STEP, FINALIZE }; static enum fault fault; static const char* target; static unsigned skip_steps, calls; static int last_result; static uint64_t expected_channel; static struct UTUN_INSTANCE* active; static int matches(sqlite3_stmt* st) { return st && target && !strncmp(sqlite3_sql(st), target, strlen(target)); } extern int __real_sqlite3_prepare_v2(sqlite3*, const char*, int, sqlite3_stmt**, const char**); int __wrap_sqlite3_prepare_v2(sqlite3* db, const char* sql, int bytes, sqlite3_stmt** st, const char** tail) { if (fault == PREPARE && target && !strncmp(sql, target, strlen(target))) { fault = NONE; *st = NULL; return SQLITE_IOERR; } return __real_sqlite3_prepare_v2(db, sql, bytes, st, tail); } extern int __real_sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); int __wrap_sqlite3_bind_int64(sqlite3_stmt* st, int index, sqlite3_int64 value) { if (fault == BIND && matches(st)) { fault = NONE; return SQLITE_IOERR; } return __real_sqlite3_bind_int64(st, index, value); } extern int __real_sqlite3_step(sqlite3_stmt*); int __wrap_sqlite3_step(sqlite3_stmt* st) { if (fault == STEP && matches(st)) { if (skip_steps) skip_steps--; else { fault = NONE; return SQLITE_IOERR; } } return __real_sqlite3_step(st); } extern int __real_sqlite3_finalize(sqlite3_stmt*); int __wrap_sqlite3_finalize(sqlite3_stmt* st) { int fail = fault == FINALIZE && matches(st); if (fail) fault = NONE; int rc = __real_sqlite3_finalize(st); return fail ? SQLITE_IOERR : rc; } static void ready(void* arg, int result, const char* link) { (void)arg; calls++; last_result = result; if (result == CHAT_JOIN_OK) { struct InviteData decoded; char error[128]; assert(link && !invite_link_decode(link, strlen(link), &decoded, error, sizeof(error))); assert(decoded.channelId == expected_channel && decoded.nodeId == active->node_id && !strcmp(decoded.password, "p")); assert(chat_join_lookup_inviter(active, expected_channel, decoded.join_key) == active->node_id); } else assert(!link); } static int read_query(unsigned query, struct UTUN_INSTANCE* inst, uint64_t channel) { uint64_t ids[4] = {77,88,99,111}, before[4]; memcpy(before, ids, sizeof(ids)); uint8_t key[32], old[32]; memset(key, 0xa5, sizeof(key)); memcpy(old, key, sizeof(key)); struct InviteAddr addresses[INVITE_ADDR_MAX]; struct InviteReality reality; int rc; if (!query) { rc = chat_invite_candidate_nodes(inst, channel, ids, 4); if (rc < 0) assert(!memcmp(ids, before, sizeof(ids))); } else if (query == 1) rc = node_has_both_public(inst, inst->node_id); else if (query == 2) { rc = get_node_pubkey(inst, inst->node_id, 0, key); if (rc < 0) assert(!memcmp(old, key, sizeof(key))); } else rc = collect_node_addrs_from_db(inst, inst->node_id, addresses, INVITE_ADDR_MAX, 0, &reality); return rc; } int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_CHAT_SYNC, DEBUG_LEVEL_DEBUG); size_t baseline = u_get_allocated_count(); utun_instance_set_tun_init_enabled(0); struct UASYNC* ua = uasync_create(); assert(ua); active = utun_instance_create_from_str(ua, "[global]\nmy_node_name=invite test\n[server: udp]\naddr=127.0.0.1:0\n" "type=public\n[ntp]\nenabled=no\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n"); assert(active && !utun_core_start(active) && !chat_service_start(active)); expected_channel = chat_core_create_channel_auto(active, "invite test"); assert(expected_channel); const char* queries[] = {"SELECT node_id,node_type", "SELECT family FROM node_addresses", "SELECT x25519_pubkey", "SELECT family,socket_id"}; unsigned cases = 0; for (unsigned q = 0; q < 4; q++) for (enum fault point = PREPARE; point <= FINALIZE; point++) { if (!q && point == BIND) continue; target = queries[q]; fault = point; skip_steps = !q && point == STEP ? 1 : 0; assert(read_query(q, active, expected_channel) < 0 && fault == NONE); assert(read_query(q, active, expected_channel) >= 0); cases++; } target = queries[0]; fault = FINALIZE; uint64_t selected = 77; assert(chat_invite_best_node(active, expected_channel, &selected) < 0 && fault == NONE && selected == 77); assert(!chat_invite_best_node(active, expected_channel, &selected) && selected == active->node_id); target = NULL; struct chat_invite_build* pending = chat_invite_build_link(active, expected_channel, 0, "p", ready, NULL); assert(pending && !calls); for (unsigned i = 0; i < 100 && !calls; i++) uasync_poll(ua, 1); assert(calls == 1 && last_result == CHAT_JOIN_OK); pending = chat_invite_build_link(active, expected_channel, active->node_id, "p", ready, NULL); assert(pending); chat_invite_build_cancel(pending); assert(calls == 2 && last_result == CHAT_JOIN_CANCELLED); assert(!chat_invite_build_link(active, expected_channel, 42, NULL, ready, NULL)); selected = 77; assert(chat_invite_best_node(NULL, expected_channel, &selected) < 0 && selected == 77); assert(chat_invite_best_node(active, 0, &selected) < 0 && selected == 77); utun_instance_destroy(active); active = NULL; uasync_poll(ua, 0); uasync_destroy(ua, 0); assert(u_get_allocated_count() == baseline); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS invite:%u SQL fault/retries,atomic outputs,shared address projection,self ACK/cancel,invalid target", cases); return 0; }