Exercise early STCP adapter failure and cancellation callbacks

This commit is contained in:
evgeny committed 2026-10-23 21:39:32 +02:00
1 parent 18f10b23b3
commit 2cad81050d
2 files changed
+91 -6

No files matched your search

+2 -1
View File
@@ -392,9 +392,10 @@ test_attachment_send_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_dm_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/lib
test_dm_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_transport_SOURCES = test_stcp_link.c
test_transport_SOURCES = test_stcp_link.c $(test_clock_sources)
test_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_transport_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_transport_LDFLAGS = $(test_clock_ldflags)
test_etcp_transport_SOURCES = test_etcp_stcp.c $(test_clock_sources)
test_etcp_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
+89 -5
View File
@@ -3,6 +3,8 @@
#include "stcp_client.h"
#include "secure_channel.h"
#include "utun_instance.h"
#include "etcp.h"
#include "test_clock.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
@@ -55,12 +57,13 @@ static int test1_basic(void) {
TASSERT(sc);
int ticks = 0;
while ((!g_on_srv_link || !cli_ready) && ticks < 5000) { uasync_poll(ua, 10); ticks++; }
while ((!g_on_srv_link || !cli_ready) && ticks < 5000) { test_clock_poll(ua, 10); ticks++; }
TASSERT(g_on_srv_link != NULL); TASSERT(cli_ready);
stcp_client_destroy(sc);
stcp_link_close(g_on_srv_link);
stcp_link_server_destroy(ss);
uasync_drain_immediate(ua);
u_free(inst);
uasync_destroy(ua, 1);
return 0;
@@ -86,7 +89,7 @@ static int test2_wrong_key(void) {
// Неверный ключ должен завершить handshake закрытием, без создания серверного линка.
struct stcp_conn* conn = stcp_client_get_conn(sc); TASSERT(conn);
uint64_t start = get_time_tb(), deadline = start + 20000;
while (conn->state != STCP_STATE_CLOSED && get_time_tb() < deadline) uasync_poll(ua, 10);
while (conn->state != STCP_STATE_CLOSED && get_time_tb() < deadline) test_clock_poll(ua, 10);
TASSERT(conn->state == STCP_STATE_CLOSED);
TASSERT(!cli_ready);
TASSERT(!g_on_srv_link); // should NOT connect with wrong key
@@ -95,22 +98,103 @@ static int test2_wrong_key(void) {
stcp_client_destroy(sc);
stcp_link_server_destroy(ss);
uasync_drain_immediate(ua);
u_free(inst);
uasync_destroy(ua, 1);
return 0;
}
struct failure_case {
struct stcp_link *link;
unsigned closed, ready;
int error, close_inside;
};
static void adapter_ready(struct stcp_link *link, void *arg) {
(void)link;
((struct failure_case *)arg)->ready++;
}
static void adapter_closed(struct stcp_link *link, int err, void *arg) {
struct failure_case *result = arg;
result->closed++; result->error = err;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "adapter close: count=%u err=%d close_inside=%d", result->closed, err, result->close_inside);
if (result->close_inside) {
stcp_link_close(link);
stcp_link_close(link); /* Повторный close внутри того же callback не планирует второй free. */
result->link = NULL;
}
}
/* 0: refused; 1: отмена до poll; 2: неверный ключ; 3: молчащий сервер; 4: close из close-callback. */
static int test_adapter_failure(unsigned scenario) {
size_t before = u_get_allocated_count();
struct UASYNC *ua = uasync_create(); TASSERT(ua);
struct UTUN_INSTANCE *inst = make_inst(ua, &c_keys); TASSERT(inst);
struct ETCP_CONN parent = {.instance = inst};
struct ETCP_LINK path = {.etcp = &parent, .is_tcp = 1};
struct failure_case result = {.close_inside = scenario == 4};
struct sockaddr_storage address = {0};
struct sockaddr_in *v4 = (struct sockaddr_in *)&address;
v4->sin_family = AF_INET; v4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
struct stcp_server *server = NULL;
socket_t reserved = SOCKET_INVALID;
int server_ready = 0;
if (scenario == 2) {
server = stcp_server_create(ua, 0, &s_keys, NULL, NULL, on_cli_ready, &server_ready, NULL, NULL, AF_INET);
TASSERT(server);
v4->sin_port = htons(stcp_server_get_port(server));
struct SC_MYKEYS rogue; TASSERT(sc_generate_keypair(&rogue) == SC_OK);
memcpy(parent.crypto_ctx.peer_public_key, rogue.public_key, SC_PUBKEY_SIZE);
} else {
reserved = socket(AF_INET, SOCK_STREAM, 0); TASSERT(reserved != SOCKET_INVALID);
TASSERT(bind(reserved, (struct sockaddr *)v4, sizeof(*v4)) == 0);
socklen_t length = sizeof(*v4);
TASSERT(getsockname(reserved, (struct sockaddr *)v4, &length) == 0);
if (scenario == 3) TASSERT(listen(reserved, 1) == 0);
memcpy(parent.crypto_ctx.peer_public_key, s_keys.public_key, SC_PUBKEY_SIZE);
}
result.link = stcp_link_connect(&path, &address, ntohs(v4->sin_port)); TASSERT(result.link);
path.tcp_link = result.link;
stcp_link_set_on_ready(result.link, adapter_ready, &result);
stcp_link_set_on_close(result.link, adapter_closed, &result);
if (scenario == 1) { stcp_link_close(result.link); result.link = NULL; }
uint64_t deadline = get_time_tb() + (scenario == 3 ? STCP_CONNECT_TIMEOUT + 1000 : 20000);
while (!result.closed && get_time_tb() < deadline) test_clock_poll(ua, 100);
TASSERT(!result.ready && !server_ready);
TASSERT(result.closed == (scenario == 1 ? 0 : 1));
if (scenario == 0 || scenario == 4) TASSERT(result.error == ECONNREFUSED);
if (scenario == 3) TASSERT(result.error == ETIMEDOUT);
test_clock_run_for(ua, 1000);
TASSERT(result.closed == (scenario == 1 ? 0 : 1));
if (result.link) stcp_link_close(result.link);
if (server) stcp_server_destroy(server);
if (reserved != SOCKET_INVALID) socket_close_wrapper(reserved);
uasync_drain_immediate(ua);
TASSERT(ua->timer_alloc_count == ua->timer_free_count && ua->socket_alloc_count == ua->socket_free_count);
u_free(inst); uasync_destroy(ua, 0);
TASSERT(u_get_allocated_count() == before);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS adapter failure scenario=%u callbacks=%u", scenario, result.closed);
return 0;
}
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO);
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
test_clock_init(100);
TASSERT(sc_generate_keypair(&s_keys) == SC_OK);
TASSERT(sc_generate_keypair(&c_keys) == SC_OK);
test1_basic();
test2_wrong_key();
tests_total++; if (!test1_basic()) tests_passed++;
tests_total++; if (!test2_wrong_key()) tests_passed++;
for (unsigned scenario = 0; scenario < 5; scenario++) {
tests_total++; if (!test_adapter_failure(scenario)) tests_passed++;
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", 2 - (test_failed ? 1 : 0), 2);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", tests_passed, tests_total);
test_clock_finish();
return test_failed ? 1 : 0;
}