Reproduce cross-group file transfer effects and destination truncation

This commit is contained in:
evgeny committed 2026-11-03 21:40:24 +01:00
1 parent a3600c8a44
commit dfdd89b5f0
1 file changed
+101 -1
+101 -1
View File
@@ -35,6 +35,8 @@
#include "../src/routing_layer/nat_detection.c"
/* Управляем границей отложенной выдачи, не заменяя обработчики MULTICAST. */
#include "../src/routing_layer/multicast.c"
/* Сверяем активные операции/offset после сетевой инъекции и до файловых callbacks. */
#include "../src/media_delivery/file_transfer.c"
#define REQUIRE(expr) do { if (!(expr)) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "isolation setup: %s", #expr); return -1; } } while (0)
@@ -790,6 +792,103 @@ static int attachment_isolation(void) {
return 0;
}
static struct {
char source[640];
unsigned lookups, blocked, done[2];
int error[2];
struct file_transfer_op* pulls[2];
} transfers;
/* Предметный владелец разрешает объект: отказ чужой группы обязан произойти раньше lookup. */
static int transfer_lookup(void* arg, uint64_t group, uint64_t peer, const uint8_t object[16],
uint64_t size, char* path, size_t cap) {
(void)arg; (void)peer; (void)object; (void)size;
transfers.lookups++;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "isolation file lookup group=%llu count=%u", (unsigned long long)group, transfers.lookups);
snprintf(path, cap, "%s", transfers.source); return 0;
}
static void transfer_done(void* arg, int error) {
unsigned i = (unsigned)(uintptr_t)arg;
transfers.pulls[i] = NULL; transfers.done[i]++; transfers.error[i] = error;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "isolation file done group=%u error=%d count=%u", 111 + i, error, transfers.done[i]);
}
/* Удерживаем настоящие DATA в reorder очереди B; A принимает их обычным обработчиком. */
static int transfer_ready(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t peer, const uint8_t* data, size_t size) {
(void)inst; (void)peer; (void)data; (void)size;
if (group != 112) return 1;
transfers.blocked++; return 0;
}
static int transfer_isolation(void) {
struct UTUN_INSTANCE* receiver = fixture.nodes[0]; struct UTUN_INSTANCE* sender = fixture.nodes[1];
uint8_t object[16] = {0x53}, bytes[65536]; memset(bytes, 0x6a, sizeof(bytes));
char paths[2][640], denied[640];
snprintf(transfers.source, sizeof(transfers.source), "%s/transfer.source", fixture.directory);
snprintf(denied, sizeof(denied), "%s/transfer.denied", fixture.directory);
FILE* file = fopen(transfers.source, "wb"); REQUIRE(file);
REQUIRE(fwrite(bytes, 1, sizeof(bytes), file) == sizeof(bytes) && !fclose(file));
file = fopen(denied, "wb"); REQUIRE(file); REQUIRE(fwrite("keep", 1, 4, file) == 4 && !fclose(file));
for (unsigned i = 0; i < 2; i++) REQUIRE(file_transfer_create(fixture.nodes[i], transfer_lookup, NULL));
struct file_transfer_op* refused = file_transfer_pull(receiver->file_transfer, 112, sender->node_id, object,
sizeof(bytes), denied, transfer_done, (void*)1);
if (refused) file_transfer_cancel(refused);
file = fopen(denied, "rb"); REQUIRE(file); char marker[4]; size_t length = fread(marker, 1, 4, file); REQUIRE(!fclose(file));
check("FILE_TRANSFER denied local pull preserves destination before fopen", !refused && length == 4 && !memcmp(marker, "keep", 4));
REQUIRE(sqlite3_exec(receiver->topo_sqlite_db, "UPDATE peers_112 SET admitted=1", NULL, NULL, NULL) == SQLITE_OK);
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], sender->node_id) ||
!topo_group_peer_ready(fixture.groups[1][1], receiver->node_id)) && get_time_tb() < deadline) uasync_poll(fixture.ua, 1);
REQUIRE(topo_group_peer_ready(fixture.groups[0][1], sender->node_id) && topo_group_peer_ready(fixture.groups[1][1], receiver->node_id));
etcp_router_set_receive_ready(receiver, ETCP_RT_ID_FILE_TRANSFER, transfer_ready);
for (unsigned i = 0; i < 2; i++) {
snprintf(paths[i], sizeof(paths[i]), "%s/transfer.%u", fixture.directory, i);
transfers.pulls[i] = file_transfer_pull(receiver->file_transfer, 111 + i, sender->node_id, object,
sizeof(bytes), paths[i], transfer_done, (void*)(uintptr_t)i);
REQUIRE(transfers.pulls[i]);
}
deadline = get_time_tb() + 30000;
while ((!transfers.done[0] || !transfers.blocked) && get_time_tb() < deadline) uasync_poll(fixture.ua, 1);
REQUIRE(transfers.done[0] == 1 && transfers.error[0] == 0 && transfers.blocked && transfers.pulls[1]);
check("FILE_TRANSFER A completes while B waits in router", transfers.pulls[1]->offset == 0 && transfers.lookups == 2);
struct ETCP_ROUTER_CONN* route = etcp_router_conn_get(receiver, 112, sender->node_id, ETCP_RT_ID_FILE_TRANSFER);
REQUIRE(route && route->peer_reset_id && route->recv_q->head);
struct SVC_ROUTE_HDR saved = {.cmd = ETCP_ID_SVC_ROUTE, .group_id = 112, .src_node_id = sender->node_id,
.dst_node_id = receiver->node_id, .svc_id = ETCP_RT_ID_FILE_TRANSFER, .reset_id = route->peer_reset_id,
.peer_reset_id = route->reset_id, .seq = route->rx_seq};
uint8_t data[26] = {FT_DATA}; memcpy(data + 1, transfers.pulls[1]->tid, 16); data[25] = 0x6a;
char sql[160]; snprintf(sql, sizeof(sql), "UPDATE peers_112 SET admitted=0 WHERE node_id=%lld", (long long)sender->node_id);
REQUIRE(sqlite3_exec(receiver->topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK);
topo_group_revalidate_members(fixture.groups[0][1]);
check("FILE_TRANSFER revoke cancels B operation before callback", !transfers.pulls[1] && transfers.done[1] == 1 &&
transfers.error[1] == -2 && !receiver->file_transfer->ops);
etcp_router_set_receive_ready(receiver, ETCP_RT_ID_FILE_TRANSFER, NULL);
REQUIRE(!send_routed(&saved, data, sizeof(data), ROUTE_CRYPTO_SIGN));
uint8_t request[49] = {FT_REQUEST}; request[1] = 0x77; memcpy(request + 17, object, 16);
uint64_t size = sizeof(bytes); memcpy(request + 33, &size, 8); saved.seq++;
unsigned lookups = transfers.lookups;
REQUIRE(!send_routed(&saved, request, sizeof(request), ROUTE_CRYPTO_SIGN));
check("revoked signed FILE_TRANSFER REQUEST never reaches lookup", transfers.lookups == lookups);
struct stat state; REQUIRE(!stat(paths[1], &state));
check("revoked FILE_TRANSFER queue and late DATA cannot write destination", state.st_size == 0 && transfers.done[1] == 1);
/* Та же личная копия повторяется через разрешённую A с новым transfer ID. */
if (transfers.pulls[1]) file_transfer_cancel(transfers.pulls[1]);
transfers.done[1] = 0;
transfers.pulls[1] = file_transfer_pull(receiver->file_transfer, 111, sender->node_id, object,
sizeof(bytes), paths[1], transfer_done, (void*)1); REQUIRE(transfers.pulls[1]);
deadline = get_time_tb() + 30000;
while (!transfers.done[1] && get_time_tb() < deadline) uasync_poll(fixture.ua, 1);
file = fopen(paths[1], "rb"); REQUIRE(file); uint8_t received[sizeof(bytes)];
length = fread(received, 1, sizeof(received), file); REQUIRE(!fclose(file));
check("FILE_TRANSFER retries same object via A on shared ETCP", transfers.done[1] == 1 && !transfers.error[1] &&
length == sizeof(bytes) && !memcmp(received, bytes, sizeof(bytes)) && fixture.transport->links_up);
for (unsigned i = 0; i < 2; i++) file_transfer_destroy(fixture.nodes[i]->file_transfer);
return 0;
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
@@ -815,13 +914,14 @@ int main(void) {
#ifdef __linux__
if (!fixture.failed && file_isolation(1)) fixture.failed++;
#endif
if (!fixture.failed && (attachment_isolation() || stop_isolation())) fixture.failed++;
if (!fixture.failed && (attachment_isolation() || transfer_isolation() || stop_isolation())) fixture.failed++;
}
}
pthread_mutex_lock(&fixture.disk_mutex); fixture.disk_released = 1; pthread_cond_signal(&fixture.disk_condition);
pthread_mutex_unlock(&fixture.disk_mutex);
for (unsigned i = 0; i < 2; i++) if (fixture.handles[i]) conn_mgr_close(fixture.handles[i]);
for (unsigned i = 0; i < 2; i++) if (fixture.nodes[i]) {
file_transfer_destroy(fixture.nodes[i]->file_transfer);
chat_join_destroy(fixture.nodes[i]); radio_call_destroy(fixture.nodes[i]); member_files_destroy(fixture.nodes[i]);
member_sync_destroy(fixture.nodes[i]); utun_instance_destroy(fixture.nodes[i]);
}