|
|
|
|
@ -28,6 +28,9 @@ static int g_fin_count = 0;
|
|
|
|
|
static int g_error_count = 0; |
|
|
|
|
static int g_last_error = 0; |
|
|
|
|
static struct tcp_conn* g_last_fin_tc = NULL; |
|
|
|
|
static int g_fin_sent_count = 0; |
|
|
|
|
static int g_closed_count = 0; |
|
|
|
|
static int g_last_event = 0; // 1=on_fin_sent, 2=on_closed — проверка порядка
|
|
|
|
|
|
|
|
|
|
static void on_fin(struct tcp_conn* tc, void* arg) { |
|
|
|
|
(void)arg; |
|
|
|
|
@ -41,11 +44,24 @@ static void on_error(struct tcp_conn* tc, int err, void* arg) {
|
|
|
|
|
g_last_error = err; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void on_fin_sent_cb(struct tcp_conn* tc, void* arg) { |
|
|
|
|
(void)tc; (void)arg; |
|
|
|
|
g_fin_sent_count++; g_last_event = 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void on_closed_cb(struct tcp_conn* tc, void* arg) { |
|
|
|
|
(void)tc; (void)arg; |
|
|
|
|
g_closed_count++; g_last_event = 2; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void reset_counters(void) { |
|
|
|
|
g_fin_count = 0; |
|
|
|
|
g_error_count = 0; |
|
|
|
|
g_last_error = 0; |
|
|
|
|
g_last_fin_tc = NULL; |
|
|
|
|
g_fin_sent_count = 0; |
|
|
|
|
g_closed_count = 0; |
|
|
|
|
g_last_event = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int push_write(struct tcp_conn* tc, const uint8_t* data, size_t len) { |
|
|
|
|
@ -292,6 +308,235 @@ static void test_error_callback(void) {
|
|
|
|
|
TEST_PASS(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ====================================================================
|
|
|
|
|
// FIN / Close через очередь — новые тесты
|
|
|
|
|
// ====================================================================
|
|
|
|
|
|
|
|
|
|
static void test_push_fin(void) { |
|
|
|
|
TEST_START("push_fin via write_queue"); |
|
|
|
|
|
|
|
|
|
uasync_t* ua = uasync_create(); |
|
|
|
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
|
|
|
reset_counters(); |
|
|
|
|
|
|
|
|
|
int sv[2]; |
|
|
|
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
|
|
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
|
|
|
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL); |
|
|
|
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
|
tc->on_fin_sent = on_fin_sent_cb; |
|
|
|
|
|
|
|
|
|
uint8_t data[64]; |
|
|
|
|
memset(data, 'D', sizeof(data)); |
|
|
|
|
ASSERT_EQ(push_write(tc, data, sizeof(data)), 0, "push_write failed"); |
|
|
|
|
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "push_fin failed"); |
|
|
|
|
|
|
|
|
|
uasync_poll(ua, 10); |
|
|
|
|
|
|
|
|
|
uint8_t recv_buf[128] = {0}; |
|
|
|
|
ssize_t total = 0; |
|
|
|
|
while (total < (ssize_t)sizeof(data)) { |
|
|
|
|
ssize_t n = recv(sv[1], recv_buf + total, sizeof(recv_buf) - total, 0); |
|
|
|
|
ASSERT_TRUE(n >= 0, "recv failed on peer"); |
|
|
|
|
total += n; |
|
|
|
|
} |
|
|
|
|
ASSERT_EQ(total, (ssize_t)sizeof(data), "should receive all data before EOF"); |
|
|
|
|
ASSERT_TRUE(memcmp(data, recv_buf, sizeof(data)) == 0, "data mismatch"); |
|
|
|
|
|
|
|
|
|
// После всех данных — EOF (FIN)
|
|
|
|
|
ssize_t n = recv(sv[1], recv_buf, sizeof(recv_buf), 0); |
|
|
|
|
ASSERT_EQ(n, 0, "should get EOF after FIN"); |
|
|
|
|
|
|
|
|
|
ASSERT_EQ(tc->fin_sent, 1, "tc->fin_sent not set"); |
|
|
|
|
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent not called"); |
|
|
|
|
ASSERT_EQ(g_closed_count, 0, "on_closed should not be called"); |
|
|
|
|
|
|
|
|
|
tcp_conn_destroy(tc); |
|
|
|
|
close(sv[1]); |
|
|
|
|
uasync_destroy(ua, 0); |
|
|
|
|
TEST_PASS(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void test_push_close(void) { |
|
|
|
|
TEST_START("push_close via write_queue"); |
|
|
|
|
|
|
|
|
|
uasync_t* ua = uasync_create(); |
|
|
|
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
|
|
|
reset_counters(); |
|
|
|
|
|
|
|
|
|
int sv[2]; |
|
|
|
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
|
|
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
|
|
|
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL); |
|
|
|
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
|
tc->on_closed = on_closed_cb; |
|
|
|
|
|
|
|
|
|
uint8_t data[64]; |
|
|
|
|
memset(data, 'E', sizeof(data)); |
|
|
|
|
ASSERT_EQ(push_write(tc, data, sizeof(data)), 0, "push_write failed"); |
|
|
|
|
ASSERT_EQ(tcp_conn_push_close(tc), 0, "push_close failed"); |
|
|
|
|
|
|
|
|
|
uasync_poll(ua, 10); |
|
|
|
|
|
|
|
|
|
// Peer должен получить данные перед закрытием
|
|
|
|
|
uint8_t recv_buf[128] = {0}; |
|
|
|
|
ssize_t total = recv(sv[1], recv_buf, sizeof(recv_buf), 0); |
|
|
|
|
ASSERT_TRUE(total == sizeof(data), "peer should receive data"); |
|
|
|
|
ASSERT_TRUE(memcmp(data, recv_buf, sizeof(data)) == 0, "data mismatch"); |
|
|
|
|
|
|
|
|
|
ASSERT_EQ(tc->closed, 1, "tc->closed not set"); |
|
|
|
|
ASSERT_EQ(tc->sock, SOCKET_INVALID, "sock should be invalid after close"); |
|
|
|
|
ASSERT_EQ(g_closed_count, 1, "on_closed not called"); |
|
|
|
|
|
|
|
|
|
tcp_conn_destroy(tc); |
|
|
|
|
close(sv[1]); |
|
|
|
|
uasync_destroy(ua, 0); |
|
|
|
|
TEST_PASS(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void test_fin_data_ordering(void) { |
|
|
|
|
TEST_START("Data before FIN ordering"); |
|
|
|
|
|
|
|
|
|
uasync_t* ua = uasync_create(); |
|
|
|
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
|
|
|
reset_counters(); |
|
|
|
|
|
|
|
|
|
int sv[2]; |
|
|
|
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
|
|
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
|
|
|
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL); |
|
|
|
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
|
tc->on_fin_sent = on_fin_sent_cb; |
|
|
|
|
|
|
|
|
|
uint8_t data1[64], data2[32], data3[48]; |
|
|
|
|
memset(data1, 0x01, sizeof(data1)); memset(data2, 0x02, sizeof(data2)); memset(data3, 0x03, sizeof(data3)); |
|
|
|
|
ASSERT_EQ(push_write(tc, data1, sizeof(data1)), 0, "push_write data1"); |
|
|
|
|
ASSERT_EQ(push_write(tc, data2, sizeof(data2)), 0, "push_write data2"); |
|
|
|
|
ASSERT_EQ(push_write(tc, data3, sizeof(data3)), 0, "push_write data3"); |
|
|
|
|
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "push_fin"); |
|
|
|
|
|
|
|
|
|
uasync_poll(ua, 10); |
|
|
|
|
|
|
|
|
|
// TCP — поток, блоки могут склеиться. Читаем всё и сверяем суммарно.
|
|
|
|
|
uint8_t buf[256], ref[sizeof(data1)+sizeof(data2)+sizeof(data3)]; |
|
|
|
|
memcpy(ref, data1, sizeof(data1)); memcpy(ref + sizeof(data1), data2, sizeof(data2)); memcpy(ref + sizeof(data1) + sizeof(data2), data3, sizeof(data3)); |
|
|
|
|
ssize_t total = 0; |
|
|
|
|
while (total < (ssize_t)sizeof(ref)) { |
|
|
|
|
ssize_t n = recv(sv[1], buf + total, sizeof(buf) - total, 0); |
|
|
|
|
ASSERT_TRUE(n > 0, "recv failed on peer"); |
|
|
|
|
total += n; |
|
|
|
|
} |
|
|
|
|
ASSERT_EQ(total, (ssize_t)sizeof(ref), "total received mismatch"); |
|
|
|
|
ASSERT_TRUE(memcmp(buf, ref, sizeof(ref)) == 0, "data content mismatch"); |
|
|
|
|
|
|
|
|
|
// EOF
|
|
|
|
|
ssize_t n = recv(sv[1], buf, sizeof(buf), 0); |
|
|
|
|
ASSERT_EQ(n, 0, "should get EOF after data"); |
|
|
|
|
|
|
|
|
|
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent should be called"); |
|
|
|
|
|
|
|
|
|
tcp_conn_destroy(tc); |
|
|
|
|
close(sv[1]); |
|
|
|
|
uasync_destroy(ua, 0); |
|
|
|
|
TEST_PASS(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void test_double_push_fin(void) { |
|
|
|
|
TEST_START("Double push_fin ignored"); |
|
|
|
|
|
|
|
|
|
uasync_t* ua = uasync_create(); |
|
|
|
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
|
|
|
reset_counters(); |
|
|
|
|
|
|
|
|
|
int sv[2]; |
|
|
|
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
|
|
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
|
|
|
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL); |
|
|
|
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
|
tc->on_fin_sent = on_fin_sent_cb; |
|
|
|
|
|
|
|
|
|
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "first push_fin"); |
|
|
|
|
ASSERT_EQ(tcp_conn_push_fin(tc), -1, "second push_fin should return -1"); |
|
|
|
|
|
|
|
|
|
uasync_poll(ua, 10); |
|
|
|
|
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent called more than once"); |
|
|
|
|
|
|
|
|
|
tcp_conn_destroy(tc); |
|
|
|
|
close(sv[1]); |
|
|
|
|
uasync_destroy(ua, 0); |
|
|
|
|
TEST_PASS(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void test_double_push_close(void) { |
|
|
|
|
TEST_START("Double push_close ignored"); |
|
|
|
|
|
|
|
|
|
uasync_t* ua = uasync_create(); |
|
|
|
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
|
|
|
reset_counters(); |
|
|
|
|
|
|
|
|
|
int sv[2]; |
|
|
|
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
|
|
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
|
|
|
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL); |
|
|
|
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
|
tc->on_closed = on_closed_cb; |
|
|
|
|
|
|
|
|
|
ASSERT_EQ(tcp_conn_push_close(tc), 0, "first push_close"); |
|
|
|
|
uasync_poll(ua, 10); |
|
|
|
|
ASSERT_EQ(g_closed_count, 1, "on_closed not called after first close"); |
|
|
|
|
|
|
|
|
|
ASSERT_EQ(tcp_conn_push_close(tc), -1, "second push_close should return -1"); |
|
|
|
|
ASSERT_EQ(g_closed_count, 1, "on_closed called more than once"); |
|
|
|
|
|
|
|
|
|
tcp_conn_destroy(tc); |
|
|
|
|
close(sv[1]); |
|
|
|
|
uasync_destroy(ua, 0); |
|
|
|
|
TEST_PASS(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void test_fin_before_close(void) { |
|
|
|
|
TEST_START("FIN before close ordering"); |
|
|
|
|
|
|
|
|
|
uasync_t* ua = uasync_create(); |
|
|
|
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
|
|
|
reset_counters(); |
|
|
|
|
|
|
|
|
|
int sv[2]; |
|
|
|
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
|
|
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
|
|
|
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL); |
|
|
|
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
|
tc->on_fin_sent = on_fin_sent_cb; |
|
|
|
|
tc->on_closed = on_closed_cb; |
|
|
|
|
|
|
|
|
|
uint8_t data[32]; |
|
|
|
|
memset(data, 'F', sizeof(data)); |
|
|
|
|
ASSERT_EQ(push_write(tc, data, sizeof(data)), 0, "push_write"); |
|
|
|
|
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "push_fin"); |
|
|
|
|
ASSERT_EQ(tcp_conn_push_close(tc), 0, "push_close"); |
|
|
|
|
|
|
|
|
|
uasync_poll(ua, 10); |
|
|
|
|
|
|
|
|
|
// Peer получает данные
|
|
|
|
|
uint8_t buf[64]; |
|
|
|
|
ssize_t n = recv(sv[1], buf, sizeof(buf), 0); |
|
|
|
|
ASSERT_EQ(n, (ssize_t)sizeof(data), "peer should get data"); |
|
|
|
|
|
|
|
|
|
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent not called"); |
|
|
|
|
ASSERT_EQ(g_closed_count, 1, "on_closed not called"); |
|
|
|
|
|
|
|
|
|
tcp_conn_destroy(tc); |
|
|
|
|
close(sv[1]); |
|
|
|
|
uasync_destroy(ua, 0); |
|
|
|
|
TEST_PASS(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int main(void) { |
|
|
|
|
debug_config_init(); |
|
|
|
|
debug_set_level(DEBUG_LEVEL_INFO); |
|
|
|
|
@ -304,6 +549,12 @@ int main(void) {
|
|
|
|
|
test_high_water_pause(); |
|
|
|
|
test_connect_detection(); |
|
|
|
|
test_error_callback(); |
|
|
|
|
test_push_fin(); |
|
|
|
|
test_push_close(); |
|
|
|
|
test_fin_data_ordering(); |
|
|
|
|
test_double_push_fin(); |
|
|
|
|
test_double_push_close(); |
|
|
|
|
test_fin_before_close(); |
|
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "=== Test Statistics ==="); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "Tests run: %d", tests_run); |
|
|
|
|
|