Reproduce revoked history and sound access on shared transport

This commit is contained in:
evgeny committed 2026-11-03 23:19:07 +01:00
1 parent f4939e4444
commit c11e38963a
1 file changed
+98 -1
+98 -1
View File
@@ -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);