[esp32] Trim mbedTLS to client-only defaults and stub vasprintf on the C6

Linking an HTTPS client (http_request, mqtt) pulls in TLS features no
ESPHome client ever negotiates: the server-side handshake, static RSA and
static ECDH key exchange, renegotiation, session tickets, AES-CCM suites,
the EC private-key parsing extras and deterministic ECDSA. Two new esp32
advanced options, on by default, turn them off and save around 14 KB per
build; OpenThread opts back in through require hooks because its DTLS
commissioner is a server and it uses CCM and deterministic ECDSA directly.

On the ESP32-C6 the ROM exports a full-format vsnprintf but no vasprintf,
so esp_http_client's vasprintf call was the only thing dragging newlib's
printf engine (~20 KB) into the image. A linker-wrapped vasprintf built on
the ROM vsnprintf keeps it out.
This commit is contained in:
Jesse Hills
2026-09-11 16:56:08 +12:00
parent 3e3822e554
commit bfb8569636
11 changed files with 288 additions and 0 deletions
@@ -0,0 +1,14 @@
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
wifi:
ssid: "test_ssid"
password: "test_password"
http_request:
verify_ssl: true
@@ -0,0 +1,19 @@
esphome:
name: test
esp32:
variant: esp32c6
framework:
type: esp-idf
network:
enable_ipv6: true
openthread:
channel: 13
network_name: OpenThread-8f28
network_key: 0xdfd34f0f05cad978ec4e32b0413038ff
pan_id: 0x8f28
ext_pan_id: 0xd63e8e3e495ebbc3
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
mesh_local_prefix: fd53:145f:ed22:ad81::/64
@@ -0,0 +1,17 @@
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
advanced:
disable_mbedtls_tls_server: false
disable_mbedtls_tls_extras: false
wifi:
ssid: "test_ssid"
password: "test_password"
http_request:
verify_ssl: true
@@ -0,0 +1,7 @@
esphome:
name: test
esp32:
variant: esp32c6
framework:
type: esp-idf
@@ -0,0 +1,9 @@
esphome:
name: test
esp32:
variant: esp32c6
framework:
type: esp-idf
advanced:
enable_full_printf: true
+67
View File
@@ -11,9 +11,12 @@ import pytest
from esphome.components.esp32 import (
KEY_FATFS_REQUIRED,
KEY_MBEDTLS_TLS_EXTRAS_REQUIRED,
KEY_MBEDTLS_TLS_SERVER_REQUIRED,
KEY_VFS_DIR_REQUIRED,
KEY_VFS_SELECT_REQUIRED,
KEY_VFS_TERMIOS_REQUIRED,
MBEDTLS_TLS_EXTRA_OPTIONS,
VARIANT_ESP32,
VARIANTS,
NetworkSdkconfigData,
@@ -1339,3 +1342,67 @@ def test_esp32_s31_gpio_validation(
with caplog.at_level("WARNING"):
validate_supports(pin)
assert "GPIO36 is a strapping PIN" in caplog.text
_TLS_SERVER_OPTIONS = (
"CONFIG_MBEDTLS_TLS_CLIENT_ONLY",
"CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT",
)
@pytest.mark.parametrize(
("config_file", "server", "extras"),
[
pytest.param("mbedtls_tls_default.yaml", (True, False), False, id="default"),
pytest.param("mbedtls_tls_opt_out.yaml", (None, None), None, id="opt_out"),
pytest.param(
"mbedtls_tls_openthread.yaml", (None, None), None, id="openthread"
),
],
)
def test_mbedtls_tls_trim_sdkconfig(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
config_file: str,
server: tuple[bool | None, bool | None],
extras: bool | None,
) -> None:
"""Client-only TLS and the unused-feature trims apply unless opted out or required."""
generate_main(component_config_path(config_file))
sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
assert tuple(sdkconfig.get(name) for name in _TLS_SERVER_OPTIONS) == server
assert {sdkconfig.get(name) for name in MBEDTLS_TLS_EXTRA_OPTIONS} == {extras}
def test_mbedtls_tls_openthread_requires_server_and_extras(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""The OpenThread hooks mark the DTLS server and CCM/deterministic ECDSA as required."""
generate_main(component_config_path("mbedtls_tls_openthread.yaml"))
assert CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_SERVER_REQUIRED] is True
assert CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_EXTRAS_REQUIRED] is True
_VASPRINTF_STUB_FLAGS = {"-Wl,--wrap=vasprintf", "-Wl,--undefined=__wrap_vasprintf"}
@pytest.mark.parametrize(
("config_file", "expected"),
[
pytest.param("vasprintf_stub_c6.yaml", True, id="c6"),
pytest.param("vasprintf_stub_c6_full_printf.yaml", False, id="c6_full_printf"),
pytest.param("exclusion_reincludes.yaml", False, id="esp32"),
],
)
def test_vasprintf_stub_only_on_rom_vsnprintf_variants(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
config_file: str,
expected: bool,
) -> None:
"""The vasprintf wrap is emitted only where the ROM lacks vasprintf but has vsnprintf."""
generate_main(component_config_path(config_file))
assert (CORE.build_flags >= _VASPRINTF_STUB_FLAGS) is expected
defines = {define.name for define in CORE.defines}
assert ("USE_ESP32_VASPRINTF_STUB" in defines) is expected
@@ -17,6 +17,8 @@ esp32:
disable_dev_null_vfs: true
disable_mbedtls_peer_cert: true
disable_mbedtls_pkcs7: true
disable_mbedtls_tls_server: true
disable_mbedtls_tls_extras: true
disable_regi2c_in_iram: true
disable_fatfs: true
sram1_as_iram: true