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.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile verbatim manager address/deadline paths with bounded host fakes."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import sys
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
source = (ROOT / 'src/wifi_manager.c').read_text()
|
||||
|
||||
def function(name):
|
||||
# Definitions end at a column-zero brace; skip forward declarations.
|
||||
import re
|
||||
match = re.search(r'^static [^\n]+\b' + name + r'\([^;]*?\)\n\{', source, re.M)
|
||||
assert match, name
|
||||
start = match.start()
|
||||
return source[start:source.index('\n}', match.end()) + 2]
|
||||
|
||||
types = source[source.index('typedef enum {'):source.index('static SemaphoreHandle_t')]
|
||||
addresses = source[source.index('typedef struct {\n esp_netif_ip_info_t ip4;'):source.index('/* IDF\'s IPv6 getters')]
|
||||
hostname_request = source[source.index('typedef struct {\n const char *hostname;'):source.index('static esp_err_t set_station_hostname')]
|
||||
names = ['set_station_hostname', 'refresh_station_hostname', 'start_mdns_announcement',
|
||||
'read_station_addresses', 'handle_got_ip', 'clear_station_network_snapshot',
|
||||
'handle_expired_deadlines', 'next_runtime_deadline', 'runtime_wait_ticks',
|
||||
'ip_event_callback']
|
||||
# Check wiring not present in the extracted paths.
|
||||
connect = function('start_next_profile')
|
||||
assert connect.index('refresh_station_hostname()') < connect.index('esp_wifi_connect()')
|
||||
message = function('handle_message')
|
||||
assert 'case MESSAGE_STA_GOT_IP6:' in message
|
||||
assert 'refresh_station_hostname()' in message
|
||||
assert 'handle_got_ip(runtime, message);' in message
|
||||
assert '#define WIFI_MANAGER_QUEUE_LENGTH 16U' in source
|
||||
assert '#define WIFI_MANAGER_TASK_STACK_SIZE 6144U' in source
|
||||
assert 'esp_netif_tcpip_exec(read_station_addresses, &addresses)' in source
|
||||
assert 'esp_netif_is_netif_up' not in function('handle_got_ip')
|
||||
assert 'mdns_service_reconcile()' in function('handle_expired_deadlines')
|
||||
assert 'runtime->radio_started ? runtime->reconcile_deadline' not in source
|
||||
for name in ['handle_sta_disconnected', 'mark_intentional_disconnect', 'stop_radio']:
|
||||
assert 'clear_station_network_snapshot();' in function(name)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='wifi-phase12-') as directory:
|
||||
tmp = Path(directory)
|
||||
(tmp / 'esp_err.h').write_text('#pragma once\ntypedef int esp_err_t;\n#define ESP_OK 0\n#define ESP_FAIL -1\n#define ESP_ERR_INVALID_STATE 1\n#define ESP_ERR_INVALID_ARG 2\n')
|
||||
(tmp / 'esp_wifi_types.h').write_text('#pragma once\ntypedef int wifi_auth_mode_t;\n')
|
||||
# Use the installed IDF public address types/macros, not a reimplementation.
|
||||
sdk_header = Path.home() / '.platformio/packages/framework-espidf/components/esp_netif/include/esp_netif_ip_addr.h'
|
||||
(tmp / 'esp_netif_ip_addr.h').write_text(sdk_header.read_text())
|
||||
(tmp / 'machine').mkdir()
|
||||
(tmp / 'machine/endian.h').write_text('#include <endian.h>\n')
|
||||
(tmp / 'sdkconfig.h').write_text('#define CONFIG_LWIP_IPV6 1\n')
|
||||
(tmp / 'types.inc').write_text(types + addresses + hostname_request)
|
||||
console = (ROOT / 'src/wifi_console.c').read_text()
|
||||
start = console.index('static void print_ipv6_addresses(')
|
||||
(tmp / 'console.inc').write_text(console[start:console.index('\n}', start) + 2])
|
||||
assert 'print_ipv6_addresses(&snapshot);' in console
|
||||
(tmp / 'production.inc').write_text('\n\n'.join(function(name) for name in names))
|
||||
sanitizer = ['-fsanitize=address,undefined', '-fno-omit-frame-pointer'] if '--sanitize' in sys.argv else []
|
||||
subprocess.run(['cc', '-std=gnu17', '-Wall', '-Wextra', '-Werror', *sanitizer,
|
||||
'-I' + str(tmp), '-I' + str(ROOT / 'src'), str(HERE / 'test.c'),
|
||||
'-o', str(tmp / 'test')], check=True, timeout=30)
|
||||
subprocess.run([str(tmp / 'test')], check=True, timeout=20)
|
||||
# Increasing SDK slots must fail compilation rather than truncate/overrun.
|
||||
oversized = (HERE / 'test.c').read_text().replace('#define LWIP_IPV6_NUM_ADDRESSES 3', '#define LWIP_IPV6_NUM_ADDRESSES 4')
|
||||
(tmp / 'oversized.c').write_text(oversized)
|
||||
result = subprocess.run(['cc', '-std=gnu17', '-I' + str(tmp), '-I' + str(ROOT / 'src'),
|
||||
'-fsyntax-only', str(tmp / 'oversized.c')], capture_output=True, text=True)
|
||||
assert result.returncode != 0 and 'IPv6 snapshot capacity' in result.stderr
|
||||
print('IPv6 slot-capacity compile contract PASS')
|
||||
header = (ROOT / 'src/wifi_manager.h').read_text()
|
||||
baseline = header.replace(' uint8_t ipv6_count;\n', '').replace(
|
||||
' wifi_manager_ipv6_address_t ipv6_addresses[WIFI_MANAGER_IPV6_MAX_ADDRESSES];\n', '')
|
||||
(tmp / 'baseline.h').write_text(baseline)
|
||||
(tmp / 'sizes.c').write_text('#include <stdio.h>\n#include "baseline.h"\n'
|
||||
'int main(void) { printf("baseline snapshot=%zu settings=%zu bytes\\n", '
|
||||
'sizeof(wifi_manager_snapshot_t), sizeof(wifi_manager_settings_t)); }\n')
|
||||
subprocess.run(['cc', '-I' + str(tmp), '-I' + str(ROOT / 'src'), str(tmp / 'sizes.c'),
|
||||
'-o', str(tmp / 'sizes')], check=True, timeout=30)
|
||||
subprocess.run([str(tmp / 'sizes')], check=True, timeout=20)
|
||||
Reference in New Issue
Block a user