From c11e38963a45a8aac567a1715307cfbfb341cec4 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Nov 2026 23:19:07 +0100 Subject: [PATCH] Reproduce revoked history and sound access on shared transport --- tests/test_chat_group_isolation.c | 99 ++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/tests/test_chat_group_isolation.c b/tests/test_chat_group_isolation.c index 661f1b916..3ca6a5181 100644 --- a/tests/test_chat_group_isolation.c +++ b/tests/test_chat_group_isolation.c @@ -11,6 +11,7 @@ #include "chat/member_sync.h" #include "chat/chat_event.h" #include "chat/chat_join.h" +#include "chat/chat_sync.h" #include "chat/chat_core_priv.h" #include "chat/chat_attachment.h" #include "media_async/media_async.h" @@ -38,6 +39,9 @@ #include "../src/routing_layer/multicast.c" /* Сверяем активные операции/offset после сетевой инъекции и до файловых callbacks. */ #include "../src/media_delivery/file_transfer.c" +/* Сверяем историю, настройки и отложенную выдачу настоящих моделей синхронизации. */ +#include "../src/chat/db_sync.c" +#include "../src/chat/merkle_sync.c" #define REQUIRE(expr) do { if (!(expr)) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "isolation setup: %s", #expr); return -1; } } while (0) @@ -959,6 +963,98 @@ static int dm_isolation(void) { return 0; } +/* READY намеренно остаётся прежним: отзыв проверяется до уведомлений BGP и на границе выдачи. */ +static int history_isolation(void) { + struct UTUN_INSTANCE* server = fixture.nodes[0]; struct UTUN_INSTANCE* client = fixture.nodes[1]; + struct ETCP_CONN* conn = instance_find_conn(server, client->node_id); + REQUIRE(sqlite3_exec(server->topo_sqlite_db, "UPDATE peers_112 SET admitted=1", NULL, NULL, NULL) == SQLITE_OK); + member_sync_destroy(server); /* chat_sync владеет новым общим Merkle вместо отдельного тестового MEMBERS. */ + REQUIRE(!chat_sync_init(server)); + for (unsigned i = 0; i < 2; i++) + REQUIRE(!topo_group_new_conn(fixture.groups[i][1], instance_find_conn(fixture.nodes[i], fixture.nodes[1 - i]->node_id))); + uint64_t deadline = get_time_tb() + 50000; + while (!topo_group_peer_ready(fixture.groups[0][1], client->node_id) && get_time_tb() < deadline) uasync_poll(fixture.ua, 1); + REQUIRE(topo_group_peer_ready(fixture.groups[0][1], client->node_id)); + for (unsigned i = 0; i < 100; i++) uasync_poll(fixture.ua, 1); + const uint8_t services[] = {ETCP_ID_DB_SYNC, ETCP_ID_CHAT_SYNC, ETCP_ID_MEMBER_SYNC}; + for (unsigned i = 0; i < sizeof(services); i++) { + fixture.receivers[services[i]] = server->api_bindings.callbacks[services[i]]; + REQUIRE(fixture.receivers[services[i]]); server->api_bindings.callbacks[services[i]] = observe; + } + struct DB_SYNC_INSTANCE* tables[2]; + for (unsigned g = 0; g < 2; g++) { + tables[g] = db_sync_instance_find(server, 111 + g); REQUIRE(tables[g]); + REQUIRE(!db_sync_verify(tables[g])); tables[g]->sync_gated = 0; + merkle_sync_cancel(server, MT_DATA_MESSAGES, client->node_id, tables[g]->ns); + } + uint8_t push[10 + DB_WIRE_FIXED + 7] = {ETCP_ID_DB_SYNC}; + struct db_message_revision revision = {.ver = 1, .nonce = {0x67}}; + db_write64(push + 1, 112); push[9] = DB_MSG_PUSH; + uint8_t* record = push + 10; + REQUIRE(!chat_message_id(112, 100, client->node_id, revision.nonce, record)); + db_write64(record + 32, 100); db_write64(record + 40, client->node_id); memcpy(record + 48, revision.nonce, 16); + db_write64(record + 64, 1); db_write32(record + 80, 7); memcpy(record + 84, "history", 7); + REQUIRE(!db_sync_sign_message(112, 100, client->node_id, &revision, "history", 7, client->my_ed25519_privkey, record + 91)); + unsigned before = db_sync_count(tables[1]); + REQUIRE(!inject(push, sizeof(push))); + check("admitted DB_SYNC PUSH is committed in B", db_sync_count(tables[1]) == before + 1); + uint8_t sound[11] = {ETCP_ID_CHAT_SYNC}; db_write64(sound + 1, 112); sound[9] = CS_MSG_SOUND_SETTING; sound[10] = 1; + REQUIRE(!inject(sound, sizeof(sound))); + struct SI_PEER* p = si_peer_find(tables[1], client->node_id); REQUIRE(p); + check("admitted SOUND_SETTING is applied in B", p->sound_on == 1); + + /* ACK адресован своей настоящей записи; чужой допуск не должен менять delivered_peers. */ + uint8_t signature[64], ack[50] = {ETCP_ID_DB_SYNC}; + REQUIRE(!db_sync_sign_message(112, 101, server->node_id, &revision, "local", 5, server->my_ed25519_privkey, signature)); + REQUIRE(!db_sync_insert_signed(tables[1], "local", 5, signature, 64, 101, NULL, &revision)); + db_write64(ack + 1, 112); ack[9] = DB_MSG_ACK_PUSH; + REQUIRE(!chat_message_id(112, 101, server->node_id, revision.nonce, ack + 10)); db_write64(ack + 42, 1); + /* Новая версия уже известного сообщения остаётся криптографически корректной после отзыва. */ + revision.ver = 2; revision.edited_at = 102; + db_write64(record + 64, revision.ver); db_write64(record + 72, revision.edited_at); + REQUIRE(!db_sync_sign_message(112, 100, client->node_id, &revision, "history", 7, client->my_ed25519_privkey, record + 91)); + char sql[192]; snprintf(sql, sizeof(sql), "UPDATE peers_112 SET admitted=0 WHERE node_id=%lld", (long long)client->node_id); + REQUIRE(sqlite3_exec(server->topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK); + REQUIRE(topo_group_peer_ready(fixture.groups[0][1], client->node_id)); + REQUIRE(!inject(push, sizeof(push)) && !inject(ack, sizeof(ack))); + check("revoked DB_SYNC PUSH cannot edit existing history", key_value(server, "SELECT ver FROM msg_112 WHERE timestamp=100") == 1); + check("revoked DB_SYNC ACK cannot acknowledge history", key_value(server, "SELECT delivered_peers FROM msg_112 WHERE timestamp=101") == 0); + sound[10] = 0; REQUIRE(!inject(sound, sizeof(sound))); + check("revoked SOUND_SETTING cannot change B", si_peer_find(tables[1], client->node_id)->sound_on == 1); + merkle_sync_cancel(server, MT_DATA_MESSAGES, client->node_id, "112"); + uint8_t wake[MS_HEADER] = {ETCP_ID_MEMBER_SYNC}; db_write64(wake + 1, 112); wake[9] = MS_WAKE; wake[22] = MT_DATA_MESSAGES; + REQUIRE(!inject(wake, sizeof(wake))); + check("revoked MESSAGES cannot create a Merkle session", !ms_find(server->msync, conn, MT_DATA_MESSAGES, 112)); + check("revoked MESSAGES cannot start locally", merkle_sync_start(server, MT_DATA_MESSAGES, client->node_id, "112", NULL, NULL) < 0); + db_write64(sound + 1, 111); REQUIRE(!inject(sound, sizeof(sound))); + p = si_peer_find(tables[0], client->node_id); + check("neighbouring SOUND_SETTING still works through same ETCP", p && !p->sound_on && fixture.transport->links_up); + + REQUIRE(sqlite3_exec(server->topo_sqlite_db, "UPDATE peers_112 SET admitted=1", NULL, NULL, NULL) == SQLITE_OK); + REQUIRE(!inject(push, sizeof(push)) && !inject(ack, sizeof(ack))); + check("same PUSH and ACK succeed after readmission", key_value(server, "SELECT ver FROM msg_112 WHERE timestamp=100") == 2 && + key_value(server, "SELECT delivered_peers FROM msg_112 WHERE timestamp=101") == 1); + merkle_sync_cancel(server, MT_DATA_MESSAGES, client->node_id, "112"); + REQUIRE(!merkle_sync_start(server, MT_DATA_MESSAGES, client->node_id, "112", NULL, NULL)); + struct ms_session* session = ms_find(server->msync, conn, MT_DATA_MESSAGES, 112); REQUIRE(session); + REQUIRE(sqlite3_exec(server->topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK); + for (unsigned i = 0; i < 20; i++) uasync_poll(fixture.ua, 1); + check("revocation before deferred Merkle work prevents transmission", !session->sent_bytes && !session->tx); + + snprintf(sql, sizeof(sql), "UPDATE peers_112 SET admitted=(node_id!=%lld)", (long long)server->node_id); + REQUIRE(sqlite3_exec(server->topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK); + db_write64(sound + 1, 112); REQUIRE(!inject(sound, sizeof(sound))); + check("revoked self cannot accept SOUND_SETTING", si_peer_find(tables[1], client->node_id)->sound_on == 1); + check("revoked self cannot serve MESSAGES", !db_message_peer(server, "112", client->node_id)); + REQUIRE(sqlite3_exec(server->topo_sqlite_db, "UPDATE peers_112 SET admitted=1", NULL, NULL, NULL) == SQLITE_OK); + fixture.groups[0][1]->stopping = 1; + REQUIRE(!inject(sound, sizeof(sound))); + check("stopping group cannot accept SOUND_SETTING or MESSAGES", si_peer_find(tables[1], client->node_id)->sound_on == 1 && + !db_message_peer(server, "112", client->node_id)); + fixture.groups[0][1]->stopping = 0; + return 0; +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO); @@ -984,7 +1080,8 @@ int main(void) { #ifdef __linux__ if (!fixture.failed && file_isolation(1)) fixture.failed++; #endif - if (!fixture.failed && (attachment_isolation() || transfer_isolation() || dm_isolation() || stop_isolation())) fixture.failed++; + if (!fixture.failed && (attachment_isolation() || transfer_isolation() || dm_isolation() || history_isolation() || stop_isolation())) + fixture.failed++; } } pthread_mutex_lock(&fixture.disk_mutex); fixture.disk_released = 1; pthread_cond_signal(&fixture.disk_condition);