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 @@
|
||||
|
||||
static admin_ssh_console_token_t token = {
|
||||
.slot_index=0, .session_id=7, .slot_generation=1,
|
||||
.transport=ADMIN_CONSOLE_TRANSPORT_WEB,
|
||||
};
|
||||
static user_principal_t admin = {.role=USER_ROLE_ADMIN};
|
||||
static bool live=true;
|
||||
static unsigned scenario;
|
||||
static bool is_current(const admin_ssh_console_token_t *t, const user_principal_t *p)
|
||||
{ assert(!lock_depth && t->transport==ADMIN_CONSOLE_TRANSPORT_WEB && p->role==USER_ROLE_ADMIN); return live; }
|
||||
static bool drained(const admin_ssh_console_token_t *t) { (void)t; return true; }
|
||||
static esp_err_t perform(const admin_ssh_console_token_t *t,
|
||||
admin_ssh_deferred_action_type_t action, uint32_t arg)
|
||||
{ (void)t; (void)action; (void)arg; assert(false); return ESP_FAIL; }
|
||||
static const admin_console_owner_t owner = {
|
||||
.is_current=is_current, .drained=drained, .perform=perform,
|
||||
};
|
||||
static void reply(void)
|
||||
{
|
||||
const char *text=scenario==1 ? "wifi-secret\x03" : scenario==5 ? "short\r" : "wifi-secret\t\r";
|
||||
size_t used=0;
|
||||
assert(s_sessions[0].prompt_hidden);
|
||||
assert(admin_ssh_console_feed_input(&token,(const uint8_t *)text,strlen(text),&used));
|
||||
assert(used==strlen(text));
|
||||
if (scenario==2) live=false;
|
||||
if (scenario==3) principal_current=false;
|
||||
if (scenario==4) admin_ssh_console_close(&token);
|
||||
}
|
||||
static void run_secret(void)
|
||||
{
|
||||
assert(admin_ssh_console_dispatch_is_web());
|
||||
uint8_t greeting[4096]; size_t greeting_length;
|
||||
assert(admin_ssh_console_read_output(&token,greeting,sizeof(greeting),&greeting_length)==ESP_OK);
|
||||
unsigned before=applies, wiped=wipes;
|
||||
uint8_t history[sizeof(s_sessions[0].history)];
|
||||
memcpy(history,s_sessions[0].history,sizeof(history));
|
||||
int result=(scenario%2 || scenario==6) ? set_ap_secret() : set_profile_secret("0");
|
||||
bool success=scenario==0 || scenario==6;
|
||||
assert(result==(success ? 0 : 1));
|
||||
assert(applies==before+success);
|
||||
assert(wipes>=wiped+2); /* Hidden local input and PSK-bearing candidate. */
|
||||
for (size_t i=0;i<sizeof(s_sessions[0].prompt_input);++i)
|
||||
assert(!s_sessions[0].prompt_input[i]);
|
||||
if (scenario<2 || scenario>=5)
|
||||
assert(!memcmp(history,s_sessions[0].history,sizeof(history)));
|
||||
uint8_t output[4097]={0}; size_t n=0;
|
||||
if (s_sessions[0].active) {
|
||||
assert(admin_ssh_console_read_output(&token,output,sizeof(output)-1,&n)==ESP_OK);
|
||||
assert(!strstr((char *)output,"wifi-secret"));
|
||||
assert(!strstr((char *)output,"short"));
|
||||
assert(!strstr((char *)output,"help")); /* Tab never invokes completion in a prompt. */
|
||||
}
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
assert(admin_ssh_console_init()==ESP_OK);
|
||||
assert(admin_ssh_console_start_uart_frontend()==ESP_OK);
|
||||
user_principal_t ordinary={.role=USER_ROLE_USER};
|
||||
assert(admin_ssh_console_open_owned(&token,&ordinary,&owner)!=ESP_OK);
|
||||
working.profiles[0].ssid_len=1;
|
||||
working.profiles[0].ssid[0]='x';
|
||||
for (scenario=0;scenario<7;++scenario) {
|
||||
live=true; principal_current=true; ++token.slot_generation;
|
||||
assert(admin_ssh_console_open_owned(&token,&admin,&owner)==ESP_OK);
|
||||
const char *line=(scenario%2 || scenario==6) ? "wifi ap secret\r" : "wifi profile secret 0\r";
|
||||
size_t used=0;
|
||||
assert(admin_ssh_console_feed_input(&token,(const uint8_t *)line,strlen(line),&used));
|
||||
unsigned before=runs;
|
||||
prompt_hook=reply; command_hook=run_secret;
|
||||
if (!setjmp(loop_done)) worker_task(NULL);
|
||||
assert(runs==before+1);
|
||||
admin_ssh_console_close(&token);
|
||||
admin_session_t empty={0};
|
||||
assert(!memcmp(&empty,&s_sessions[0],sizeof(empty)));
|
||||
}
|
||||
assert(applies==2 && working.profiles[0].psk_len==11);
|
||||
assert(working.ap_psk_len==11 && !memcmp(working.ap_psk,"wifi-secret",11));
|
||||
assert(!memcmp(working.profiles[0].psk,"wifi-secret",11));
|
||||
puts("PASS: browser Wi-Fi policy-to-dispatch, actual profile/AP secret handlers and shared IO; hidden input/history/Tab, cancel, owner/account revocation, close, short input and wiping; ordinary role denied");
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Actual Wi-Fi secret handlers through shared browser prompt IO; host fakes only."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
IDF = Path(os.environ.get("IDF_PATH", str(Path.home() / ".platformio/packages/framework-espidf")))
|
||||
|
||||
def stripped(path):
|
||||
return "\n".join(line for line in (ROOT / path).read_text().splitlines()
|
||||
if not line.startswith(("#include", "#pragma once")))
|
||||
|
||||
def function(source, signature):
|
||||
start = source.index(signature)
|
||||
return source[start:source.index("\n}", start) + 2] + "\n"
|
||||
|
||||
wifi = (ROOT / "src/wifi_console.c").read_text()
|
||||
unit = (ROOT / "tests/admin_console_boundary/fakes.h").read_text() + "\n"
|
||||
unit += stripped("src/admin_ssh_console.h") + "\n" + stripped("src/admin_ssh_console.c") + "\n"
|
||||
unit += stripped("src/wifi_config.h") + r'''
|
||||
#include <stdlib.h>
|
||||
#define ESP_ERR_INVALID_SIZE 100
|
||||
#define UART_NUM_0 0
|
||||
#define WIFI_CONSOLE_SECRET_CAPACITY WIFI_CONFIG_PSK_MAX_LEN
|
||||
static esp_err_t uart_flush_input(int port) { (void)port; assert(false); return ESP_FAIL; }
|
||||
static int uart_read_bytes(int port, void *out, size_t n, unsigned wait)
|
||||
{ (void)port; (void)out; (void)n; (void)wait; assert(false); return -1; }
|
||||
static wifi_app_config_t working;
|
||||
static unsigned applies, wipes;
|
||||
void wifi_config_secure_wipe(void *p, size_t n) {
|
||||
secure_wipe(p,n); ++wipes;
|
||||
for (size_t i=0;i<n;++i) assert(!((uint8_t *)p)[i]);
|
||||
}
|
||||
static esp_err_t wifi_manager_get_working_config(wifi_app_config_t *out)
|
||||
{ *out=working; return ESP_OK; }
|
||||
static esp_err_t wifi_manager_apply_working_config(const wifi_app_config_t *in)
|
||||
{ working=*in; ++applies; return ESP_OK; }
|
||||
'''
|
||||
unit += stripped("src/console_input.c") + "\n"
|
||||
for signature in ("static bool parse_u32(", "static bool parse_slot(",
|
||||
"static esp_err_t apply_candidate(", "static esp_err_t read_secret_no_echo(",
|
||||
"static int set_profile_secret(", "static int set_ap_secret("):
|
||||
unit += function(wifi, signature)
|
||||
unit += (ROOT / "tests/admin_console_boundary/wifi.c").read_text()
|
||||
with tempfile.TemporaryDirectory(prefix="browser-wifi-prompts-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(unit)
|
||||
subprocess.run(["cc", "-std=c11", "-Wall", "-Wextra", "-Werror", "-Wno-unused-variable", str(path / "test.c"),
|
||||
str(IDF / "components/console/split_argv.c"), "-o", str(path / "test")],
|
||||
check=True, timeout=30)
|
||||
subprocess.run([str(path / "test")], check=True, timeout=10)
|
||||
Reference in New Issue
Block a user