Migrate to IDF 5.5.3 candidate

Pin PlatformIO packages and toolchains, rebase protected SDK
overrides, and add WebSocket receive regression coverage. Document
isolated candidate validation, archive provenance, and remaining gates.
This commit is contained in:
2026-09-18 14:23:13 +02:00
parent cdc4d4a8df
commit 797d2681ac
32 changed files with 1394 additions and 123 deletions
+45 -5
View File
@@ -1,5 +1,8 @@
#!/usr/bin/env python3
"""Compile actual transport callbacks using the existing serial/store doubles."""
import argparse
import importlib.util
import json
import os
import pathlib
import re
@@ -14,7 +17,40 @@ sys.path.insert(0, str(BASE))
from run import HEADERS
from serial_headers import SERIAL_HEADERS
os.environ['CCACHE_DISABLE'] = '1'
IDF = pathlib.Path(os.environ.get('IDF_PATH', str(pathlib.Path.home() / '.platformio/packages/framework-espidf')))
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--idf-path', type=pathlib.Path, default=pathlib.Path(os.environ.get(
'IDF_PATH', str(pathlib.Path.home() / '.platformio/packages/framework-espidf'))))
parser.add_argument('--build-dir', type=pathlib.Path,
help='Require the actual generated WS compilation input and verify its bytes')
parser.add_argument('--sanitize', action='store_true')
args = parser.parse_args()
IDF = args.idf_path.resolve()
def generated_ws():
spec = importlib.util.spec_from_file_location('performance_security_overrides', ROOT / 'tools/security_overrides.py')
sdk = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = sdk
spec.loader.exec_module(sdk)
sdk.verify_version(IDF)
entry = next(e for e in sdk.ENTRIES if e.name == 'httpd_ws')
original, rendered = sdk.render_entry(entry, {'idf': IDF, 'project': ROOT})
if args.build_dir:
build = args.build_dir.resolve()
expected = build / 'security_overrides/httpd_ws/httpd_ws.c'
commands = json.loads((build / 'compile_commands.json').read_text())
def input_path(command):
return (pathlib.Path(command['directory']) / command['file']).resolve()
matches = [c for c in commands if input_path(c) == expected]
assert len(matches) == 1, ('missing/ambiguous generated WS compilation input', matches)
assert not any(input_path(c) == original.resolve() for c in commands), 'vendor WS still compiled'
command = matches[0].get('command') or ' '.join(matches[0]['arguments'])
assert 'CMakeFiles/__idf_esp_http_server.dir/' in command, 'wrong WS owner'
assert expected.read_bytes() == rendered, 'stale generated WS input'
print('WS input: verified actual compilation input', expected)
return expected.read_text()
print('WS input: current hash-verified override render (not firmware build evidence)')
return rendered.decode()
def function(source, name):
match = re.search(r'^(?:static )?(?:esp_err_t|int|ssize_t) ' + name + r'\(.*?^\}', source, re.M | re.S)
@@ -22,8 +58,8 @@ def function(source, name):
return match.group() + '\n'
adapter = (ROOT / 'src/web_httpd_adapter.c').read_text()
assert 'ESP_IDF_VERSION_VAL(5, 5, 0)' in adapter
ws_source = (IDF / 'components/esp_http_server/src/httpd_ws.c').read_text()
assert 'ESP_IDF_VERSION_VAL(5, 5, 3)' in adapter
ws_source = generated_ws()
ws = function(ws_source, 'httpd_ws_send_frame_async')
main = (IDF / 'components/esp_http_server/src/httpd_main.c').read_text()
assert main.index('/* Case0:') < main.index('httpd_process_ctrl_msg(hd);') < main.index('/* Case1:')
@@ -53,6 +89,10 @@ with tempfile.TemporaryDirectory(prefix='web-performance-') as directory:
(tmp / 'fixture.c').write_text(fixture)
(tmp / 'binary.inc').write_text(function(adapter, 'web_httpd_aborted_send') + function(adapter, 'web_httpd_ws_send_binary'))
(tmp / 'sdk_ws.inc').write_text(ws.replace('httpd_ws_send_frame_async(', 'sdk_ws_send_frame('))
private = (IDF / 'components/esp_http_server/src/esp_httpd_priv.h').read_text()
receive_options = re.search(r'typedef enum \{[^}]*\} httpd_recv_opt_t;', private)
assert receive_options, 'Reaudit SDK receive option type'
(tmp / 'sdk_recv_options.inc').write_text(receive_options.group())
automatic = ''.join(function(ws_source, name) for name in
('httpd_ws_check_req', 'httpd_ws_send_frame', 'httpd_ws_get_frame_type'))
automatic = automatic.replace('httpd_ws_send_frame_async(', 'sdk_ws_send_frame(')
@@ -65,10 +105,10 @@ with tempfile.TemporaryDirectory(prefix='web-performance-') as directory:
(tmp / 'console.inc').write_text(
'static const char *esp_err_to_name(esp_err_t e) { (void)e; return "error"; }\n'
+ console[start:end])
flags = ['-fsanitize=address,undefined', '-fno-omit-frame-pointer'] if '--sanitize' in sys.argv else []
flags = ['-fsanitize=address,undefined', '-fno-omit-frame-pointer'] if args.sanitize else []
subprocess.run(['cc', '-std=c11', '-Wall', '-Wextra', '-Werror', '-g', *flags,
'-I'+str(tmp), '-I'+str(ROOT / 'src'), '-ffunction-sections', '-fdata-sections',
'-Wl,--gc-sections', str(HERE / 'test.c'), str(ROOT / 'src/web_session_store.c'),
str(ROOT / 'src/web_auth_parse.c'), '-o', str(tmp / 'test')], check=True, timeout=30)
subprocess.run([str(tmp / 'test')], check=True, timeout=15)
print('PASS installed IDF WS two-write / HTTPS forwarding / TLS partial-return contract guards')
print('PASS generated IDF WS two-write / HTTPS forwarding / TLS partial-return contract guards')
+3 -2
View File
@@ -61,9 +61,10 @@ static int host_shutdown(int fd, int how)
#define HTTPD_WS_TYPE_PONG 10
static uint8_t incoming_opcode;
static unsigned automatic_reads;
static int httpd_recv_with_opt(httpd_req_t *r, char *out, size_t n, bool peek)
#include "sdk_recv_options.inc"
static int httpd_recv_with_opt(httpd_req_t *r, char *out, size_t n, httpd_recv_opt_t opt)
{
(void)r; assert(n == 1 && !peek); *out = 0x80 | incoming_opcode;
(void)r; assert(n == 1 && opt == HTTPD_RECV_OPT_BLOCKING); *out = 0x80 | incoming_opcode;
++automatic_reads; return 1;
}
static esp_err_t sdk_control_recv(httpd_req_t *r, httpd_ws_frame_t *f, size_t n)