diff --git a/components/esp_wifi/test_apps/wifi_connect/main/CMakeLists.txt b/components/esp_wifi/test_apps/wifi_connect/main/CMakeLists.txt index 4123a65ebfe..96727ced0bd 100644 --- a/components/esp_wifi/test_apps/wifi_connect/main/CMakeLists.txt +++ b/components/esp_wifi/test_apps/wifi_connect/main/CMakeLists.txt @@ -2,6 +2,7 @@ idf_component_register(SRCS "app_main.c" "test_wifi_conn.c" "test_bss_max_idle.c" + "test_connect_passive_channel_hidden_ap.c" PRIV_INCLUDE_DIRS . ${CMAKE_CURRENT_BINARY_DIR} PRIV_REQUIRES cmock test_utils nvs_flash esp_common esp_wifi esp_event diff --git a/components/esp_wifi/test_apps/wifi_connect/main/app_main.c b/components/esp_wifi/test_apps/wifi_connect/main/app_main.c index 88d3ed123f3..13d3839ecee 100644 --- a/components/esp_wifi/test_apps/wifi_connect/main/app_main.c +++ b/components/esp_wifi/test_apps/wifi_connect/main/app_main.c @@ -1,9 +1,11 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "nvs_flash.h" #include "nvs.h" #include "esp_err.h" @@ -12,8 +14,7 @@ #include "esp_heap_caps.h" -// Some resources are lazy allocated in wifi and lwip -#define TEST_MEMORY_LEAK_THRESHOLD (-1896) +#define TEST_MEMORY_LEAK_THRESHOLD (-2100) static size_t before_free_8bit; static size_t before_free_32bit; @@ -21,7 +22,7 @@ static size_t before_free_32bit; static void check_leak(size_t before_free, size_t after_free, const char *type) { ssize_t delta = after_free - before_free; - printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, (unsigned)before_free, (unsigned)after_free, (int)delta); TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); } @@ -36,8 +37,10 @@ void setUp(void) void tearDown(void) { + ESP_ERROR_CHECK(esp_wifi_stop()); + vTaskDelay(pdMS_TO_TICKS(100)); ESP_ERROR_CHECK(esp_wifi_deinit()); - vTaskDelay(500 / portTICK_PERIOD_MS); + vTaskDelay(pdMS_TO_TICKS(500)); size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); check_leak(before_free_8bit, after_free_8bit, "8BIT"); diff --git a/components/esp_wifi/test_apps/wifi_connect/main/test_connect_passive_channel_hidden_ap.c b/components/esp_wifi/test_apps/wifi_connect/main/test_connect_passive_channel_hidden_ap.c new file mode 100644 index 00000000000..b60118ddd37 --- /dev/null +++ b/components/esp_wifi/test_apps/wifi_connect/main/test_connect_passive_channel_hidden_ap.c @@ -0,0 +1,253 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/* + * Tests station association (L2 only; no DHCP / GOT_IP) to a hidden SoftAP when passive-hidden-AP support is enabled. + * + * Not in default wifi_connect builds: enable CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT only via + * sdkconfig.ci.passive_hidden_ap (pytest config "passive_hidden_ap"), or merge that file in + * SDKCONFIG_DEFAULTS for local builds. + * + * Wi-Fi remote: CONFIG_WIFI_RMT_PASSIVE_HIDDEN_AP_SUPPORT maps to the same option. + */ + +#include "sdkconfig.h" + +#if CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT + +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_wifi.h" +#include "esp_wifi_default.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "test_utils.h" +#include "freertos/event_groups.h" +#include "soc/soc_caps.h" + +#ifndef TEST_SUFFIX_STR +#define TEST_SUFFIX_STR "_0000" +#endif + +#define TEST_DEFAULT_SSID "SSID_" CONFIG_IDF_TARGET TEST_SUFFIX_STR +#define TEST_DEFAULT_PWD "PASS_" CONFIG_IDF_TARGET TEST_SUFFIX_STR + +#if CONFIG_SOC_WIFI_SUPPORT_5G +/* DFS channel: passive scan is required by regulatory rules on many domains. */ +#define TEST_PASSIVE_HIDDEN_AP_CHANNEL (52) +#else +/* 2.4 GHz passive scan scenario: channel 12 with CN regulatory (see apply_country_auto). */ +#define TEST_PASSIVE_HIDDEN_AP_CHANNEL (12) +#endif + +#define CONNECT_TIMEOUT_MS (30000) +#define MAXIMUM_RETRY (5) + +#define WIFI_DISCONNECT_EVENT (1 << 1) +#define WIFI_STA_CONNECTED (1 << 2) +#define WIFI_AP_STA_CONNECTED (1 << 3) + +static const char *TAG = "test_passive_hidden_ap"; + +static EventGroupHandle_t s_wifi_events; +static esp_netif_t *s_sta_netif; +static esp_netif_t *s_ap_netif; +static int s_retry_num; +static bool s_sta_conn_first; + +static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + switch (event_id) { + case WIFI_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STACONNECTED"); + if (s_wifi_events) { + xEventGroupSetBits(s_wifi_events, WIFI_AP_STA_CONNECTED); + } + break; + case WIFI_EVENT_STA_CONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_STA_CONNECTED"); + if (s_wifi_events) { + xEventGroupSetBits(s_wifi_events, WIFI_STA_CONNECTED); + s_sta_conn_first = true; + } + break; + case WIFI_EVENT_STA_DISCONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED"); + if (s_wifi_events) { + if ((s_retry_num < MAXIMUM_RETRY) && !s_sta_conn_first) { + s_retry_num++; + ESP_LOGI(TAG, "retry connect to hidden passive-channel AP (%d/%d)", s_retry_num, MAXIMUM_RETRY); + esp_wifi_connect(); + } else { + xEventGroupSetBits(s_wifi_events, WIFI_DISCONNECT_EVENT); + } + } + break; + default: + break; + } +} + +static esp_err_t event_init_sta_side(void) +{ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); + s_sta_netif = esp_netif_create_default_wifi_sta(); + return ESP_OK; +} + +static esp_err_t event_deinit_sta_side(void) +{ + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler)); + esp_netif_destroy_default_wifi(s_sta_netif); + s_sta_netif = NULL; + ESP_ERROR_CHECK(esp_event_loop_delete_default()); + return ESP_OK; +} + +static esp_err_t event_init_ap_side(void) +{ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); + s_ap_netif = esp_netif_create_default_wifi_ap(); + return ESP_OK; +} + +static esp_err_t event_deinit_ap_side(void) +{ + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler)); + esp_netif_destroy_default_wifi(s_ap_netif); + s_ap_netif = NULL; + ESP_ERROR_CHECK(esp_event_loop_delete_default()); + return ESP_OK; +} + +static void apply_country_auto(void) +{ + wifi_country_t country = {0}; + memcpy(country.cc, "CN", 2); + country.schan = 1; + country.nchan = 13; + country.policy = WIFI_COUNTRY_POLICY_AUTO; +#if CONFIG_SOC_WIFI_SUPPORT_5G + country.wifi_5g_channel_mask = 0; +#endif + TEST_ESP_OK(esp_wifi_set_country(&country)); +} + +static void start_hidden_softap_on_passive_channel(void) +{ + wifi_config_t w_config = { + .ap.ssid = TEST_DEFAULT_SSID, + .ap.password = TEST_DEFAULT_PWD, + .ap.ssid_len = strlen(TEST_DEFAULT_SSID), + .ap.channel = TEST_PASSIVE_HIDDEN_AP_CHANNEL, + .ap.authmode = WIFI_AUTH_WPA2_PSK, + .ap.ssid_hidden = true, + .ap.max_connection = 4, + .ap.beacon_interval = 100, + }; + + event_init_ap_side(); + if (s_wifi_events == NULL) { + s_wifi_events = xEventGroupCreate(); + } + xEventGroupClearBits(s_wifi_events, 0x00ffffff); + + apply_country_auto(); + TEST_ESP_OK(esp_wifi_set_mode(WIFI_MODE_AP)); + TEST_ESP_OK(esp_wifi_set_config(WIFI_IF_AP, &w_config)); + TEST_ESP_OK(esp_wifi_start()); + ESP_LOGI(TAG, "hidden softap on channel %u, ssid (hidden): %s", (unsigned)TEST_PASSIVE_HIDDEN_AP_CHANNEL, TEST_DEFAULT_SSID); +} + +static void start_sta(void) +{ + event_init_sta_side(); + if (s_wifi_events == NULL) { + s_wifi_events = xEventGroupCreate(); + } + xEventGroupClearBits(s_wifi_events, 0x00ffffff); + + apply_country_auto(); + TEST_ESP_OK(esp_wifi_set_mode(WIFI_MODE_STA)); + TEST_ESP_OK(esp_wifi_start()); +} + +static void stop_wifi_sta_side(void) +{ + TEST_ESP_OK(esp_wifi_stop()); + event_deinit_sta_side(); + if (s_wifi_events) { + vEventGroupDelete(s_wifi_events); + s_wifi_events = NULL; + } + vTaskDelay(500 / portTICK_PERIOD_MS); +} + +static void stop_wifi_ap_side(void) +{ + TEST_ESP_OK(esp_wifi_stop()); + event_deinit_ap_side(); + if (s_wifi_events) { + vEventGroupDelete(s_wifi_events); + s_wifi_events = NULL; + } + vTaskDelay(500 / portTICK_PERIOD_MS); +} + +static void sta_connect_hidden_on_passive_channel(void) +{ + wifi_config_t w_config = { 0 }; + strncpy((char *)w_config.sta.ssid, TEST_DEFAULT_SSID, sizeof(w_config.sta.ssid)); + strncpy((char *)w_config.sta.password, TEST_DEFAULT_PWD, sizeof(w_config.sta.password)); + w_config.sta.channel = TEST_PASSIVE_HIDDEN_AP_CHANNEL; + + TEST_ESP_OK(esp_wifi_set_config(WIFI_IF_STA, &w_config)); + s_retry_num = 0; + s_sta_conn_first = false; + TEST_ESP_OK(esp_wifi_connect()); + ESP_LOGI(TAG, "esp_wifi_connect hidden AP, channel hint %u", (unsigned)TEST_PASSIVE_HIDDEN_AP_CHANNEL); + + EventBits_t bits = xEventGroupWaitBits(s_wifi_events, + WIFI_STA_CONNECTED | WIFI_DISCONNECT_EVENT, + pdTRUE, + pdFALSE, + pdMS_TO_TICKS(CONNECT_TIMEOUT_MS)); + TEST_ASSERT_MESSAGE(bits & WIFI_STA_CONNECTED, "STA did not connect (hidden passive-channel path, L2 only)"); +} + +static void test_passive_hidden_channel_sta(void) +{ + start_sta(); + vTaskDelay(pdMS_TO_TICKS(1000)); + sta_connect_hidden_on_passive_channel(); + stop_wifi_sta_side(); +} + +static void test_passive_hidden_channel_softap(void) +{ + start_hidden_softap_on_passive_channel(); + + EventBits_t bits = xEventGroupWaitBits(s_wifi_events, + WIFI_AP_STA_CONNECTED, + pdFALSE, + pdFALSE, + pdMS_TO_TICKS(CONNECT_TIMEOUT_MS)); + TEST_ASSERT_MESSAGE(bits & WIFI_AP_STA_CONNECTED, "No station connected to hidden softap in time"); + + stop_wifi_ap_side(); +} + +TEST_CASE_MULTIPLE_DEVICES("connect passive channel hidden softap", + "[wifi][passive_hidden_ap]", + test_passive_hidden_channel_sta, + test_passive_hidden_channel_softap); + +#endif /* CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT */ diff --git a/components/esp_wifi/test_apps/wifi_connect/pytest_wifi_connect.py b/components/esp_wifi/test_apps/wifi_connect/pytest_wifi_connect.py index dc771bb3e4f..8982c968d05 100644 --- a/components/esp_wifi/test_apps/wifi_connect/pytest_wifi_connect.py +++ b/components/esp_wifi/test_apps/wifi_connect/pytest_wifi_connect.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded_idf.unity_tester import CaseTester @@ -55,3 +55,32 @@ def test_wifi_connect_cases_esp32c2_rev2_xtal26m(case_tester: CaseTester) -> Non @idf_parametrize('target', ['esp32c3'], indirect=['target']) def test_wifi_connect_cases_esp32c3_rev1(case_tester: CaseTester) -> None: case_tester.run_all_cases() + + +# Must match TEST_CASE_MULTIPLE_DEVICES name string in test_connect_passive_channel_hidden_ap.c (menu line quoted text). +_PASSIVE_HIDDEN_AP_CASE = 'connect passive channel hidden softap' + + +@pytest.mark.two_duts +@pytest.mark.parametrize( + 'count, config', + [ + (2, 'passive_hidden_ap'), + ], + indirect=True, +) +@idf_parametrize( + 'target', + ['esp32', 'esp32c3', 'esp32s2', 'esp32s3', 'esp32c5', 'esp32c6', 'esp32c61'], + indirect=['target'], +) +def test_wifi_connect_passive_hidden_ap(case_tester: CaseTester) -> None: + """Firmware uses sdkconfig.ci.passive_hidden_ap; runs only the passive hidden AP multi-DUT case.""" + for case in case_tester.test_menu: + if case.name == _PASSIVE_HIDDEN_AP_CASE: + case_tester.run_multi_dev_case(case=case, reset=True) + return + pytest.fail( + f'Case {_PASSIVE_HIDDEN_AP_CASE!r} not in menu; build must merge sdkconfig.ci.passive_hidden_ap ' + '(CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT).' + ) diff --git a/components/esp_wifi/test_apps/wifi_connect/sdkconfig.ci.passive_hidden_ap b/components/esp_wifi/test_apps/wifi_connect/sdkconfig.ci.passive_hidden_ap new file mode 100644 index 00000000000..bfb9ca3d68d --- /dev/null +++ b/components/esp_wifi/test_apps/wifi_connect/sdkconfig.ci.passive_hidden_ap @@ -0,0 +1,3 @@ +# Merged when pytest uses config "passive_hidden_ap" (or set SDKCONFIG_DEFAULTS to include this file). +# Enables the hidden passive-channel AP test and WIFI_INIT passive-hidden feature bit only for that build. +CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT=y diff --git a/components/esp_wifi/test_apps/wifi_connect/sdkconfig.defaults b/components/esp_wifi/test_apps/wifi_connect/sdkconfig.defaults index 4f3584cd25a..f0d6ab29a51 100644 --- a/components/esp_wifi/test_apps/wifi_connect/sdkconfig.defaults +++ b/components/esp_wifi/test_apps/wifi_connect/sdkconfig.defaults @@ -1,3 +1,6 @@ +# Passive hidden AP test: off by default. Merge sdkconfig.ci.passive_hidden_ap when building/running that case only +# (see pytest test_wifi_connect_passive_hidden_ap). Example: +# idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.passive_hidden_ap" set-target esp32c3 build # ignore task watchdog triggered by unity_run_menu CONFIG_ESP_TASK_WDT_EN=n