/* SPDX-License-Identifier: GPL-3.0-only */ #include #include #include #include "mdns_service.c" static int held, inits, frees, names, adds, removes; static int fail_init, fail_name, fail_instance, fail_record; static bool https, ssh; static char hostname[64]; static int critical, tcpip, actions, enables, fail_action, drop_action; static bool up = true, valid6 = true, present = true, zero6; static uint32_t ipv4 = 1; static unsigned ready; static int64_t clock_us; static esp_netif_t sta; void fake_enter(portMUX_TYPE *m) { (void)m; assert(!critical); critical = 1; } void fake_exit(portMUX_TYPE *m) { (void)m; assert(critical); critical = 0; } int64_t esp_timer_get_time(void) { assert(!held && !critical); return clock_us; } esp_netif_t *esp_netif_get_handle_from_ifkey(const char *key) { assert(!held && !critical && !strcmp(key, "WIFI_STA_DEF")); return present ? &sta : NULL; } bool esp_netif_is_netif_up(esp_netif_t *n) { assert(tcpip && n == &sta); return up; } esp_err_t esp_netif_get_ip_info(esp_netif_t *n, esp_netif_ip_info_t *ip) { assert(tcpip && n == &sta); ip->ip.addr = ipv4; return ESP_OK; } int esp_netif_get_all_ip6(esp_netif_t *n, esp_ip6_addr_t *ip) { assert(tcpip && n == &sta); memset(ip, 0, sizeof(*ip)); ip->addr[0] = zero6 ? 0 : 0xfe80; return valid6 ? 1 : 0; } esp_err_t esp_netif_tcpip_exec(esp_err_t (*fn)(void *), void *arg) { assert(!held && !critical && !tcpip); tcpip = 1; esp_err_t error = fn(arg); tcpip = 0; return error; } esp_err_t mdns_netif_action(esp_netif_t *n, mdns_event_actions_t action) { assert(!held && !critical && !tcpip && n == &sta); ++actions; if (fail_action) return ESP_ERR_NO_MEM; if (drop_action) return ESP_OK; /* Actual upstream full-queue behavior. */ if (action & MDNS_EVENT_ENABLE_IP4) { ready |= 1; ++enables; } if (action & MDNS_EVENT_ENABLE_IP6) { ready |= 2; ++enables; } if (action & MDNS_EVENT_DISABLE_IP4) ready &= ~1U; if (action & MDNS_EVENT_DISABLE_IP6) ready &= ~2U; return ESP_OK; } static void test_families(void) { assert(ready == 3); int before = actions; for (int i = 0; i < 20; ++i) assert(mdns_service_reconcile() == ESP_OK); assert(actions == before); /* Healthy dual-stack poll has no action. */ ipv4 = 0; /* Lost IPv4, surviving IPv6: A readiness must be removed. */ drop_action = 1; assert(mdns_service_reconcile() == ESP_OK && ready == 3); drop_action = 0; assert(mdns_service_reconcile() == ESP_OK && ready == 2); before = enables; for (int i = 0; i < 20; ++i) assert(mdns_service_reconcile() == ESP_OK); assert(enables == before); /* Disables do not restart the healthy family. */ zero6 = true; /* Defensive rejection even if an API supplied a zero entry. */ assert(mdns_service_reconcile() == ESP_OK && ready == 0); zero6 = false; valid6 = false; assert(mdns_service_reconcile() == ESP_OK && ready == 0); valid6 = true; /* Missed GOT_IP6 and silently lost repair submission. */ drop_action = 1; assert(mdns_service_reconcile() == ESP_OK && ready == 0); drop_action = 0; clock_us += MDNS_FAMILY_REPAIR_US; assert(mdns_service_reconcile() == ESP_OK && ready == 2); ipv4 = 1; fail_action = 1; assert(mdns_service_reconcile() == ESP_ERR_NO_MEM && ready == 2); fail_action = 0; assert(mdns_service_reconcile() == ESP_OK && ready == 3); ready = 0; /* Late upstream disconnect action after the last sample. */ clock_us += MDNS_FAMILY_REPAIR_US; assert(mdns_service_reconcile() == ESP_OK && ready == 3); up = false; assert(mdns_service_reconcile() == ESP_OK && ready == 0); up = true; assert(mdns_service_reconcile() == ESP_OK && ready == 3); mdns_service_stop(); assert(ready == 0); /* Even if stale nonzero addresses remain in netif. */ assert(mdns_service_start() == ESP_OK && ready == 3); present = false; assert(mdns_service_reconcile() == ESP_ERR_INVALID_STATE); present = true; assert(mdns_service_reconcile() == ESP_OK); } SemaphoreHandle_t xSemaphoreCreateMutex(void) { return &held; } int xSemaphoreTake(SemaphoreHandle_t m, int wait) { (void)m; assert(!critical); if (held) { assert(!wait); return 0; } held = 1; return pdTRUE; } int xSemaphoreGive(SemaphoreHandle_t m) { (void)m; assert(held); held = 0; return 1; } esp_err_t mdns_config_validate(const mdns_config_t *c) { return c && c->suffix_len ? ESP_OK : ESP_ERR_INVALID_ARG; } void mdns_config_defaults(mdns_config_t *c) { memset(c, 0, sizeof(*c)); strcpy(c->suffix, "default"); c->suffix_len = 7; } esp_err_t mdns_config_load(mdns_config_t *c, bool *stored) { mdns_config_defaults(c); *stored = false; return ESP_OK; } esp_err_t mdns_config_save(const mdns_config_t *c) { (void)c; return ESP_OK; } esp_err_t mdns_init(void) { assert(!held && !critical); ++inits; return fail_init ? ESP_ERR_NO_MEM : ESP_OK; } void mdns_free(void) { assert(!held && !critical); ++frees; } esp_err_t mdns_hostname_set(const char *n) { assert(!held && !critical); ++names; if (fail_name) return ESP_ERR_NO_MEM; strcpy(hostname, n); return ESP_OK; } esp_err_t mdns_instance_name_set(const char *n) { assert(!held && !critical && n); return fail_instance ? ESP_ERR_NO_MEM : ESP_OK; } esp_err_t mdns_service_add(const char *instance, const char *type, const char *proto, uint16_t port, void *txt, size_t count) { assert(!held && !critical && !instance && !txt && !count && !strcmp(proto, "_tcp")); ++adds; if (fail_record) return ESP_ERR_NO_MEM; bool *record = !strcmp(type, "_https") ? &https : &ssh; assert(port == (record == &https ? 443 : 22)); assert(!*record); *record = true; return ESP_OK; } esp_err_t mdns_service_remove(const char *type, const char *proto) { assert(!held && !critical && !strcmp(proto, "_tcp")); ++removes; if (fail_record) return ESP_ERR_NO_MEM; bool *record = !strcmp(type, "_https") ? &https : &ssh; assert(*record); *record = false; return ESP_OK; } int main(int argc, char **argv) { assert(argc == 2); fail_init = !strcmp(argv[1], "init-failure"); fail_name = !strcmp(argv[1], "hostname-failure"); fail_instance = !strcmp(argv[1], "instance-failure"); mdns_service_set_https_available(true); mdns_service_set_ssh_available(false); assert(!inits && !adds); assert(mdns_service_reconcile() == ESP_ERR_INVALID_STATE); mdns_config_t c; mdns_config_defaults(&c); assert(mdns_service_init(&c) == ESP_OK); assert(mdns_service_reconcile() == ESP_OK && !inits); if (fail_init || fail_name || fail_instance) { assert(mdns_service_start() == ESP_ERR_NO_MEM); assert(frees == (fail_init ? 0 : 1)); fail_init = fail_name = fail_instance = 0; for (int i = 0; i < 10; ++i) { assert(mdns_service_start() == ESP_ERR_NO_MEM); assert(mdns_service_reconcile() == ESP_ERR_NO_MEM); assert(mdns_service_reannounce() == ESP_ERR_NO_MEM); mdns_service_stop(); } assert(inits == 1 && !adds); } else { assert(mdns_service_start() == ESP_OK && https && !ssh); for (int i = 0; i < 10; ++i) assert(mdns_service_start() == ESP_OK); assert(inits == 1 && names == 1 && adds == 1); test_families(); held = 1; /* Notifications must never acquire the service mutex. */ mdns_service_set_https_available(false); mdns_service_set_ssh_available(true); held = 0; fail_record = 1; assert(mdns_service_reconcile() == ESP_ERR_NO_MEM && https && !ssh); fail_record = 0; assert(mdns_service_reconcile() == ESP_OK && !https && ssh); mdns_service_stop(); mdns_service_set_ssh_available(false); assert(mdns_service_reconcile() == ESP_OK && !ssh); strcpy(c.suffix, "offline"); c.suffix_len = 7; assert(mdns_service_set_config(&c) == ESP_OK); fail_name = 1; assert(mdns_service_start() == ESP_ERR_NO_MEM); fail_name = 0; assert(mdns_service_start() == ESP_OK && !strcmp(hostname, "sak-offline")); mdns_service_set_https_available(true); mdns_service_set_https_available(false); int before = adds; assert(mdns_service_reconcile() == ESP_OK && adds == before); assert(inits == 1 && !frees); } printf("PASS %s\n", argv[1]); return 0; }