diff --git a/tests/test_etcp_session.c b/tests/test_etcp_session.c index e2046134..f2c72a3a 100644 --- a/tests/test_etcp_session.c +++ b/tests/test_etcp_session.c @@ -447,6 +447,49 @@ static void oom_session_recovery(void) { puts("PASS encrypted OOM recovery: REINIT both owners, preserved links, stale DATA rejected, fresh delivery both ways"); } +/* Настоящий AES/ingress: одинаковая пара seq=1/no=1 в новой эпохе не принимает прежний ACK. */ +static struct frame encrypted_attempt_ack(void) { + struct ETCP_DGRAM* pkt = memory_pool_alloc(ep[1].inst.pkt_pool); CHECK(pkt); + pkt->link = &ep[1].links[0]; pkt->data_len = etcp_ack_section_size(1); pkt->noencrypt_len = 0; + memset(pkt->data,0,pkt->data_len); pkt->data[0] = ETCP_SECTION_ACK; pkt->data[1] = 1; + pkt->data[ETCP_ACK_BASE_SIZE] = 1; pkt->data[ETCP_ACK_BASE_SIZE+4] = 1; + unsigned before = tail; + CHECK(etcp_encrypt_send(pkt) > 0 && tail == before+1 && head == before); + memory_pool_free(ep[1].inst.pkt_pool,pkt); + return frames[head++ % 4096]; +} + +static void compact_attempt_epoch_reuse(void) { + setup(0); + ep[0].inst.data_pool = memory_pool_init(PACKET_DATA_SIZE,"attempt epoch payload"); CHECK(ep[0].inst.data_pool); + struct frame old_ack = {0}; + uint64_t first_epoch = ep[0].c->reset_id; + for (unsigned round = 0; round < 2; round++) { + struct ETCP_CONN* c = ep[0].c; struct ETCP_LINK* link = &ep[0].links[0]; + link->inflight_lim_bytes = 65536; + const uint8_t data[] = {1,0,253}; + CHECK(etcp_int_send(c,data,sizeof(data)) == 0); + struct ETCP_DGRAM* pkt = etcp_request_pkt(c); CHECK(pkt && pkt->attempt); + CHECK(pkt->attempt->seq == 1 && pkt->attempt->attempt_id == etcp_tx_attempt_id(1,1)); + etcp_tx_complete(pkt,1); memory_pool_free(ep[0].inst.pkt_pool,pkt); + if (!round) { + old_ack = encrypted_attempt_ack(); + etcp_conn_fatal_reinit(c,"attempt epoch reuse test"); converge(); + CHECK(c->reset_id != first_epoch); + } else { + CHECK(c->unacked_bytes == sizeof(data) && link->inflight_bytes == sizeof(data)); + uint64_t last_rx = link->last_recv_local_time; + deliver(&old_ack,0); + CHECK(c->unacked_bytes == sizeof(data) && link->inflight_bytes == sizeof(data)); + CHECK(!c->ack_packets_count && !link->peer_rx_data_time_us && link->last_recv_local_time == last_rx); + struct frame fresh_ack = encrypted_attempt_ack(); deliver(&fresh_ack,0); + CHECK(!c->unacked_bytes && !link->inflight_bytes && c->ack_packets_count == 1); + } + } + teardown(); memory_pool_destroy(ep[0].inst.data_pool); ep[0].inst.data_pool = NULL; + puts("PASS encrypted ACK isolation when compact attempt pair is reused in a new epoch"); +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); test_clock_init(10); @@ -463,6 +506,7 @@ int main(void) { } restart_during_confirmation(); CHECK(u_get_allocated_count() == baseline); oom_session_recovery(); CHECK(u_get_allocated_count() == baseline); + compact_attempt_epoch_reuse(); CHECK(u_get_allocated_count() == baseline); for (int scenario=0;scenario<9;scenario++) { setup(scenario == 5); saved[2]=stream_packet(0); saved[3]=stream_packet(1);