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