Add IPv6-aware Wi-Fi state, HTTPS/SSH listeners, mDNS service reconciliation, and browser Wi-Fi administration. Include a guarded build-local fix for mDNS 1.12.0 membership handling, focused regression suites, and Phase 12 acceptance documentation.
172 lines
8.2 KiB
C
172 lines
8.2 KiB
C
/* SPDX-License-Identifier: GPL-3.0-only */
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#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;
|
|
}
|