12 changed files with 373 additions and 107 deletions
@ -0,0 +1,180 @@ |
|||||||
|
/* Проверка режима и фаз standby на реальных keepalive-пакетах до шифрования.
|
||||||
|
* Управляемая отправка исключает сеть; отказ таймера проверяет сохранение рабочего режима. */ |
||||||
|
#include <assert.h> |
||||||
|
#include <string.h> |
||||||
|
#include "utun_instance.h" |
||||||
|
#include "etcp.h" |
||||||
|
#include "etcp_keepalive.h" |
||||||
|
#include "standby.h" |
||||||
|
#include "debug_config.h" |
||||||
|
|
||||||
|
static struct UASYNC* ua; |
||||||
|
static struct UTUN_INSTANCE inst; |
||||||
|
static struct ETCP_CONN udp, tcp; |
||||||
|
static struct ETCP_LINK links[3]; |
||||||
|
static struct ll_queue udp_queue, tcp_queue; |
||||||
|
static struct ll_entry *udp_entry, *tcp_entry; |
||||||
|
static struct { unsigned count; uint8_t flags; uint16_t active, sleep; } sent[3]; |
||||||
|
static int fail_phase_timer, activity_events, wait_calls; |
||||||
|
static unsigned wait_after_packets; |
||||||
|
|
||||||
|
/* Перехватывает сформированный keepalive, сохраняя его реальные флаги и тайминг. */ |
||||||
|
int __wrap_etcp_encrypt_send(struct ETCP_DGRAM* pkt) { |
||||||
|
assert(pkt && pkt->data[0] == ETCP_KEEPALIVE && pkt->data_len >= 4); |
||||||
|
int i; |
||||||
|
for (i = 0; i < 3 && pkt->link != &links[i]; i++); |
||||||
|
assert(i < 3); |
||||||
|
sent[i].count++; |
||||||
|
sent[i].flags = pkt->data[1]; |
||||||
|
sent[i].active = pkt->data_len >= 6 ? pkt->data[4] | ((uint16_t)pkt->data[5] << 8) : 0; |
||||||
|
sent[i].sleep = pkt->data_len >= 8 ? pkt->data[6] | ((uint16_t)pkt->data[7] << 8) : 0; |
||||||
|
return pkt->data_len; |
||||||
|
} |
||||||
|
|
||||||
|
void* __real_uasync_set_timeout(struct UASYNC* ua, int tb, void* arg, timeout_callback_t cb, const char* name); |
||||||
|
|
||||||
|
/* Отказывает только фазовому таймеру, остальные таймеры выполняются настоящим uasync. */ |
||||||
|
void* __wrap_uasync_set_timeout(struct UASYNC* loop, int tb, void* arg, timeout_callback_t cb, const char* name) { |
||||||
|
if (fail_phase_timer && name && !strcmp(name, "standby_phase")) { |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "[TEST] injected standby phase timer failure"); |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
return __real_uasync_set_timeout(loop, tb, arg, cb, name); |
||||||
|
} |
||||||
|
|
||||||
|
/* Подписчики активности видят уже согласованные режимы, без промежуточного состояния. */ |
||||||
|
static void activity_changed(struct UTUN_INSTANCE* owner, int active, void* arg) { |
||||||
|
(void)arg; |
||||||
|
assert(owner == &inst && inst.client_activity == active); |
||||||
|
assert(standby_is_enabled() == !active); |
||||||
|
for (int i = 0; i < 3; i++) assert(links[i].ka_my_sleeping == !active); |
||||||
|
activity_events++; |
||||||
|
} |
||||||
|
|
||||||
|
/* Работа возобновляется после отправки новой фазы пирам или выхода из standby. */ |
||||||
|
static void waited(void* arg) { |
||||||
|
(void)arg; |
||||||
|
assert(sent[0].count > wait_after_packets); |
||||||
|
if (standby_is_enabled()) { |
||||||
|
assert(standby_get_active_remaining_tb() > 0 && sent[0].flags == KA_FLAG_SLEEP && sent[0].active > 0); |
||||||
|
} else { |
||||||
|
assert(inst.client_activity == CLIENT_ACTIVITY_ACTIVE && !links[0].ka_my_sleeping && sent[0].flags == 0); |
||||||
|
} |
||||||
|
wait_calls++; |
||||||
|
} |
||||||
|
|
||||||
|
/* Мобильный узел с исходящим UDP, входящим UDP и TCP линками. */ |
||||||
|
static void setup(void) { |
||||||
|
ua = uasync_create(); assert(ua); |
||||||
|
memset(&inst, 0, sizeof(inst)); memset(&udp, 0, sizeof(udp)); memset(&tcp, 0, sizeof(tcp)); |
||||||
|
memset(links, 0, sizeof(links)); memset(sent, 0, sizeof(sent)); |
||||||
|
activity_events = wait_calls = fail_phase_timer = 0; |
||||||
|
inst.ua = ua; inst.client_type = CLIENT_TYPE_MOBILE; inst.client_activity = CLIENT_ACTIVITY_STANDBY; |
||||||
|
udp.instance = tcp.instance = &inst; |
||||||
|
strcpy(udp.log_name, "standby-test-udp"); strcpy(tcp.log_name, "standby-test-tcp"); |
||||||
|
for (int i = 0; i < 3; i++) { |
||||||
|
links[i].etcp = i == 2 ? &tcp : &udp; |
||||||
|
links[i].local_link_id = i + 1; links[i].initialized = links[i].link_status = 1; |
||||||
|
links[i].keepalive_interval = links[i].ka_period_ms = 2000; |
||||||
|
links[i].keepalive_timeout = 20000; links[i].last_recv_local_time = get_time_tb(); |
||||||
|
} |
||||||
|
links[0].next = &links[1]; links[1].is_server = 1; links[2].is_tcp = 1; |
||||||
|
udp.links = &links[0]; tcp.links = &links[2]; |
||||||
|
udp_entry = queue_entry_new(sizeof(struct conn_queue_entry)); assert(udp_entry); |
||||||
|
tcp_entry = queue_entry_new(sizeof(struct tcp_conn_entry)); assert(tcp_entry); |
||||||
|
((struct conn_queue_entry*)udp_entry->data)->conn = &udp; |
||||||
|
((struct tcp_conn_entry*)tcp_entry->data)->etcp_conn = &tcp; |
||||||
|
memset(&udp_queue, 0, sizeof(udp_queue)); memset(&tcp_queue, 0, sizeof(tcp_queue)); |
||||||
|
udp_queue.head = udp_entry; tcp_queue.head = tcp_entry; |
||||||
|
inst.connections = &udp_queue; inst.tcp_connections = &tcp_queue; |
||||||
|
standby_set_intervals_ms(200, 300, 0); |
||||||
|
standby_init(ua); etcp_keepalive_register(&inst); |
||||||
|
utun_add_activity_cbk(&inst, activity_changed, NULL); |
||||||
|
for (int i = 0; i < 3; i++) etcp_keepalive_link_ready(&links[i]); |
||||||
|
} |
||||||
|
|
||||||
|
/* Отменяет таймеры и подписки перед повторной инициализацией ядра. */ |
||||||
|
static void teardown(void) { |
||||||
|
standby_deinit(); |
||||||
|
for (int i = 0; i < 3; i++) { |
||||||
|
if (links[i].keepalive_timer) uasync_cancel_timeout(ua, links[i].keepalive_timer); |
||||||
|
if (links[i].ka_sleep_announce_timer) uasync_cancel_timeout(ua, links[i].ka_sleep_announce_timer); |
||||||
|
} |
||||||
|
while (inst.activity_cbks) utun_remove_activity_cbk(&inst, inst.activity_cbks->fn, inst.activity_cbks->arg); |
||||||
|
queue_entry_free(udp_entry); queue_entry_free(tcp_entry); |
||||||
|
uasync_poll(ua, 0); |
||||||
|
assert(ua->timer_alloc_count == ua->timer_free_count); |
||||||
|
uasync_destroy(ua, 0); |
||||||
|
} |
||||||
|
|
||||||
|
/* Первый фон при начальном STANDBY, повторы, новые линки, обе причины wake и выход в ACTIVE. */ |
||||||
|
static void check_transitions(void) { |
||||||
|
setup(); |
||||||
|
assert(!standby_is_enabled()); |
||||||
|
for (int i = 0; i < 3; i++) assert(sent[i].count == 1 && sent[i].flags == 0 && links[i].keepalive_timer); |
||||||
|
assert(utun_set_client_activity(&inst, 0) == 0 && standby_is_enabled() && activity_events == 0); |
||||||
|
for (int i = 0; i < 3; i++) { |
||||||
|
assert(links[i].ka_my_sleeping && !links[i].keepalive_timer); |
||||||
|
assert(sent[i].count == 2 && sent[i].flags == KA_FLAG_SLEEP && sent[i].active > 0 && sent[i].sleep == 3); |
||||||
|
} |
||||||
|
uint64_t timers = ua->timer_alloc_count; |
||||||
|
assert(utun_set_client_activity(&inst, 0) == 0 && ua->timer_alloc_count == timers); |
||||||
|
assert(sent[0].count == 2 && sent[1].count == 2 && sent[2].count == 2); |
||||||
|
uasync_poll(ua, -1); assert(standby_get_sleep_tb() > 0); |
||||||
|
for (int i = 0; i < 3; i++) { |
||||||
|
etcp_keepalive_link_ready(&links[i]); |
||||||
|
assert(sent[i].flags == KA_FLAG_SLEEP && sent[i].active == 0 && sent[i].sleep > 0 && !links[i].keepalive_timer); |
||||||
|
} |
||||||
|
assert(links[0].ka_sleep_announce_timer && links[1].ka_sleep_announce_timer && !links[2].ka_sleep_announce_timer); |
||||||
|
timers = ua->timer_alloc_count; wait_after_packets = sent[0].count; |
||||||
|
assert(standby_wait(NULL, waited)); assert(ua->timer_alloc_count == timers); |
||||||
|
fail_phase_timer = 1; |
||||||
|
standby_notify_network_activity(); |
||||||
|
assert(standby_get_sleep_tb() > 0 && wait_calls == 0 && sent[0].count == wait_after_packets); |
||||||
|
fail_phase_timer = 0; |
||||||
|
standby_notify_network_activity(); assert(wait_calls == 1); |
||||||
|
uasync_poll(ua, -1); assert(standby_get_sleep_tb() > 0); |
||||||
|
wait_after_packets = sent[0].count; |
||||||
|
void* cancelled = standby_wait(NULL, waited); assert(cancelled); |
||||||
|
standby_wait_cancel(cancelled); |
||||||
|
assert(standby_wait(NULL, waited)); |
||||||
|
uasync_poll(ua, -1); assert(wait_calls == 2 && standby_get_active_remaining_tb() > 0); |
||||||
|
uasync_poll(ua, -1); assert(standby_get_sleep_tb() > 0); |
||||||
|
wait_after_packets = sent[0].count; assert(standby_wait(NULL, waited)); |
||||||
|
links[0].ka_peer_sleeping = 1; |
||||||
|
assert(utun_set_client_activity(&inst, 1) == 0 && activity_events == 1 && wait_calls == 3); |
||||||
|
for (int i = 0; i < 3; i++) { |
||||||
|
assert(!links[i].ka_my_sleeping && sent[i].flags == 0 && !links[i].ka_sleep_announce_timer); |
||||||
|
} |
||||||
|
assert(!links[0].keepalive_timer && links[1].keepalive_timer && links[2].keepalive_timer); |
||||||
|
timers = ua->timer_alloc_count; unsigned packets = sent[0].count; |
||||||
|
assert(utun_set_client_activity(&inst, 1) == 0 && ua->timer_alloc_count == timers); |
||||||
|
assert(activity_events == 1 && sent[0].count == packets); |
||||||
|
assert(utun_set_client_activity(&inst, 0) == 0 && activity_events == 2); |
||||||
|
teardown(); |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[PASS] first background, repeated commands, UDP/TCP ready, phase-before-work, peer sleep and cleanup"); |
||||||
|
} |
||||||
|
|
||||||
|
/* Ошибка ENTER сохраняет normal; потеря фазового таймера возвращает normal, без зависания сна. */ |
||||||
|
static void check_timer_failures(void) { |
||||||
|
setup(); assert(utun_set_client_activity(&inst, 1) == 0); |
||||||
|
fail_phase_timer = 1; |
||||||
|
assert(utun_set_client_activity(&inst, 0) < 0); |
||||||
|
assert(inst.client_activity == CLIENT_ACTIVITY_ACTIVE && !standby_is_enabled() && activity_events == 1); |
||||||
|
for (int i = 0; i < 3; i++) assert(!links[i].ka_my_sleeping && links[i].keepalive_timer); |
||||||
|
fail_phase_timer = 0; |
||||||
|
assert(utun_set_client_activity(&inst, 0) == 0); |
||||||
|
fail_phase_timer = 1; |
||||||
|
uasync_poll(ua, -1); |
||||||
|
assert(!standby_is_enabled() && inst.client_activity == CLIENT_ACTIVITY_ACTIVE && activity_events == 3); |
||||||
|
for (int i = 0; i < 3; i++) assert(!links[i].ka_my_sleeping && links[i].keepalive_timer); |
||||||
|
fail_phase_timer = 0; teardown(); |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[PASS] timer failure rollback and safe normal fallback"); |
||||||
|
} |
||||||
|
|
||||||
|
int main(void) { |
||||||
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); |
||||||
|
check_transitions(); check_transitions(); check_timer_failures(); |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "PASS: standby transport regression tests"); |
||||||
|
return 0; |
||||||
|
} |
||||||
Loading…
Reference in new issue