Files
ESP32_Serial_Swiss_Army_Knife/tests/mdns_membership/test.c
T
Commander1024 8902b25d78 Complete Phase 12 dual-stack networking
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.
2026-09-20 22:35:34 +02:00

175 lines
6.0 KiB
C

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
typedef int esp_err_t;
typedef int mdns_if_t;
typedef int mdns_ip_protocol_t;
enum { ESP_OK, ESP_ERR_NO_MEM, ESP_ERR_INVALID_STATE };
enum { MDNS_IP_PROTOCOL_V4, MDNS_IP_PROTOCOL_V6, MDNS_IP_PROTOCOL_MAX };
#define MDNS_MAX_INTERFACES 2
#define MDNS_SERVICE_PORT 5353
struct udp_pcb { int mcast_ttl, remote_port, remote_ip; };
static int any_address;
#define IP_ANY_TYPE (&any_address)
#define ip_addr_copy(to, from) ((to) = (from))
static void receive(void) {}
static struct udp_pcb storage;
static bool allocated, fail_alloc, fail_bind, fail_join, fail_leave;
static int allocations, removals, joins[2][2], leaves[2][2], references[2][2];
static struct udp_pcb *udp_new(void)
{
if (fail_alloc) return NULL;
assert(!allocated);
allocated = true;
allocations++;
return &storage;
}
static int udp_bind(struct udp_pcb *pcb, const int *address, int port)
{
assert(pcb == &storage && address == IP_ANY_TYPE && port == 5353);
return fail_bind;
}
static void udp_remove(struct udp_pcb *pcb)
{
assert(pcb == &storage && allocated);
allocated = false;
removals++;
}
static void udp_recv(struct udp_pcb *pcb, void (*callback)(void), void *arg)
{ assert(pcb == &storage); (void)callback; (void)arg; }
static void udp_disconnect(struct udp_pcb *pcb) { assert(pcb == &storage); }
static esp_err_t join_group(mdns_if_t interface, mdns_ip_protocol_t family, bool join)
{
if (join) {
joins[interface][family]++;
if (fail_join) return ESP_ERR_INVALID_STATE;
references[interface][family]++;
/* A real lwIP group has a bounded use count: never accumulate it. */
assert(references[interface][family] == 1);
} else {
leaves[interface][family]++;
if (fail_leave) return ESP_ERR_INVALID_STATE;
assert(references[interface][family] == 1);
references[interface][family]--;
}
return ESP_OK;
}
#include "actual_functions.inc"
static void reset(void)
{
assert(!allocated && s_pcb_main == NULL);
memset(s_interfaces, 0, sizeof(s_interfaces));
memset(joins, 0, sizeof(joins));
memset(leaves, 0, sizeof(leaves));
memset(references, 0, sizeof(references));
allocations = removals = 0;
fail_alloc = fail_bind = fail_join = fail_leave = false;
}
static void check(int interface, int family, bool active)
{
assert(!!mdns_priv_if_ready(interface, family) == active);
assert(references[interface][family] == (int)active);
assert(joins[interface][family] - leaves[interface][family] == (int)active);
}
static void transitions(int family)
{
reset();
int other = 1 - family;
assert(pcb_if_init(0, family) == ESP_OK);
assert(pcb_if_init(0, other) == ESP_OK);
for (int i = 0; i < 512; i++) {
pcb_if_deinit(0, family);
check(0, family, false);
check(0, other, true);
assert(s_interfaces[0].ready && allocated && removals == 0);
pcb_if_deinit(0, family); /* Already-disabled family must not leave again. */
check(0, family, false);
assert(pcb_if_init(0, family) == ESP_OK);
check(0, family, true);
int before = joins[0][family];
assert(pcb_if_init(0, family) == ESP_ERR_INVALID_STATE);
assert(joins[0][family] == before);
}
pcb_if_deinit(0, other);
assert(s_interfaces[0].ready && allocated);
pcb_if_deinit(0, family);
assert(!s_interfaces[0].ready && !allocated);
assert(allocations == 1 && removals == 1);
pcb_if_deinit(0, family);
pcb_if_deinit(0, other);
check(0, family, false);
check(0, other, false);
}
static void failures(int family)
{
reset();
pcb_if_deinit(0, family);
assert(leaves[0][family] == 0 && removals == 0);
fail_join = true;
assert(pcb_if_init(0, family) == ESP_ERR_INVALID_STATE);
assert(!s_interfaces[0].ready && s_interfaces[0].proto == 0);
assert(allocations == 0 && leaves[0][family] == 0);
fail_join = false;
for (int mode = 0; mode < 2; mode++) {
fail_alloc = mode == 0;
fail_bind = mode == 1;
for (int i = 0; i < 512; i++) {
int before = leaves[0][family];
assert(pcb_if_init(0, family) == (fail_alloc ? ESP_ERR_NO_MEM : ESP_ERR_INVALID_STATE));
assert(leaves[0][family] == before + 1);
assert(references[0][family] == 0 && !allocated && !s_pcb_main);
assert(!s_interfaces[0].ready && s_interfaces[0].proto == 0);
pcb_if_deinit(0, family);
assert(leaves[0][family] == before + 1);
}
}
fail_alloc = fail_bind = false;
assert(pcb_if_init(0, family) == ESP_OK);
pcb_if_deinit(0, family);
assert(!allocated && references[0][family] == 0);
/* Preserve upstream best-effort leave semantics when netif has gone down. */
reset();
assert(pcb_if_init(0, family) == ESP_OK);
fail_leave = true;
pcb_if_deinit(0, family);
pcb_if_deinit(0, family);
assert(leaves[0][family] == 1 && !s_interfaces[0].ready && !allocated);
references[0][family] = 0; /* Model netif teardown clearing its memberships. */
reset();
fail_alloc = fail_leave = true;
assert(pcb_if_init(0, family) == ESP_ERR_NO_MEM);
assert(leaves[0][family] == 1 && !s_interfaces[0].ready && !allocated);
references[0][family] = 0;
}
static void shared_pcb(int family)
{
reset();
assert(pcb_if_init(1, 1 - family) == ESP_OK);
for (int i = 0; i < 512; i++) {
assert(pcb_if_init(0, family) == ESP_OK);
pcb_if_deinit(0, family);
pcb_if_deinit(0, family);
check(0, family, false);
check(1, 1 - family, true);
assert(!s_interfaces[0].ready && allocated && removals == 0);
}
assert(allocations == 1);
pcb_if_deinit(1, 1 - family);
assert(!allocated && removals == 1);
}
int main(void)
{
for (int family = 0; family < 2; family++) {
transitions(family);
failures(family);
shared_pcb(family);
}
puts("PASS: actual mDNS functions, 512 cycles per family/scenario, failure unwind and shared PCB");
return 0;
}