/* Проверка режима и фаз standby на реальных keepalive-пакетах до шифрования. * Управляемая отправка исключает сеть; отказ таймера проверяет сохранение рабочего режима. */ #include #include #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; }