Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
797d2681ac | ||
|
|
cdc4d4a8df | ||
|
|
51f835c22f | ||
|
|
bea33e1c95 | ||
|
|
4d3bb490c9 | ||
|
|
c010e1a1d5 | ||
|
|
cdc9c7335a | ||
|
|
751dfb9ddb | ||
|
|
436c27adb1 |
@@ -8,11 +8,14 @@ add_compile_definitions(
|
||||
NO_WOLFSSL_ESP32_CRYPT_AES
|
||||
NO_WOLFSSL_ESP32_CRYPT_HASH
|
||||
WOLFSSL_ED25519_STREAMING_VERIFY
|
||||
# CVE-2025-12888: upstream PR9275 small math policy for enabled algorithms.
|
||||
# These affect public key layouts; library and consumers must agree.
|
||||
CURVE25519_SMALL
|
||||
ED25519_SMALL
|
||||
)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
set(PROJECT_VER "0.1.0")
|
||||
project(esp32_serial_swiss_army_knife)
|
||||
|
||||
# Narrow mDNS 1.12.0 correctness fix; see tests/mdns_membership/README.md.
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/mdns_membership.cmake)
|
||||
include(cmake/security_overrides.cmake)
|
||||
include(cmake/wolf_crypto_policy.cmake)
|
||||
|
||||
@@ -14,9 +14,9 @@ ESP32-S3 firmware for a secure, multi-transport RS-232 adapter. It operates one
|
||||
|
||||
## Development status
|
||||
|
||||
Hardware characterization, serial/USB/Wi-Fi/HTTPS/SSH and local display/control are implemented and hardware-validated. **Phase 8 role-based users and administration is complete:** 8A–8C were target-hardware validated and the user explicitly signed off tested firmware at **8D.22 (2026-09-13)**. See the [roadmap](docs/roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and its [consolidated acceptance evidence](docs/roadmap.md#phase8-acceptance-evidence). Very low internal/DMA lifetime minima remain a nonblocking headroom follow-up, not an approved reserve. The reduced Phase 9 focuses on intermittent trusted-network use, standard no-dump/silent-panic defaults and a short [operational checklist](docs/security_operations.md), without dependency patches or upgrades. **The reduced Phase 9 is complete by user sign-off on 2026-09-18.** The user waived a new device check based on prior Phase 8 validation; application code is unchanged, but crash-diagnostic defaults changed. No new hardware or panic-test pass is claimed.
|
||||
Hardware characterization, serial/USB/Wi-Fi/HTTPS/SSH and local display/control are implemented and hardware-validated. **Phase 8 role-based users and administration is complete:** 8A–8C were target-hardware validated and the user explicitly signed off tested firmware at **8D.22 (2026-09-13)**. See the [roadmap](docs/roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and [acceptance evidence](docs/web_administration_acceptance.md). Very low internal/DMA lifetime minima remain a nonblocking headroom follow-up, not an approved reserve. **Phase 9 security hardening is in progress**: 9A crash/debug policy, 9B SSH admission/credential handling and 9C library cleanup/protocol policy have passed host/build checks. **9D maintenance and lifecycle is in progress:** an operator runbook, finite SSH parser/caller and IDF applicability reviews, and offline notice assembly tooling are available. Scoped corrections have parent host/build evidence; the [finite dependency search](docs/dependency_advisory_coverage.md) is complete (all 22 IDF advisory entries across three pages plus named managed/browser channels), but fresh findings, explicit coverage gaps, release delivery/source/legal questions and whole-phase target gates remain open. **The fix-bearing vendor Wi-Fi bundle is integrated in the validated IDF 5.5.3 candidate, not radio-hardware closure**: the [update plan](docs/wifi_security_update_plan.md) retains target gates and unchanged PMF/WPA3; **Phase 9 is not complete or production-ready**. The user will validate Phase 9 as a whole; no 9D device validation or license clearance is claimed. See [security hardening](docs/security_hardening.md) for scope, operational profiles, and validation gates.
|
||||
|
||||
**Phase 10 application firmware upload is complete by explicit user acceptance on 2026-09-18:** upload works and normal operation is verified. This does not claim specific fault-injection, NVS before/after comparison, power-loss or wired-recovery checks. Administrators can upload a locally built application from **Settings → HTTPS / Reboot → Firmware update**. Install this OTA-enabled firmware by wire first; future uploads use `.pio/build/esp32-s3-devkitc-1-n16r8/firmware.bin`. See [firmware update and recovery](docs/roadmap.md#phase10-update-and-recovery) for compatibility, interruption and validation limits.
|
||||
Latest supplied parent integration: **fresh isolated IDF 5.5.3 application build PASS**, **95,552 B linked RAM / 1,749,493 B flash** (**+1,212 B RAM / −19,408 B flash** versus historical 94,340 / 1,768,901 B). Explicit-candidate Phase 9 validation with interop and web performance **PASS 24/24**. Default root `pio run` timed out at **200 seconds during installation, before compilation**—not a normal root build PASS. See [candidate integration](docs/idf_candidate_integration.md) and [exact command/snapshot evidence](docs/phase9_validation.md#current-candidate-execution--2026-09-18). Linked size and host passes are not target headroom or release acceptance.
|
||||
|
||||
### Browser administration
|
||||
|
||||
@@ -33,11 +33,17 @@ Keep UART0 ready for administrative recovery and native USB for network-independ
|
||||
- [Hardware wiring](docs/wiring.md): hardware profile, GPIO assignments, connector guidance, and safety notes.
|
||||
- [Electrical tests](docs/electrical_tests.md): OLED/buttons, MAX3243, UART loopback, and session-broker verification procedures.
|
||||
- [Role-based user database and UART0 administration](docs/user_administration_tests.md): user provisioning and administration, HTTPS/SSH authentication, session revocation, and reusable integrated web-administration regression procedures (not execution evidence).
|
||||
- [Web administration](docs/web_administration.md): current bounded API/owner and recovery contracts; [roadmap acceptance evidence](docs/roadmap.md#phase8-acceptance-evidence) records sign-off and telemetry limits.
|
||||
- [Firmware update](docs/roadmap.md#phase-10--simple-admin-web-firmware-upload): admin-only application upload, NVS preservation, uncertain outcomes, wired recovery, acceptance evidence and reusable regression checks.
|
||||
- [Security operations](docs/security_operations.md): trusted-network use, credentials, identity verification, shutdown and recovery; physical-extraction limits and lightweight upstream maintenance.
|
||||
- [Web administration](docs/web_administration.md): current bounded API/owner and recovery contracts; [acceptance evidence](docs/web_administration_acceptance.md) records sign-off and telemetry limits.
|
||||
- [Security hardening](docs/security_hardening.md): Phase 9 scope, crash/debug baseline, diagnostic handling, and pending validation.
|
||||
- [Security operations](docs/security_operations.md): provisioning, explicit saves, rotation, recovery, reconstruction and approved destructive reset/decommissioning; procedures, not device evidence.
|
||||
- [Dependency license inventory](docs/dependency_licenses.md): bounded local inventory and unresolved distribution/source/notice obligations, not release clearance.
|
||||
- [wolfSSL/wolfSSH advisory review](docs/wolf_security_review.md): implemented scoped mitigations, historical research and remaining target/maintenance gates.
|
||||
- [Remaining SSH parser review](docs/ssh_parser_remaining_review.md): completed finite server-profile dispatch/caller review, corrections and explicit exclusions; not exhaustive parser safety.
|
||||
- [IDF applicability review](docs/idf_security_review.md): six named findings and a bounded advisory-index feature screen, not all-CVE clearance.
|
||||
- [Bounded Phase 9 host validation](docs/phase9_validation.md): [orchestrator](tools/validate_phase9.py), 23 default commands; build and OpenSSH interop are separate opt-ins.
|
||||
- [Exact icon provenance](docs/icon_provenance.md): official 7.4.47 retained SVGs verified; manual bitmap derivation and distinct mockup Wi-Fi path remain unresolved.
|
||||
- [Release notice packaging](docs/release_packaging.md): offline [notice tool](tools/release_notices.py), deterministic bounded assembly and separate recipient/source/legal gates.
|
||||
- [Command reference](docs/command_reference.md): UART0/admin-SSH administration, serial, broker, USB, Wi-Fi, mDNS, web, SSH, and diagnostic commands.
|
||||
- [Dual-stack networking and discovery](docs/roadmap.md#phase-12--advanced-network-integration): DHCPv4 hostname publication, STA SLAAC/link-local IPv6, HTTPS/SSH DNS-SD and dual-stack access; complete by explicit user validation, including fresh boot and the full client mix at 230400 baud. Includes recorded heap/stack measurements, evidence limits, saved-config requirements and the narrowly guarded mDNS membership fix.
|
||||
|
||||
## Flash partition layout
|
||||
|
||||
@@ -48,15 +54,15 @@ The N16R8 target has 16 MiB flash and 8 MiB octal PSRAM. PlatformIO uses the cus
|
||||
| `nvs` | `0x009000` | 512 KiB | Serial, Wi-Fi, mDNS hostname, local-display, role-based user, HTTPS identity, and SSH host-key data |
|
||||
| `otadata` | `0x089000` | 8 KiB | Active OTA-slot selection metadata |
|
||||
| `phy_init` | `0x08B000` | 4 KiB | Optional PHY initialization data |
|
||||
| `nvs_key` | `0x08C000` | 4 KiB | Reserved for future encrypted-NVS keys |
|
||||
| `coredump` | `0x08D000` | 128 KiB | Reserved for flash core dumps |
|
||||
| `nvs_key` | `0x08C000` | 4 KiB | Unused; retained for layout compatibility (NVS encryption excluded) |
|
||||
| `coredump` | `0x08D000` | 128 KiB | Unused by the supported crash policy; retained for layout compatibility |
|
||||
| `ota_0` | `0x0B0000` | 4 MiB | Primary application/OTA slot |
|
||||
| `ota_1` | `0x4B0000` | 4 MiB | Alternate application/OTA slot |
|
||||
| `storage` | `0x8B0000` | 7488 KiB | Reserved for future LittleFS web assets, logs, and files |
|
||||
|
||||
Application offsets are aligned to the ESP32-S3's required 64 KiB boundary. The final storage partition ends at `0x1000000`, exactly the end of the 16 MiB flash chip.
|
||||
|
||||
Admin HTTPS application upload uses the inactive OTA slot and `otadata`; NVS and other data partitions are untouched by the updater. It does not provide remote downloads, automatic rollback/health confirmation, core-dump handling, NVS encryption or filesystem mounting. Image validation does not guarantee a working application; compatible storage schemas and wired recovery remain the owner's responsibility.
|
||||
The table reserves OTA and storage space; it does not implement OTA downloads, rollback confirmation, NVS encryption, or filesystem mounting. Phase 9A disables new core dumps; it does not clear any existing `coredump` contents. The unused `nvs_key` and `coredump` reservations remain unchanged for layout compatibility; neither implies future enablement or secure erasure.
|
||||
|
||||
### One-time migration from the default partition table
|
||||
|
||||
@@ -68,7 +74,7 @@ pio run --target upload
|
||||
pio device monitor -b 115200
|
||||
```
|
||||
|
||||
This removes saved serial configuration and all other flash contents. The firmware recreates NVS with safe defaults. **Do not erase for subsequent ordinary wired or web application updates.** This destructive migration is only for the former default/factory layout, not for installing the updater on an existing custom-layout device.
|
||||
**Obtain explicit approval for the target and all flash/NVS data loss before erase.** This removes firmware, accounts, Wi-Fi secrets, server identities and all saved configuration, not just serial settings. Reflash in an isolated RF environment and follow [UART0-first provisioning](docs/security_operations.md#first-provisioning--uart0-first); generated defaults enable fallback Wi-Fi and require deliberate protection. Erase success is not a forensic secure-erasure guarantee. Subsequent ordinary uploads do not need a full erase.
|
||||
|
||||
## Build
|
||||
|
||||
@@ -76,7 +82,7 @@ This removes saved serial configuration and all other flash contents. The firmwa
|
||||
pio run
|
||||
```
|
||||
|
||||
The small Phase 9 sets `CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y` and `CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y` in `sdkconfig.defaults`. An existing generated `sdkconfig.esp32-s3-devkitc-1-n16r8` can retain earlier selections: check its resolved values after configuration, or change them using `pio run --target menuconfig`. Defaults alone do not enforce existing configurations. Silent panic reboot removes panic register/backtrace output; ordinary logs remain. No existing flash contents are erased, and stored secrets remain unencrypted.
|
||||
The root configuration pins PlatformIO espressif32 **6.13.0**, ESP-IDF **5.5.3** (`framework-espidf@3.50503.0`), and Xtensa/RISC-V toolchains **14.2.0+20251107**, with pinned component sources. Exact version pins do not establish a complete immutable build closure. Nine C overrides plus one forced header retain the reviewed corrections, including five signed WS receive-size checks; see the [semantic rebase review](docs/idf_553_rebase_review.md). It generates audited security corrections under `.pio/build/` without changing the installed SDK/managed components; changed source hashes fail configuration rather than silently dropping a fix. See the [library review and upgrade contract](docs/security_library_review.md). Do not edit generated corrections or update hashes without reviewing the new source.
|
||||
|
||||
## Upload and monitor
|
||||
|
||||
@@ -91,18 +97,24 @@ The firmware provides an interactive UART0 console at `serial-tool>`. Run `help`
|
||||
|
||||
The console supports session history, line editing, cursor movement, and hierarchical Tab completion. After an unattended boot, attach an ANSI-capable terminal and press Enter once to enable enhanced editing; this avoids blocking while no terminal is attached.
|
||||
|
||||
Serial, Wi-Fi, and mDNS hostname edits remain in RAM until explicitly saved with `serial save`, `wifi save`, or `mdns save`. Authenticated admin SSH sessions expose the shared operational administration registry, including interactive secrets, TLS/SSH identity management, network diagnostics, and deferred reboot/SSH lifecycle commands. Create the first administrator on UART0 with `user add <username> admin` (optionally `--generate`). Explicit recovery of an unavailable user database remains UART0-only and rebuilds it empty; it refuses a healthy database. An administrator also cannot generate a replacement password for its own account over SSH, preventing the one-time value from being lost when that mutation revokes the session. Legacy web credential commands and `user bootstrap` are removed.
|
||||
Serial, Wi-Fi, mDNS hostname and display edits remain in RAM until explicitly saved with `serial save`, `wifi save`, `mdns save` or `display save`. These are same-device persistence commands, not backups; see [lifecycle and persistence semantics](docs/security_operations.md#working-configuration-versus-service-lifecycle). Authenticated admin SSH sessions expose the shared operational administration registry, including interactive secrets, TLS/SSH identity management, network diagnostics, and deferred reboot/SSH lifecycle commands. Create the first administrator on UART0 with `user add <username> admin` (optionally `--generate`). Explicit recovery of an unavailable user database remains UART0-only and rebuilds it empty; it refuses a healthy database. An administrator also cannot generate a replacement password for its own account over SSH, preventing the one-time value from being lost when that mutation revokes the session. Legacy web credential commands and `user bootstrap` are removed.
|
||||
|
||||
## Security notes
|
||||
|
||||
The HTTPS interface uses a device-specific self-signed certificate and a same-origin login page with bounded server-side cookie sessions; HTTP Basic is no longer accepted. Open `/` or `/login`, sign in with a user-database password, and use **Sign out** before switching accounts. Four sessions have a one-hour absolute lifetime, including active serial connections; logout closes only that session's serial access. Login is globally limited to five credential verifications per 60 seconds, with explicit capacity/backoff errors. Direct-IP and mDNS access use separate host-only Secure/HttpOnly/SameSite=Strict cookies. Non-browser clients also require cookies, strict Origin and CSRF for mutations rather than Basic credentials. There is no plaintext HTTP or TCP serial listener. SSH accepts role-based passwords and authorized Ed25519/ECDSA P-256 public keys. User passwords are stored as salted PBKDF2-HMAC-SHA256 verifiers, but the HTTPS private key, SSH private key, and Wi-Fi credentials remain recoverable from unencrypted application-owned NVS blobs. Offline password guessing and stale append-oriented flash copies also remain possible. The reserved `nvs_key` partition does not enable encryption. This firmware is not resistant to physical flash or RAM extraction; the completed reduced Phase 9 deliberately does not add that protection.
|
||||
The HTTPS interface uses a device-specific self-signed certificate and a same-origin login page with bounded server-side cookie sessions; HTTP Basic is no longer accepted. Open `/` or `/login`, sign in with a user-database password, and use **Sign out** before switching accounts. Four sessions have a one-hour absolute lifetime, including active serial connections; logout closes only that session's serial access. Login is globally limited to five credential verifications per 60 seconds, with explicit capacity/backoff errors. Direct-IP and mDNS access use separate host-only Secure/HttpOnly/SameSite=Strict cookies. Non-browser clients also require cookies, strict Origin and CSRF for mutations rather than Basic credentials. There is no plaintext HTTP or TCP serial listener. SSH accepts role-based passwords and authorized Ed25519/ECDSA P-256 public keys. User passwords are stored as salted PBKDF2-HMAC-SHA256 verifiers, but the HTTPS private key, SSH private key, and Wi-Fi credentials remain recoverable from unencrypted application-owned NVS blobs. Offline password guessing and stale append-oriented flash copies also remain possible. The reserved `nvs_key` partition does not enable encryption. Physical flash/RAM extraction and firmware replacement remain outside the threat model even after Phase 9. Secure boot and encrypted NVS are explicitly excluded; no flash/PSRAM encryption or physical JTAG eFuse restriction is promised.
|
||||
|
||||
SSH uses separate, boot-lifetime global admission budgets for handshakes and password/signed-key checks (burst six, one refill per ten seconds), and unsigned key probes (burst twelve, one per five seconds). Reconnect, SSH restart and counter clearing do not replenish them. Rate denial closes the authenticating connection without sleeping the owner task; the three-attempt per-connection failure limit remains. These global limits can temporarily deny legitimate new SSH logins under attack and do not promise fair access or zero CPU impact. Hidden console prompts reject overlong/unsupported input instead of silently accepting a prefix; consumed SSH admin staging bytes are wiped. See [security hardening](docs/security_hardening.md#9b-ssh-admission-and-credential-handling) for exact semantics and remaining review work.
|
||||
|
||||
Phase 9C limits HTTPS to TLS 1.2 ECDHE-ECDSA with AES-128/256-GCM, and SSH to AES-128/256-GCM with Curve25519/P-256 key exchange. Legacy-only clients may no longer connect; existing device identities and authorized keys do not need rotation. The HTTPS policy is server-local, leaving outbound-client defaults intact. Pinned dependency corrections address cleanup leaks, parser bounds and secret-bearing storage retirement; secure allocation growth can temporarily require old and new blocks, so target resource validation remains necessary. These fixes are not a complete secret-zeroization or upstream-advisory certification. See the [audit, exact algorithms and limits](docs/security_library_review.md).
|
||||
|
||||
The Phase 9A supported build baseline requires disabled core dumps and silent panic reboot, rejecting panic print/halt/GDBstub and software debugger-aware options at compile time. Development, test, and production are operational profiles of the same build baseline, not separate PlatformIO environments. Silent panic reboot sacrifices panic backtraces/register dumps; reset-reason/boot information and ordinary status/logging can remain. This is not a general log-redaction guarantee. Treat raw flash, RAM and dumps as secret-bearing; do not export them as routine diagnostics. No retroactive dump clearing or secure-erase claim is made. See [security hardening](docs/security_hardening.md) for the pending checks and reviewed synthetic-secret debugging procedure.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [GNU General Public License version 3 only](LICENSE) (`GPL-3.0-only`). Third-party components remain subject to their respective licenses. The integration baseline uses Espressif registry components `espressif/mdns` `1.12.0`, `wolfssl/wolfssl` `5.8.2~1`, and `wolfssl/wolfssh` `1.4.20`; review upstream security releases before production use.
|
||||
This project is licensed under the [GNU General Public License version 3 only](LICENSE) (`GPL-3.0-only`). Third-party components remain subject to their respective licenses. The integration baseline uses Espressif registry components `espressif/mdns` `1.12.0`, `wolfssl/wolfssl` `5.8.2~1`, and `wolfssl/wolfssh` `1.4.20`; see the [partial advisory review](docs/wolf_security_review.md) for scoped corrections and remaining review/target gates, not a whole-library safety claim. The [license inventory](docs/dependency_licenses.md) records open radio-blob corresponding-source/exception questions, notice delivery and preferred-source requirements, generated-file modification notices, and wolfSSH packaging clarification. Neither this license declaration nor the inventory clears firmware/device distribution.
|
||||
|
||||
### Legacy credential removal
|
||||
|
||||
Missing user storage is persisted as an empty database; no shared credential is imported or synchronized. Existing valid v1 user records retain their accounts, roles, IDs, verifiers and keys without a schema change. HTTPS `web_sec/material` upgrades valid 1,392-byte v1 storage to 1,340-byte TLS-only v2, retaining exact certificate/key DER, fingerprint and generation, and committing before publication. Invalid records or migration failures fail closed rather than triggering fallback replacement. `web certificate rotate --force` remains available; `web reset --force` replaces TLS identity only, not users.
|
||||
|
||||
**Downgrade warning:** older v1-only firmware cannot read v2 HTTPS material. Logical NVS replacement is not a secure flash wipe; historical plaintext credentials can remain in flash. This cleanup requires no factory/partition erase. See [consolidated compatibility and evidence limits](docs/roadmap.md#phase8-legacy-credential-compatibility); Phase 8 acceptance is recorded there.
|
||||
**Downgrade warning:** older v1-only firmware cannot read v2 HTTPS material. Logical NVS replacement is not a secure flash wipe; historical plaintext credentials can remain in flash. This cleanup requires no factory/partition erase. See [implementation and evidence limits](docs/legacy_credential_removal.md); final integration build evidence is pending.
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Only the lwIP backend needs this fix. Keep the managed component immutable.
|
||||
function(project_mdns_membership_overlay)
|
||||
if(CONFIG_MDNS_NETWORKING_SOCKET)
|
||||
return()
|
||||
endif()
|
||||
idf_component_get_property(mdns_dir espressif__mdns COMPONENT_DIR)
|
||||
idf_component_get_property(mdns_lib espressif__mdns COMPONENT_LIB)
|
||||
idf_component_get_property(mdns_version espressif__mdns COMPONENT_VERSION)
|
||||
if(NOT mdns_version STREQUAL "1.12.0")
|
||||
message(FATAL_ERROR "mDNS membership overlay requires component version 1.12.0; review upstream")
|
||||
endif()
|
||||
idf_build_get_property(python PYTHON)
|
||||
set(helper "${CMAKE_CURRENT_LIST_DIR}/mdns_membership.py")
|
||||
set(original "${mdns_dir}/mdns_networking_lwip.c")
|
||||
set(overlay "${CMAKE_BINARY_DIR}/mdns_membership/mdns_networking_lwip.c")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${helper}" "${original}" "${mdns_dir}/idf_component.yml" "${overlay}")
|
||||
execute_process(COMMAND "${python}" "${helper}" "${mdns_dir}" "${overlay}"
|
||||
RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error)
|
||||
if(NOT result EQUAL 0)
|
||||
message(FATAL_ERROR "mDNS membership overlay failed: ${output}${error}")
|
||||
endif()
|
||||
|
||||
get_target_property(sources ${mdns_lib} SOURCES)
|
||||
get_target_property(source_dir ${mdns_lib} SOURCE_DIR)
|
||||
set(replaced 0)
|
||||
set(updated_sources)
|
||||
foreach(source IN LISTS sources)
|
||||
get_filename_component(absolute "${source}" ABSOLUTE BASE_DIR "${source_dir}")
|
||||
if(absolute STREQUAL original)
|
||||
list(APPEND updated_sources "${overlay}")
|
||||
math(EXPR replaced "${replaced} + 1")
|
||||
else()
|
||||
list(APPEND updated_sources "${source}")
|
||||
endif()
|
||||
endforeach()
|
||||
if(NOT replaced EQUAL 1)
|
||||
message(FATAL_ERROR "Expected exactly one mDNS lwIP target source, found ${replaced}; review upstream CMake")
|
||||
endif()
|
||||
set_property(TARGET ${mdns_lib} PROPERTY SOURCES "${updated_sources}")
|
||||
message(STATUS "mDNS 1.12.0: using guarded build-local multicast membership fix")
|
||||
endfunction()
|
||||
|
||||
project_mdns_membership_overlay()
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the narrowly guarded mDNS 1.12.0 membership overlay; never edit upstream."""
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
SOURCE_SHA256 = "adc139fa504a925ab644f21f8dce3659927f534e390a176b72b0ae3206c6a3ea"
|
||||
OLD_DEINIT = """ s_interfaces[tcpip_if].proto &= ~(ip_protocol == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6);
|
||||
if (s_interfaces[tcpip_if].proto == 0) {
|
||||
s_interfaces[tcpip_if].ready = false;
|
||||
join_group(tcpip_if, ip_protocol, false);
|
||||
"""
|
||||
NEW_DEINIT = """ int proto = (ip_protocol == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6);
|
||||
if (!(s_interfaces[tcpip_if].proto & proto)) {
|
||||
return;
|
||||
}
|
||||
join_group(tcpip_if, ip_protocol, false);
|
||||
s_interfaces[tcpip_if].proto &= ~proto;
|
||||
if (s_interfaces[tcpip_if].proto == 0) {
|
||||
s_interfaces[tcpip_if].ready = false;
|
||||
"""
|
||||
OLD_INIT = """ err = pcb_init();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
"""
|
||||
NEW_INIT = """ err = pcb_init();
|
||||
if (err) {
|
||||
join_group(tcpip_if, ip_protocol, false);
|
||||
return err;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def generate(component: Path, output: Path) -> None:
|
||||
manifest = (component / "idf_component.yml").read_text()
|
||||
if re.findall(r"^version:\s*(\S+)\s*$", manifest, re.MULTILINE) != ["1.12.0"]:
|
||||
raise ValueError("mDNS membership overlay requires exactly version 1.12.0; review upstream")
|
||||
original = (component / "mdns_networking_lwip.c").read_bytes()
|
||||
if hashlib.sha256(original).hexdigest() != SOURCE_SHA256:
|
||||
raise ValueError("mDNS networking source SHA-256 mismatch; review upstream, do not bypass guard")
|
||||
patched = original.decode("utf-8")
|
||||
for old, new in ((OLD_DEINIT, NEW_DEINIT), (OLD_INIT, NEW_INIT)):
|
||||
if patched.count(old) != 1:
|
||||
raise ValueError("mDNS membership replacement must match exactly once")
|
||||
patched = patched.replace(old, new, 1)
|
||||
result = patched.encode("utf-8")
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not output.exists() or output.read_bytes() != result:
|
||||
output.write_bytes(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("component", type=Path)
|
||||
parser.add_argument("output", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
generate(args.component, args.output)
|
||||
except (OSError, ValueError) as error:
|
||||
parser.exit(1, f"mDNS membership overlay: {error}\n")
|
||||
@@ -0,0 +1,150 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
# Include after project(): IDF component targets and their final source lists exist.
|
||||
if(CMAKE_VERSION VERSION_LESS 3.18)
|
||||
message(FATAL_ERROR "Security overrides need CMake 3.18 source-property directory support")
|
||||
endif()
|
||||
|
||||
set(_sak_security_script "${CMAKE_CURRENT_LIST_DIR}/../tools/security_overrides.py")
|
||||
get_filename_component(_sak_security_script "${_sak_security_script}" REALPATH)
|
||||
if(NOT PYTHON)
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
set(_sak_security_python "${Python3_EXECUTABLE}")
|
||||
else()
|
||||
set(_sak_security_python "${PYTHON}")
|
||||
endif()
|
||||
idf_build_get_property(_sak_security_idf IDF_PATH)
|
||||
execute_process(
|
||||
COMMAND "${_sak_security_python}" "${_sak_security_script}"
|
||||
--idf-path "${_sak_security_idf}"
|
||||
--project-dir "${PROJECT_SOURCE_DIR}"
|
||||
--binary-dir "${CMAKE_BINARY_DIR}"
|
||||
RESULT_VARIABLE _sak_security_result
|
||||
OUTPUT_VARIABLE _sak_security_stdout
|
||||
ERROR_VARIABLE _sak_security_stderr
|
||||
)
|
||||
if(NOT _sak_security_result EQUAL 0)
|
||||
message(FATAL_ERROR "SDK security override generation failed:\n${_sak_security_stdout}${_sak_security_stderr}")
|
||||
endif()
|
||||
include("${CMAKE_BINARY_DIR}/security_overrides/manifest.cmake")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${_sak_security_script}" "${SAK_SECURITY_VERSION_HEADER}")
|
||||
if(SAK_SECURITY_HEADER_IDS)
|
||||
get_filename_component(_sak_security_tools "${_sak_security_script}" DIRECTORY)
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${_sak_security_tools}/wolfssh_order/delta.json")
|
||||
endif()
|
||||
|
||||
# Public extension point: the Python Entry registry supplies the mapping. This
|
||||
# function is backend-agnostic; a later pinned project/vendor source uses it too.
|
||||
function(sak_security_replace_source component original generated nested_target)
|
||||
idf_component_get_property(_target "${component}" COMPONENT_LIB)
|
||||
if(NOT TARGET "${_target}")
|
||||
message(FATAL_ERROR "Security override: missing component target ${component}")
|
||||
endif()
|
||||
if(NOT "${nested_target}" STREQUAL "")
|
||||
if(NOT component STREQUAL "mbedtls" OR
|
||||
NOT nested_target MATCHES "^(mbedtls|mbedx509|mbedcrypto)$")
|
||||
message(FATAL_ERROR "Security override: invalid nested target ${component}/${nested_target}")
|
||||
endif()
|
||||
if(NOT TARGET "${nested_target}")
|
||||
message(FATAL_ERROR "Security override: missing nested target ${nested_target}")
|
||||
endif()
|
||||
set(_target "${nested_target}")
|
||||
get_target_property(_imported "${_target}" IMPORTED)
|
||||
get_target_property(_alias "${_target}" ALIASED_TARGET)
|
||||
get_target_property(_owner_dir "${_target}" SOURCE_DIR)
|
||||
get_filename_component(_owner_dir "${_owner_dir}" REALPATH)
|
||||
get_filename_component(_expected_owner "${_sak_security_idf}/components/mbedtls/mbedtls/library" REALPATH)
|
||||
if(_imported OR _alias OR NOT _owner_dir STREQUAL _expected_owner)
|
||||
message(FATAL_ERROR "Security override: unexpected nested target owner ${_target}: ${_owner_dir}")
|
||||
endif()
|
||||
endif()
|
||||
get_target_property(_source_dir "${_target}" SOURCE_DIR)
|
||||
get_target_property(_sources "${_target}" SOURCES)
|
||||
get_filename_component(_expected "${original}" REALPATH)
|
||||
set(_matches 0)
|
||||
set(_replaced)
|
||||
foreach(_source IN LISTS _sources)
|
||||
if(_source MATCHES "\\$<")
|
||||
# An expression could hide an additional copy of the protected source.
|
||||
message(FATAL_ERROR "Security override: unaudited source expression in ${component}: ${_source}")
|
||||
endif()
|
||||
get_filename_component(_absolute "${_source}" ABSOLUTE BASE_DIR "${_source_dir}")
|
||||
get_filename_component(_absolute "${_absolute}" REALPATH)
|
||||
if(_absolute STREQUAL _expected)
|
||||
math(EXPR _matches "${_matches} + 1")
|
||||
|
||||
list(APPEND _replaced "${generated}")
|
||||
else()
|
||||
list(APPEND _replaced "${_source}")
|
||||
endif()
|
||||
endforeach()
|
||||
if(NOT _matches EQUAL 1)
|
||||
message(FATAL_ERROR "Security override: ${component} needs exactly one ${original}; found ${_matches}")
|
||||
endif()
|
||||
|
||||
# Target flags/includes/definitions are retained because the target is not
|
||||
# replaced. Preserve source-specific properties in the owning directory too.
|
||||
set(_properties COMPILE_FLAGS COMPILE_OPTIONS COMPILE_DEFINITIONS
|
||||
INCLUDE_DIRECTORIES OBJECT_DEPENDS OBJECT_OUTPUTS LANGUAGE
|
||||
SKIP_PRECOMPILE_HEADERS SKIP_UNITY_BUILD_INCLUSION
|
||||
SKIP_LINTING HEADER_FILE_ONLY)
|
||||
set(_configs DEBUG RELEASE RELWITHDEBINFO MINSIZEREL
|
||||
${CMAKE_CONFIGURATION_TYPES} ${CMAKE_BUILD_TYPE})
|
||||
foreach(_config IN LISTS _configs)
|
||||
string(TOUPPER "${_config}" _config)
|
||||
list(APPEND _properties "COMPILE_DEFINITIONS_${_config}")
|
||||
endforeach()
|
||||
foreach(_property IN LISTS _properties)
|
||||
get_property(_is_set SOURCE "${_expected}" DIRECTORY "${_source_dir}"
|
||||
PROPERTY "${_property}" SET)
|
||||
if(_is_set)
|
||||
get_property(_value SOURCE "${_expected}" DIRECTORY "${_source_dir}"
|
||||
PROPERTY "${_property}")
|
||||
set_property(SOURCE "${generated}" DIRECTORY "${_source_dir}"
|
||||
PROPERTY "${_property}" "${_value}")
|
||||
endif()
|
||||
endforeach()
|
||||
get_filename_component(_original_dir "${original}" DIRECTORY)
|
||||
get_property(_includes SOURCE "${generated}" DIRECTORY "${_source_dir}"
|
||||
PROPERTY INCLUDE_DIRECTORIES)
|
||||
# Restore the implicit quoted-include search directory lost by relocating C.
|
||||
set_property(SOURCE "${generated}" DIRECTORY "${_source_dir}"
|
||||
PROPERTY INCLUDE_DIRECTORIES "${_original_dir};${_includes}")
|
||||
set_property(TARGET "${_target}" PROPERTY SOURCES "${_replaced}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${original}" "${generated}")
|
||||
message(STATUS "Security override: ${component}: ${original} -> ${generated}")
|
||||
endfunction()
|
||||
|
||||
# HandshakeInfo and WOLFSSH are ABI-sensitive. Force the same generated header
|
||||
# into the library AND every transitive consumer, even if a consumer adds a
|
||||
# vendor include directory ahead of the overlay. The original include guard
|
||||
# then prevents a second, stale definition. This also creates compiler deps.
|
||||
foreach(_sak_security_id IN LISTS SAK_SECURITY_HEADER_IDS)
|
||||
if(NOT _sak_security_id STREQUAL "wolfssh_internal_header" OR
|
||||
NOT SAK_SECURITY_${_sak_security_id}_COMPONENT STREQUAL "wolfssl__wolfssh")
|
||||
message(FATAL_ERROR "Security override: unaudited header overlay")
|
||||
endif()
|
||||
idf_component_get_property(_sak_security_wolfssh wolfssl__wolfssh COMPONENT_LIB)
|
||||
if(NOT TARGET "${_sak_security_wolfssh}")
|
||||
message(FATAL_ERROR "Security override: missing wolfSSH overlay target")
|
||||
endif()
|
||||
target_include_directories("${_sak_security_wolfssh}" BEFORE PUBLIC
|
||||
"${SAK_SECURITY_WOLFSSH_INCLUDE}")
|
||||
# PlatformIO's ESP-IDF adapter sorts app flags and deduplicates component
|
||||
# flags. Keep option+operand atomic, as with wolf_crypto_policy.h.
|
||||
target_compile_options("${_sak_security_wolfssh}" PUBLIC
|
||||
"-include${SAK_SECURITY_${_sak_security_id}_GENERATED}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${SAK_SECURITY_${_sak_security_id}_ORIGINAL}"
|
||||
"${SAK_SECURITY_${_sak_security_id}_GENERATED}")
|
||||
endforeach()
|
||||
|
||||
foreach(_sak_security_id IN LISTS SAK_SECURITY_OVERRIDE_IDS)
|
||||
sak_security_replace_source(
|
||||
"${SAK_SECURITY_${_sak_security_id}_COMPONENT}"
|
||||
"${SAK_SECURITY_${_sak_security_id}_ORIGINAL}"
|
||||
"${SAK_SECURITY_${_sak_security_id}_GENERATED}"
|
||||
"${SAK_SECURITY_${_sak_security_id}_TARGET}")
|
||||
endforeach()
|
||||
@@ -0,0 +1,14 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
# Apply after project(). PUBLIC propagates the resolved-settings guard to every
|
||||
# wolfSSL consumer, including wolfSSH and the application (ABI-sensitive keys).
|
||||
idf_component_get_property(_sak_wolf_target wolfssl__wolfssl COMPONENT_LIB)
|
||||
if(NOT TARGET "${_sak_wolf_target}")
|
||||
message(FATAL_ERROR "wolf crypto policy: missing wolfSSL component target")
|
||||
endif()
|
||||
# Existing upstream checks, not a vendor-source backport. PUBLIC keeps library
|
||||
# and consumer settings consistent without changing the root build file.
|
||||
target_compile_definitions("${_sak_wolf_target}" PUBLIC
|
||||
WOLFSSL_VALIDATE_ECC_IMPORT
|
||||
WOLFSSL_ECDHX_SHARED_NOT_ZERO)
|
||||
target_compile_options("${_sak_wolf_target}" PUBLIC
|
||||
"-include${CMAKE_CURRENT_LIST_DIR}/wolf_crypto_policy.h")
|
||||
@@ -0,0 +1,43 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
#ifndef SAK_WOLF_CRYPTO_POLICY_H
|
||||
#define SAK_WOLF_CRYPTO_POLICY_H
|
||||
|
||||
#include <wolfssl/wolfcrypt/settings.h>
|
||||
|
||||
/* PR10133 recommends this existing check for older releases. In 5.8.2 the
|
||||
* software validator must not be replaced with a successful hardware stub. */
|
||||
#if !defined(HAVE_ECC) || !defined(WOLFSSL_VALIDATE_ECC_IMPORT) || \
|
||||
!defined(HAVE_ECC_CHECK_KEY)
|
||||
#error "wolf crypto policy: ECC requires validated imports"
|
||||
#endif
|
||||
#if defined(NO_ECC_CHECK_PUBKEY_ORDER) || defined(WOLF_CRYPTO_CB_ONLY_ECC) || \
|
||||
defined(WOLFSSL_ATECC508A) || defined(WOLFSSL_ATECC608A) || \
|
||||
defined(WOLFSSL_CRYPTOCELL) || defined(WOLFSSL_SILABS_SE_ACCEL) || \
|
||||
defined(WOLFSSL_SE050) || defined(WOLFSSL_STM32_PKA)
|
||||
#error "wolf crypto policy: review ECC validation backend before changing it"
|
||||
#endif
|
||||
#ifndef WOLFSSL_ECDHX_SHARED_NOT_ZERO
|
||||
#error "wolf crypto policy: X25519 requires all-zero shared-secret rejection"
|
||||
#endif
|
||||
|
||||
/* PR9275 selects small math on Xtensa to avoid compiler-introduced timing
|
||||
* differences. Check resolved settings, not just command-line intentions.
|
||||
* https://github.com/wolfSSL/wolfssl/pull/9275
|
||||
*/
|
||||
#if !defined(HAVE_CURVE25519) || !defined(CURVE25519_SMALL)
|
||||
#error "wolf crypto policy: X25519 requires CURVE25519_SMALL"
|
||||
#endif
|
||||
#if !defined(HAVE_ED25519) || !defined(ED25519_SMALL)
|
||||
#error "wolf crypto policy: Ed25519 requires ED25519_SMALL"
|
||||
#endif
|
||||
/* 5.8.2 excludes small math from automatic blinding and rejects this pairing.
|
||||
* Do not force blinding back on: key layout and function signatures differ.
|
||||
*/
|
||||
#ifdef WOLFSSL_CURVE25519_BLINDING
|
||||
#error "wolf crypto policy: small X25519 is incompatible with blinding"
|
||||
#endif
|
||||
#if defined(HAVE_CURVE448) || defined(HAVE_ED448)
|
||||
#error "wolf crypto policy: review PR9275 small math before enabling 448"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -58,7 +58,7 @@ dependencies:
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.5.0
|
||||
version: 5.5.3
|
||||
wolfssl/wolfssh:
|
||||
component_hash: 24d623360c07374a90b1ade8d1218b24bb7b661fd51ace9135dccf510a338927
|
||||
dependencies: []
|
||||
|
||||
@@ -78,7 +78,7 @@ UART RX -> serial-service RX stream -> broker task
|
||||
|
||||
The broker drains serial input even with no clients. A full client output stream drops only that client's copy and updates drop counters; it does not block UART reception or other clients.
|
||||
|
||||
Binary serial WebSocket output uses the IDF-5.5.0-pinned `web_httpd_ws_send_binary` adapter: one owner-only session-override send of a bounded header+payload copy (516 bytes of local scratch, 512-byte payload). Existing generation validation and one outstanding work item per slot remain required. Non-full sends immediately replace that session's send override with a reject-only guard, mark it closing and shut down the socket before deferred cleanup; this prevents automatic SDK control replies from reentering TLS after incomplete output. HTTPD retains TLS destruction ownership. Text/control and admin output retain the SDK sender. One send call does not imply one TLS record, packet or peer receipt; the user signed off drop-free230400-baud full-client-mix operation at160MHz. Runtime stack-margin validation remains a separate follow-up; evidence and acceptance scope are in `current-state.md`.
|
||||
Binary serial WebSocket output uses the IDF-5.5.3-pinned `web_httpd_ws_send_binary` adapter: one owner-only session-override send of a bounded header+payload copy (516 bytes of local scratch, 512-byte payload). Existing generation validation and one outstanding work item per slot remain required. Non-full sends immediately replace that session's send override with a reject-only guard, mark it closing and shut down the socket before deferred cleanup; this prevents automatic SDK control replies from reentering TLS after incomplete output. HTTPD retains TLS destruction ownership. Text/control and admin output retain the SDK sender. One send call does not imply one TLS record, packet or peer receipt; the user signed off drop-free230400-baud full-client-mix operation at160MHz. Runtime stack-margin validation remains a separate follow-up; evidence and acceptance scope are in `current-state.md`.
|
||||
|
||||
Active-client counter snapshots expose ID/type/pending/HWM/UART/queued/read/dropped. HWM is maintained independently of web tracing and counter clear seeds current occupancy. Read means transport handoff, not peer receipt. Disconnect removes the client row while global totals retain traffic and unread-output discards. Independent default-disabled web performance capture uses two fixed slot records and nonwrapping epoch/generation fences; toggles fence in-flight samples, disable freezes aggregates, clear preserves enable state. Binary-only timestamps bound reservation-entry to callback-entry before locking, synchronous send calls and completion to broker-read return; idle and intervening work prevent scheduler-only/backlog-at-completion claims. No new instrumentation allocations or scheduling/buffer changes. Exact fields, overhead and capture contracts: [throughput diagnostics](../web_throughput_diagnostics.md).
|
||||
|
||||
@@ -104,11 +104,11 @@ TinyUSB callbacks enqueue/copy data and state; the transport task owns broker li
|
||||
|
||||
### HTTPS, WebSocket, and web serial
|
||||
|
||||
`web_server` owns HTTPS on port 443 with a persisted self-signed P-256 identity and IDF's dual-stack listener. Shared Host/Origin parsing accepts canonical bracketed IPv6 literals (no interface zones) while preserving same-origin/session binding; DNS discovery does not confer certificate trust. `web_serial_transport` mediates two fixed WebSocket slots through the broker; HTTPD owns socket sends/close, the transport task owns broker IO. Four outstanding serial tickets, four cookie sessions, one optional admin WebSocket and six total HTTPD sockets are distinct limits; LRU is disabled. Current handler capacity is 40. Base HTTPS can serve authenticated non-WebSocket routes if optional serial/admin transport initialization fails.
|
||||
`web_server` owns HTTPS on port 443 with a persisted self-signed P-256 identity. Phase 9C uses exact-hash build-tree SDK corrections for failed-start/post-handshake TLS cleanup, copied-key wiping, HTTPD scratch retirement and TLS1.2 ECDHE-ECDSA AES-GCM-only server policy. Client defaults/global crypto are unchanged. The checked-in override registry plus pinned original, not installed source alone, define compiled behavior. [Source/ownership contract](../security_library_review.md). `web_serial_transport` mediates two fixed WebSocket slots through the broker; HTTPD owns socket sends/close, the transport task owns broker IO. Four outstanding serial tickets, four cookie sessions, one optional admin WebSocket and six total HTTPD sockets are distinct limits; LRU is disabled. Current handler capacity is 39. Base HTTPS can serve authenticated non-WebSocket routes if optional serial/admin transport initialization fails.
|
||||
|
||||
Cookie login/logout replaces Basic/cache. Digest-only records carry copied principals, CSRF state, absolute expiry and nonreused originating-session IDs. Strict same-origin/CSRF mutations and session/principal checks gate admission; logout invalidates its session before transport cleanup, account mutations invalidate only the affected account, and ongoing currentness is authoritative. Authentication initialization failure gates HTTPS; failed start/accepted stop wipes records. RNG/SHA/database calls run outside short spinlocks with post-call epoch/identity revalidation. [Authentication contract](../web_administration.md#authentication-and-admission).
|
||||
Cookie login/logout replaces Basic/cache. Digest-only records carry copied principals, CSRF state, absolute expiry and nonreused originating-session IDs. Strict same-origin/CSRF mutations and session/principal checks gate admission; logout invalidates its session before transport cleanup, account mutations invalidate only the affected account, and ongoing currentness is authoritative. Authentication initialization failure gates HTTPS; failed start/accepted stop wipes records. A non-consuming quota/epoch check rejects exhausted login requests before body receive; verification reservation remains post-parse and only that reservation charges the existing fixed window. RNG/SHA/database calls run outside short spinlocks with post-call epoch/identity revalidation. [Authentication contract](../web_administration.md#authentication-and-admission).
|
||||
|
||||
`web_httpd_adapter` is the sole private IDF 5.5.0 boundary for duplicate headers, admission-before-101, consumed-scratch wiping, staged optional URI registration, combined binary sends and owner-only idle sweeps. Re-audit its version guard on SDK upgrades. HTTPD debug logging must not expose headers/tickets. `web_diagnostics` independently observes public post-TLS callbacks using six metadata records and a default-disabled 32-event ring; it cannot see preaccept/in-progress/failed TLS. [Admission diagnostics](../web_admission_diagnostics.md).
|
||||
`web_httpd_adapter` is the sole private IDF 5.5.3 boundary for duplicate headers, admission-before-101, consumed-scratch wiping, staged optional URI registration, combined binary sends and owner-only idle sweeps. Re-audit its version guard on SDK upgrades. The 5.5.3 rebase retains private-layout/ownership contracts and adds a pinned WS receive correction so negative fixed-header reads cannot pass unsigned length comparisons; see [rebase evidence](../idf_553_rebase_review.md). HTTPD debug logging must not expose headers/tickets. `web_diagnostics` independently observes public post-TLS callbacks using six metadata records and a default-disabled 32-event ring; it cannot see preaccept/in-progress/failed TLS. [Admission diagnostics](../web_admission_diagnostics.md).
|
||||
|
||||
`web_httpd_idle` uses one one-second timer, six rows and at most one queued owner probe. Current-owner shutdown follows 15 seconds of observed ordinary idle, exempting actual WebSockets/async/pending input. Owner delays prevent hard timeout guarantees; accepted-but-lost work stays reserved until successful destruction, failed stop retains ownership. [Idle lifecycle contract](../https_idle_cleanup.md).
|
||||
|
||||
@@ -122,19 +122,13 @@ Cookie login/logout replaces Basic/cache. Digest-only records carry copied princ
|
||||
|
||||
`web_server_replace_identity` reserves service before identity and retains both through commit → reserved stop/start. Direct security and canonical CLI/browser-shell paths share task-bound nonreused identity reservations. Crypto/NVS run outside short security/service locks; commit precedes publication/wipe. Precommit failure leaves identity/HTTPD/logins unchanged; postcommit lifecycle failure never rolls back identity and can leave served/stored fingerprints different. Failed stop skips start and retains canonical recovery. Public service/security projections are separate observations, not authorization. [HTTPS ownership, generation and recovery contract](../web_administration.md#https-and-reboot).
|
||||
|
||||
### Admin firmware upload
|
||||
|
||||
`web_firmware_update` streams a raw admin-only application image through HTTPD into the inactive OTA slot using standard ESP-IDF APIs. Existing Origin/CSRF/session checks precede body IO; raw length is bounded before HTTPD's narrowed length is trusted, SDK image validation and final principal currentness precede boot selection. One 4KiB buffer and a transient reboot task bound application storage; NVS and partition layout are untouched. HTTPD is occupied during upload, so normal web traffic can stall; deadlines bound receive progress, not flash-operation latency.
|
||||
|
||||
The server transition and identity reservations exclude competing lifecycle work. A separate atomic gate excludes ordinary reboot paths without depending on HTTPS initialization, preserving UART0 recovery. Successful response schedules delayed reset while retaining reservations; failed response after selection retains the selected-image latch but releases resources for manual reboot. A subsequent upload is refused until reset. No automatic retry, rollback, signing infrastructure or dependency patching. [Contract and validation limits](../roadmap.md#phase-10--simple-admin-web-firmware-upload).
|
||||
|
||||
### SSH
|
||||
|
||||
Typed SSH settings use the existing ID dispatcher and original-login result slot, never HTTPD wolfSSH calls or owner waits. Conditional lifecycle/session controls compare a saturated service generation and exact nonreused session ID under canonical locks. `ssh_transport_replace_identity` reserves service then identity before stop, retaining the command mutex across stop → commit → conditional restart. Failed stop skips mutation/start; failed persistence may follow disconnection; committed identity is never rolled back after restart failure. Only the SSH owner frees context after all slots retire, and start rejects orphan handles. Direct security/CLI/deferred SSH callers share task-bound identity reservations; crypto/NVS run outside security locks. HTTPS remains available, so no self-cutting HTTP ACK gate is needed. [SSH contracts](../web_administration.md#ssh).
|
||||
|
||||
`ssh_transport` uses wolfSSH on port 22 with one explicitly dual-stack IPv6 wildcard listener and two fixed session/handshake slots. Peer formatting preserves IPv6 interface scope; IPv4 shares the listener, not a second socket. Initialization calls `wolfSSH_Init()` in the caller before task creation; after that, one owner task pinned to core 1 exclusively owns runtime contexts/sessions and wolfSSH calls. It enforces bounded handshakes, authentication attempts, receive work, and session buffers.
|
||||
`ssh_transport` uses wolfSSH on port 22 with two fixed session/handshake slots. Initialization calls `wolfSSH_Init()` in the caller before task creation; after that, one owner task pinned to core 1 exclusively owns runtime contexts/sessions and wolfSSH calls. It enforces bounded handshakes, authentication attempts, receive work, and session buffers. Phase 9B adds owner-only boot-lifetime token buckets for handshake admission, password/signed-key checks and unsigned probes; reconnect/service restart/counter clear do not reset them. Rate rejection closes the authenticating connection without sleeping the owner. Global starvation remains a tradeoff; see [admission policy](../security_hardening.md#9b-ssh-admission-and-credential-handling).
|
||||
|
||||
Authentication uses user-database passwords or stored Ed25519/ECDSA-P256 public keys. Public-key lookup authorizes a username/key pair, while wolfSSH verifies signed proof of possession. SSH host identity is a separate persisted P-256 key managed by `ssh_security`.
|
||||
Authentication uses user-database passwords or stored Ed25519/ECDSA-P256 public keys. Phase 9C applies explicit GCM/Curve25519/P-256 algorithm lists before context publication; policy failures discard the candidate. A source-pinned parser correction bounds password fields before callbacks and wipes the method payload afterward (synchronous project callbacks). Global wolfSSL memory hooks wipe retired usable allocations; shrink retains capacity, growth may require old and new blocks simultaneously. These hooks do not replace mbedTLS allocation. [Policy/limits](../security_library_review.md). Public-key lookup authorizes a username/key pair, while wolfSSH verifies signed proof of possession. SSH host identity is a separate persisted P-256 key managed by `ssh_security`. A pending-result marker gates exactly-once signed-key completion/currentness; the reviewed wolfSSH version/feature profile is guarded and keyboard-interactive has an explicit rejecting callback (advertisement is not a dispatch filter). Consumed admin staging bytes and retired slots are wiped; this is not a full library-memory wipe guarantee.
|
||||
|
||||
Routing follows the authenticated role:
|
||||
|
||||
@@ -185,15 +179,13 @@ For SSH, standard output/error is redirected to the invoking session's bounded o
|
||||
|
||||
Admin SSH `exit`, remote reboot, SSH stop/disconnect, and host-key rotate/reset use deferred control. The control task waits up to ten seconds for command state plus administration and transport application buffers to clear, then adds a short delay; this is a bounded best-effort heuristic, not peer-delivery confirmation. UART0 invokes these actions synchronously. User mutations and their revocations are not part of this mechanism. UART0 linenoise and the SSH editor consume the same manually maintained completion matcher and candidate formatter, so the two administration routes cannot drift in offered or displayed ambiguous completions; the hints can still drift from command registration and are not an authorization list.
|
||||
|
||||
Browser stop/reboot uses this same owner-adapter control path. Exact forced certificate rotation instead uses the typed queue union and immutable `dispatcher_actions` mask to hand off after drain/200 ms to the existing 12 KiB dispatcher, not the 4 KiB control stack. Pending input is discarded through execution and an executing slot remains reserved across self-detach. Canonical shared service/identity replacement preserves commit/stop/start failure semantics. Parsed other-account interactive add/password and forced role/delete are allowed. Canonical Wi-Fi commands are also allowed, including hidden secret prompts and disruptive changes; these use the ordinary owner queue and may cut off the browser before output arrives, without a deferred acknowledgement guarantee. Browser self/generated/key/recovery, mDNS mutations and restricted SSH shell actions remain denied; typed Settings has separate permissions. Post-prompt currentness is operation admission, not an atomic session-liveness/NVS guarantee. [Browser policy and recovery](../web_administration.md#browser-shell-policy).
|
||||
Browser stop/reboot uses this same owner-adapter control path. Exact forced certificate rotation instead uses the typed queue union and immutable `dispatcher_actions` mask to hand off after drain/200 ms to the existing 12 KiB dispatcher, not the 4 KiB control stack. Pending input is discarded through execution and an executing slot remains reserved across self-detach. Canonical shared service/identity replacement preserves commit/stop/start failure semantics. Parsed other-account interactive add/password and forced role/delete are allowed, but browser self/generated/key/recovery and restricted network/SSH shell actions are not; typed Settings has separate permissions. Post-prompt currentness is operation admission, not an atomic session-liveness/NVS guarantee. [Browser policy and recovery](../web_administration.md#browser-shell-policy).
|
||||
|
||||
## Wi-Fi and persistence
|
||||
|
||||
`wifi_config` owns a fixed-width versioned NVS schema with four prioritized station profiles and AP policy `off`, `fallback`, or `always`. Missing configuration generates per-device defaults including a random AP password. Invalid stored data is generally left untouched while RAM defaults are used.
|
||||
|
||||
`wifi_manager` is a permanent task with one bounded command/event queue. ESP-IDF callbacks only copy compact events into the queue. The task owns association, DHCP/address deadlines, profile failover, AP policy, retries/backoff, next-profile requests, and the mDNS announcement lifecycle. One-second reconciliation reads authoritative driver/netif state in TCP/IP context, enables STA SLAAC and creates link-local addresses without restarting DAD. IPv4 or preferred IPv6 (including link-local-only) establishes ONLINE and may retire the fallback AP; ONLINE does not imply a default route or Internet access. DHCPv4 option 12 carries the full `sak-<suffix>` from permanent bounded hostname storage installed before connection; rename updates subsequent exchanges without restarting DHCP. The bounded snapshot retains up to three preferred numeric IPv6 addresses from the same observation as its availability flags; compile-time capacity checks reject larger lwIP address-slot settings. `wifi status` formats these addresses and their scope. Web Network settings encode up to three fixed-width strings from the same snapshot and display link-local/ULA/GUA groups within a 2304-byte JSON bound; the overview/OLED retains availability-only reporting.
|
||||
|
||||
`mdns_service` initializes the responder at most once after either-family STA readiness. Initialization failure is latched rather than retried because partial upstream low-memory initialization is not safely recoverable; mDNS failure is nonfatal. HTTPS/SSH owners publish availability under a short portMUX, and only the Wi-Fi owner reconciles the two STA-only DNS-SD records and address families, including while offline. Component calls run outside project service mutexes. Missing-family disable is reasserted every pass and present-family enable/reprobe every 30 seconds because upstream actions can silently drop and expose no readiness acknowledgement. This is eventual convergence, not instantaneous withdrawal: a transient stale/zero-A window can occur after DHCP loss, and AAAA follows upstream valid-address semantics including deprecated addresses. A version/hash-guarded build-local mDNS 1.12.0 source overlay balances per-family multicast membership and join cleanup; managed source stays immutable. Tests and upgrade/removal contract: `tests/mdns_membership/README.md`. ESP-IDF Wi-Fi storage is RAM-only; the application blob is authoritative, and edits require explicit save. Edits to disabled station profiles are staged in RAM without restarting the radio; enabling/disabling a profile or changing enabled station/AP policy restarts it asynchronously. Start/stop—including local controls—intentionally update the RAM `enabled_at_boot` field. Working-configuration copies contain PSKs and must be securely wiped; routine status and the local UI use secret-free snapshots.
|
||||
`wifi_manager` is a permanent task with one bounded command/event queue. ESP-IDF callbacks only copy compact events into the queue. The task owns association, DHCP deadlines, profile failover, AP policy, retries/backoff, next-profile requests, and the mDNS announcement lifecycle. `mdns_service` initializes the responder at most once after a validated STA `GOT_IP`; the managed component's own event handlers withdraw and restore the STA announcement across transient connectivity changes, while the project tracks whether announcement is currently expected. Initialization failure is latched rather than retried because partial upstream low-memory initialization is not safely recoverable; mDNS failure is nonfatal. It also reconciles against authoritative driver/netif state so dropped events do not permanently wedge policy. ESP-IDF Wi-Fi storage is RAM-only; the application blob is authoritative, and edits require explicit save. Edits to disabled station profiles are staged in RAM without restarting the radio; enabling/disabling a profile or changing enabled station/AP policy restarts it asynchronously. Start/stop—including local controls—intentionally update the RAM `enabled_at_boot` field. Working-configuration copies contain PSKs and must be securely wiped; routine status and the local UI use secret-free snapshots.
|
||||
|
||||
Persistent namespaces/blobs include:
|
||||
|
||||
@@ -239,4 +231,4 @@ Hardware diagnostics are synchronous console commands. RS-232 tests own the phys
|
||||
|
||||
`user_database_init(load_result)` has no credential input. Missing storage is persisted empty; `user_database_recover_empty()` is the unavailable-only destructive recovery API. Valid v1 user bytes load without rewriting or account changes. The private `v1_admin_marker` retains its byte position and is derived from administrator count during mutations; it is not a public bootstrap state, new role or schema change. No user migration/bootstrap/synchronization API remains.
|
||||
|
||||
`web_security` owns TLS only. A private reader validates 1,392-byte v1 `web_sec/material`, copies exact key/certificate DER, fingerprint and generation into 1,340-byte v2, commits, then publishes. Temporary v1 credential-bearing input is wiped; no public legacy credential type/getter/rotation remains. Malformed/unknown records and read/validation/commit failures fail closed, with no fallback regeneration or overwrite of rejected records. Missing material may be generated; explicit reset replaces TLS only. Downgrade to v1-only firmware is incompatible. Logical NVS replacement is not secure flash erasure. Contracts/evidence: [legacy compatibility](../roadmap.md#phase8-legacy-credential-compatibility).
|
||||
`web_security` owns TLS only. A private reader validates 1,392-byte v1 `web_sec/material`, copies exact key/certificate DER, fingerprint and generation into 1,340-byte v2, commits, then publishes. Temporary v1 credential-bearing input is wiped; no public legacy credential type/getter/rotation remains. Malformed/unknown records and read/validation/commit failures fail closed, with no fallback regeneration or overwrite of rejected records. Missing material may be generated; explicit reset replaces TLS only. Downgrade to v1-only firmware is incompatible. Logical NVS replacement is not secure flash erasure. Contracts/evidence: [legacy compatibility](../legacy_credential_removal.md).
|
||||
|
||||
@@ -12,11 +12,28 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
- Dependencies: every subsystem initializer
|
||||
- Lifecycle constraint: optional display/network failures should not remove UART0 administrative recovery or USB UART1 access; the custom administration frontend starts only after command registration.
|
||||
|
||||
## Small Phase 9 operating baseline
|
||||
## Crash/debug build policy
|
||||
|
||||
- `sdkconfig.defaults` explicitly selects no core dumps and silent panic reboot; existing generated configs can override defaults. README documents verification. No dependency overlays, custom crypto policy or version changes are part of this phase.
|
||||
- User accepted the reduced Phase 9 on 2026-09-18 and waived a new hardware check; this is not a claim of a new hardware-test pass.
|
||||
- [Operational checklist](../security_operations.md) and [scope/acceptance](../roadmap.md#phase-9--security-and-production-hardening): intermittent trusted-network use, unchanged authentication/recovery, no physical-extraction protection. Keep upgrades separate and do not restore the abandoned extensive patchset.
|
||||
- Files: `src/security_build_policy.c`, registration in `src/CMakeLists.txt`, diagnostic flags in `sdkconfig.defaults`; tests: `tests/security_build_policy/run.py` (optional `--sdkconfig-header` checks the generated configuration).
|
||||
- Compile-only guard: require no core dumps and silent panic reboot; reject panic/register output, panic/runtime GDB stubs and OCD-aware panic handling. No runtime allocation/task or physical JTAG restriction. Policy, operational profiles and target gates: [Phase 9 hardening](../security_hardening.md).
|
||||
|
||||
## Source-pinned dependency corrections (Phases 9C–9D)
|
||||
|
||||
- Files: root `CMakeLists.txt` (after `project()`), `cmake/security_overrides.cmake`, `tools/security_overrides.py`; tests: `tests/sdk_security_overrides/run.py --build-dir .pio/build/esp32-s3-devkitc-1-n16r8`.
|
||||
- Build input is the exact-hash original **plus checked-in edits**, not installed source alone. Generated copies replace nine target C sources plus one PUBLIC forced header without modifying SDK/managed components: HTTPS cleanup/private-key release, HTTPD scratch lifetime/null first read, ESP-TLS server-only protocol list, wolfSSH password bounds/payload wiping plus bounded IGNORE/service/string parsing, channel-window overflow rejection and ECC/Ed25519 label/exact-signature framing, DHCP option bounds (CVE-2026-45160), TLS 1.2 EMS error propagation (CVE-2026-50581), X.509 OID allocation failure (CVE-2026-34874), and five signed WS fixed-header size comparisons on IDF 5.5.3 (982 cases / 10 mutation checks against generated code). The mbedTLS entries explicitly select validated nested `mbedtls`/`mbedx509` targets, not the component wrapper. Original notices and compile properties retained; all outputs carry the 2026-09-15 baseline modification notice and wolfSSH outputs add the 2026-09-16 ordering/provenance notice; source/hash/target ambiguity fails configuration. Never hand-edit generated copies or silently repin.
|
||||
- Xtensa crypto policy: root `CMakeLists.txt` sets `CURVE25519_SMALL`/`ED25519_SMALL` before component parsing; `cmake/wolf_crypto_policy.cmake` PUBLIC-propagates `cmake/wolf_crypto_policy.h` to wolfSSL consumers. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` enable existing P-256 import and X25519 all-zero-result checks. The resolved-settings guard requires these checks and both small implementations, rejects reviewed ECC validator-disabling/hardware-stub configurations, X25519 blinding and unreviewed Curve448/Ed448 enablement. Tests: `tests/wolf_crypto_policy/run.py` (strict actual production flags, real vendor crypto/ASN vectors and independently specified exact source deltas; candidate injection is not production evidence). [Key-validation review](../ssh_key_validation_review.md) records effective flags, caller/API limits and unmeasured validation cost. [Wolf review](../wolf_security_review.md) distinguishes implemented restricted ordering/crypto/parser mitigations from pending target gates and explicit dormant-profile exclusions; [IDF review](../idf_security_review.md) records three backports, finite six-finding applicability completion and first-page feature exclusions, not exhaustive advisory safety.
|
||||
- Parser tests: `tests/wolfssh_parser_contract/run.py`, [scope and exclusions](../../tests/wolfssh_parser_contract/README.md): 3,258 base cases per each of two stack modes plus five channel profiles in both modes (2,737/profile/mode for TERM, TERM+SHELL, TERM+SHELL+AGENT; 2,735 for neither/SHELL-only); 11 base + 18 name/length + 2 application-gate mutations rejected. Guard pages/UBSan and crypto doubles test parsing/gating, not arithmetic. [Finite review](../ssh_parser_remaining_review.md) closes exact CHANNEL_FAILURE recipient, ssh-userauth service and nine channel-name dispatch checks; PR899 client skips remain unchanged behind role/ordering gates, forwarding disabled, generic caller trace dispositioned but APIs unpatched. Unknown/trailing behavior is retained; not exhaustive safety. Ordering has its separate suite below.
|
||||
- Historical IDF 5.5.0 supplied parent `pio run` PASS: **94,340 B linked RAM / 1,768,901 B flash (+200 B versus 1,768,701 B)**. Final parent **all seven suites PASS**: ordering `--interop` (8,028 checks / seven rejected mutations / 12 exact-256-KiB sessions, clean channel close and transport EOF), SDK overrides `--build-dir .pio/build/esp32-s3-devkitc-1-n16r8`, auth (135), protocol, strict crypto, notices (30), and parser (3,258 × two modes plus channel profiles; 11 + 18 + 2 rejected mutations). Interop used unsandboxed approval only for local AF_UNIX sockets; no remote network/device operation. Review's misplaced EOF guard is corrected in `SendChannelEof` before lookup/serialization/state mutation; follow-up verification found no scoped blocker. Not target/runtime-reserve evidence.
|
||||
- Restricted **CVE-2025-14942** ordering correction implemented: `tools/wolfssh_order/delta.json` supplies exact executable edits to `internal.c`, `ssh.c` and `internal.h`; [README](../../tools/wolfssh_order/README.md) records PR793/819/840/855/921 prerequisite disposition, and `provenance.json` pins archived patch hashes/commit IDs. Existing X25519/P-256 KEX only; independent SELF/PEER bits, expected-message/auth gates and exactly-once queued NEWKEYS under WANT_WRITE. **EXT_INFO intentionally disabled, no `server-sig-algs`; `extInfoSent` stays zero.** No full upstream backport or dependency upgrade. CMake propagates the generated ABI header BEFORE PUBLIC plus PUBLIC forced include with stale-header rejection; joined `-include/path` flags preserve ordering/crypto guards through PlatformIO sorting/deduplication.
|
||||
- Ordering tests: [tests/wolfssh_order_contract](../../tests/wolfssh_order_contract/README.md), full generated C/real wolfCrypt, **8,028 checks / seven rejected mutations**; `pio_adapter.py` validates installed PlatformIO/SCons flags with a real Xtensa consumer. Initial interop failed a harness close race despite early PASS output; fixed harness awaits peer close and transport EOF and independently reaps the server via fd-passing proxy. Final agent `--interop --interop-repeat 3`: **36/36 sessions**, exact **256 KiB** each, **two or ten** key exchanges and clean client/server exits. [Remaining gates](../wolf_security_review.md#restricted-ordering-correction-and-remaining-gates): target cleanup during rekey, no-EXT_INFO client compatibility, resource/timing and whole-phase validation; no device operation or phase sign-off.
|
||||
- [Finite dependency advisory snapshot](../dependency_advisory_coverage.md): all 22 IDF index entries across three pages plus named managed/browser channels accounted for; declared search complete, not exhaustive clearance. **Fix-bearing Wi-Fi vendor bundle integrated in the validated IDF 5.5.3 candidate; radio-hardware closure pending**; [Wi-Fi update plan](../wifi_security_update_plan.md) requires coherent vendor framework/toolchain/source/header/binary ABI bundles and override revalidation, not PMF/WPA3 weakening. Residual applicability questions and target gates remain.
|
||||
- [Bounded Phase 9 runner](../../tools/validate_phase9.py), [usage/evidence](../phase9_validation.md), fixtures `tests/phase9_validation/run.py`: 23 default commands; build and OpenSSH interop are independent opt-ins. After the owning agent fixed the stale console-boundary anchor, final supplied parent `CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py` **PASS 23/23** and orchestrator fixtures **PASS 10 tests**. That default run is historical. Current supplied parent explicit-candidate validation with `--interop --web-performance` **PASS 24/24**; [exact command and pre/post source equality](../phase9_validation.md#current-candidate-execution--2026-09-18). Fresh isolated `app-validated` build PASS: **95,552 B RAM / 1,749,493 B flash (+1,212 / −19,408 B)**. Default root build timed out at 200 seconds during installation before compilation, not a root build PASS. Root now pins platform 6.13.0 / IDF 5.5.3 / both toolchains 14.2.0+20251107; [integration](../idf_candidate_integration.md), [semantic rebase and resolved historical WS blockers](../idf_553_rebase_review.md). Version pins are not complete immutable dependency closure or phase acceptance.
|
||||
- Policy/evidence/limits: [library review](../security_library_review.md), [Phase 9C](../security_hardening.md#9c-library-cleanup-and-protocol-policy). Source-contract tests must locate and verify actual generated compilation inputs, not assume original vendor paths.
|
||||
|
||||
## Offline release notice assembly
|
||||
|
||||
- [Procedure and release gates](../release_packaging.md), [tool](../../tools/release_notices.py), catalog `third_party/release-notices/inputs.json`, scope/TLSF provenance under `third_party/release-notices/`; tests: `tests/release_notices/run.py` (supplied agent 36 fixture tests PASS). 75 explicit hash/size-pinned inputs (nine IDF/toolchain notice entries semantically rebased for the candidate; the other 66 unchanged); no recursive collection, fetch, build, asset regeneration or device access; output must be fresh and outside input roots.
|
||||
- Supplied agent evidence: two actual 75-input bundles verified deterministic, each **77 files / 4,433,930 bytes**; no reassembly here. [Exact retained SVG provenance](../icon_provenance.md) resolves official 7.4.47 sources at `9e04201d4557e729822fb57f62a316c3dea1d4a8` (Google USB, Simran Wi-Fi); manual bitmap derivation and distinct mockup Wi-Fi path remain unresolved. The catalog pins `docs/icon_provenance.md`, not this code map or the edited overview docs; coordinate changes to pinned evidence, never silently repin. Recipient delivery, corresponding source, legal/radio-blob basis and whole-phase target gates remain open. A notice manifest is not a source archive, complete SBOM or release clearance.
|
||||
|
||||
## Secure randomness
|
||||
|
||||
@@ -70,8 +87,9 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
- Files: `src/web_server.{h,c}`, `src/web_serial_transport.{h,c}`, `src/web_ui.{h,c}`, `src/web_console.{h,c}`
|
||||
- Ordinary HTTPS idle cleanup: `src/web_httpd_idle.{c,h}`, owner sweep in `web_httpd_adapter.{c,h}`, lifecycle/TLS composition in `web_server.c`; `tests/web_httpd_idle/run.py`. Independent of diagnostics/optional transports: 15-second observed idle, one-second timer/one queued probe, six rows, actual WS/async/pending exemptions, safe current-owner shutdown and stop/restart fencing. No LRU/socket/timeout/stack increase. SDK queue/owner-delay and regression contract: `docs/https_idle_cleanup.md`.
|
||||
- Independent throughput diagnostics: `web_serial_transport.{c,h}` owns two fixed per-slot binary-TX aggregates and epoch fences; `web_console.c` exposes default-disabled `web performance enable|disable|show|clear`. Queue-entry/callback-entry, synchronous-send and completion/drain-return estimates, not peer receipt or scheduler-only latency. `tests/web_serial_performance/run.py`; resource/evidence limits and UART0 paired capture: `docs/web_throughput_diagnostics.md`.
|
||||
- Storage compatibility: `user_database` persists missing storage empty and preserves valid v1 user bytes; private derived `v1_admin_marker`, no public bootstrap/migration/sync APIs. `web_security` privately migrates v1 1392-byte material to TLS-only v2 1340-byte material, exact identity/generation retained, commit before publish, fail closed without fallback overwrite. Credential commands removed; user generated passwords and TLS rotation remain. Contracts, downgrade and evidence limits: [legacy compatibility](../roadmap.md#phase8-legacy-credential-compatibility).
|
||||
- Storage compatibility: `user_database` persists missing storage empty and preserves valid v1 user bytes; private derived `v1_admin_marker`, no public bootstrap/migration/sync APIs. `web_security` privately migrates v1 1392-byte material to TLS-only v2 1340-byte material, exact identity/generation retained, commit before publish, fail closed without fallback overwrite. Credential commands removed; user generated passwords and TLS rotation remain. Contracts, downgrade and evidence limits: `docs/legacy_credential_removal.md`.
|
||||
- Security files: `src/web_security.{h,c}`, `src/web_cookie_auth.{h,c}`, `src/web_session_store.{h,c}`, `src/web_auth_parse.{h,c}`. Private IDF boundary: `src/web_httpd_adapter.{h,c}`.
|
||||
- Phase 9C web login: non-consuming early quota/epoch probe before body receive, authoritative reservation after parse; raw JSON wiped before KDF, credentials before error send. Existing verification-count/window/service-restart semantics retained. `tests/web_cookie_auth/run.py --admission` and domain regressions.
|
||||
- HTTP policy/UI: `web_cookie_auth` + `web_auth_parse` enforce bounded cookie/Origin/CSRF/admin admission; `web_login_ui.{c,h}` serves login, `web_ui.c` owns session-fenced Serial/Admin/Settings and shared quick controllers. Tests: `tests/web_cookie_auth/run.py` (domain variants), `tests/web_auth_parse/run.py`, `tests/web_login_ui/run.py`, `tests/web_ui_session/run.py`.
|
||||
- Admission diagnostics: `web_diagnostics.{c,h}`, `tests/web_diagnostics/run.py`; six post-TLS records/32-event opt-in ring, no HTTPD off-owner inspection. [Contract](../web_admission_diagnostics.md).
|
||||
- Identity/lifecycle: `web_server_replace_identity()` + `web_security` reserve service before identity; commit before reserved stop/start, no rollback after commit. `web_lifecycle_settings.{c,h}` owns original-login ID/ACK handoff. Tests: `tests/web_security/run.py`, `tests/web_admin_transport/server_lifecycle.py`.
|
||||
@@ -88,16 +106,10 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
### Browser admin backend
|
||||
|
||||
- Files: `src/web_admin_transport.{c,h}`, `src/web_admin_tickets.{c,h}`, protected registration/lifecycle in `web_server.c`, revocation through `web_serial_transport_revoke_*`, diagnostics in `web_console.c`.
|
||||
- Routes: CSRF-protected admin-only `POST /api/admin/ws-ticket`; ordinary `GET /ws/admin` with cookie/Origin/ticket/shared-console admission before explicit 101. Admin UI entry is explicit; no admin broker client. One socket, two tickets, existing two shared console slots; six total HTTPD sockets, LRU disabled; current overall capacity is 40 URI handlers.
|
||||
- Routes: CSRF-protected admin-only `POST /api/admin/ws-ticket`; ordinary `GET /ws/admin` with cookie/Origin/ticket/shared-console admission before explicit 101. Admin UI entry is explicit; no admin broker client. One socket, two tickets, existing two shared console slots; six total HTTPD sockets, LRU disabled; current overall capacity is 39 URI handlers.
|
||||
- Currentness/policy: `admin_ssh_console_open_available()` shares two slots with runtime SSH; transport-qualified tokens and owner adapters revalidate outside console locks before commands/prompts. Parsed browser policy remains narrower than typed Settings; [shell contract](../web_administration.md#browser-shell-policy). Tests: `tests/admin_console_boundary/{run,accounts,lifecycle}.py`, `tests/admin_ssh_policy/run.py`, `tests/web_admin_transport/run.py --tickets`, `tests/web_cookie_auth/run.py --admin`.
|
||||
- Ownership: 20 ms ESP timer queues at most one HTTPD poll, no new task; HTTPD owns 1,552 B PSRAM-only payload and IO. Closure uses HTTPD-owned `shutdown`, not IDF's reusable-pointer queued close. Detach fences submitters; only successful HTTPD stop retires queued state before restart. Session/principal currentness and generation checks protect all sensitive boundaries.
|
||||
|
||||
## Firmware upload
|
||||
|
||||
- `src/web_firmware_update.{c,h}`: admin raw `POST /api/firmware`, cookie/Origin/CSRF admission via `web_cookie_auth`, registration in `web_server`, UI in `web_ui`. Standard SDK OTA into inactive app only; bounded4KiB buffer, validated raw length/header/final image and principal before boot selection. No NVS/layout writes or vendor patches.
|
||||
- HTTPD handles streaming synchronously; upload is deliberately disruptive to web traffic. Server/identity reservations fence lifecycle changes; atomic ordinary-reboot gate also covers console/SSH/browser/button reset paths. Success-response schedules delayed reset; response failure after selection latches uploads until deliberate manual reboot. No blind retries.
|
||||
- Tests: `tests/web_firmware_update/run.py` (active-build SDK5.5.0 headers and actual begin/abort contract), `tests/web_ui_session/run.py`, existing auth/lifecycle tests. [Usage, acceptance limits and reusable regression checks](../roadmap.md#phase-10--simple-admin-web-firmware-upload). Phase 10 complete by explicit user acceptance on 2026-09-18: upload works and normal operation verified; no specific fault/NVS-comparison/power-loss/recovery pass implied. Initial install by wire; subsequent upload is application `firmware.bin`, not full-flash image.
|
||||
|
||||
## Typed settings source and regression map
|
||||
|
||||
HTTPD reads zero-wait projections and queues only IDs to the existing dispatcher. One original-login slot per domain; canonical owners compare/reserve at execution. [API/lifetime and failure contracts](../web_administration.md#typed-settings-api-and-operation-lifetime).
|
||||
@@ -118,7 +130,9 @@ Shared UI regression: `tests/web_ui_session/run.py` and its domain `.cjs` fixtur
|
||||
|
||||
**Responsibility:** authenticate SSH, route users to serial and administrators to the command dispatcher, and own wolfSSH lifecycle.
|
||||
|
||||
- Files: `src/ssh_transport.{h,c}`, `src/ssh_security.{h,c}`, `src/ssh_console.{h,c}`
|
||||
- Files: `src/ssh_transport.{h,c}`, `src/ssh_auth_policy.{h,c}`, `src/ssh_memory.{h,c}`, `src/ssh_protocol_policy.{h,c}`, `src/ssh_security.{h,c}`, `src/ssh_console.{h,c}`
|
||||
- Phase 9C: global wolfSSL hooks securely retire unpoisoned IDF5.5 usable extents (no header; shrink retains capacity, grow can need both blocks). Five checked static-lifetime algorithm setters before context publication. Tests: `tests/ssh_memory/run.py` (optional `--idf-path` extent audit), `tests/ssh_protocol_policy/run.py` (generated feature/KEXINIT/context failure checks).
|
||||
- Phase 9B admission: three owner-only boot-lifetime token buckets (handshakes, password/signed-key requests, unsigned probes); no restart/counter-clear reset. Explicit keyboard rejection, pending-result marker and version/feature guard preserve reviewed callback order. Consumed admin RX/accepted TX and retired slots are wiped. Tests: `tests/ssh_auth_policy/run.py`, `tests/ssh_auth_transport/run.py`, `tests/wolfssh_auth_contract/run.py` (requires installed vendor source and production compile database). [Policy/counters/limits](../security_hardening.md#9b-ssh-admission-and-credential-handling).
|
||||
- Interfaces: init/start/stop, session snapshots/disconnect/revocation, host-key replacement, counters; `ssh_transport_get_management_snapshot()` / `ssh_transport_manage_current()` fence lifecycle and exact session admission. `ssh_transport_replace_identity()` reserves service before task-bound security identity across stop/commit/restart, retains context until all slots retire and rejects orphan starts. Tests: `tests/ssh_management/run.py`, `tests/ssh_management/security.py`, `tests/ssh_management/runtime.py`.
|
||||
- Called by: startup, network clients, user revocation, console/local UI
|
||||
- Dependencies: user database, broker, admin SSH console, secure random, wolfSSH/wolfSSL; boot start gate requires Wi-Fi and SSH security/runtime readiness, independently of HTTPS identity readiness (verified in `main.c` after accepted legacy cleanup).
|
||||
@@ -149,6 +163,7 @@ Shared UI regression: `tests/web_ui_session/run.py` and its domain `.cjs` fixtur
|
||||
- Flow: `UART0/admin SSH/browser admin -> bounded request queue -> one dispatcher -> esp_console_run()`
|
||||
- Ownership: dispatcher is sole `esp_console_run()` caller; the SSH owner exclusively performs post-initialization wolfSSH runtime calls.
|
||||
- Lifecycle: remote session tokens include slot generation; fixed output/history/prompt state is wiped immediately on idle close or after an executing handler returns. Admin SSH `exit` and Ctrl+D on an empty command line request bounded deferred self-disconnect after best-effort output draining.
|
||||
- Hidden input: UART0 and shared remote prompts reject overflow/unsupported bytes on submit with wiped output, sticky across edits; visible editing is unchanged. Tests: `tests/hidden_input/run.py` plus console boundary regressions.
|
||||
- Constraint: one slow command or prompt serializes all administration. Admin SSH is unavailable until command registration and UART frontend creation complete; supported deferred actions wait only for a bounded application-buffer drain heuristic.
|
||||
|
||||
## Wi-Fi
|
||||
@@ -158,9 +173,7 @@ Shared UI regression: `tests/web_ui_session/run.py` and its domain `.cjs` fixtur
|
||||
- Files: `src/wifi_config.{h,c}`, `src/wifi_manager.{h,c}`, `src/wifi_console.{h,c}`, `src/mdns_config.{h,c}`, `src/mdns_service.{h,c}`, `src/mdns_console.{h,c}`, `src/network_console.{h,c}`
|
||||
- Interfaces: config defaults/validate/load/save; manager init/start/stop/apply/reconnect/next-profile/snapshot
|
||||
- Dependencies: secure random for default AP password, NVS, ESP-NETIF/Wi-Fi/events, Espressif mDNS, lwIP diagnostics
|
||||
- Lifecycle: permanent manager task and bounded queue; callbacks enqueue compact events only. One-second owner reconciliation handles DHCP hostname updates, preferred IPv4/IPv6 readiness, missed events and mDNS records/families. `ONLINE` includes preferred link-local-only IPv6 and may retire fallback AP without IPv4; snapshots retain IPv6 availability flags plus up to three preferred numeric addresses from the same observation. `wifi status` and web Network settings render those addresses; overview/OLED remain availability-only. The typed runtime adds `ipv6_addresses` (up to three fixed-width strings); backend and UI share a 2304-byte response bound. Regression coverage includes byte order, address clearing and the compile-time slot-capacity bound.
|
||||
- Phase 12: `mdns_service_set_https_available()` / `mdns_service_set_ssh_available()` publish under a short portMUX; only the Wi-Fi owner calls component APIs. STA DNS-SD has two records; family repair re-probes available families every 30 seconds because upstream action admission has no reliable acknowledgement. AAAA uses valid (including deprecated) upstream addresses. DHCPv4 uses full `sak-<suffix>` before connection and in later exchanges after rename; permanent raw-netif hostname storage is updated only in TCP/IP context.
|
||||
- Focused tests: `tests/wifi_phase12`, `tests/mdns_phase12`, `tests/ssh_phase12`, `tests/mdns_membership`; HTTPS IPv6 authority cases live in `tests/web_auth_parse` and cookie suites. Build-local `cmake/mdns_membership.*` replaces only reviewed mDNS 1.12.0 networking source to balance multicast membership; version/hash drift fails configuration. No managed source is edited.
|
||||
- Lifecycle: permanent manager task and bounded queue; callbacks enqueue compact events only.
|
||||
- Constraint: application NVS is authoritative (`WIFI_STORAGE_RAM`); working edits are not persisted until save. Start/stop, including local controls, intentionally update the RAM `enabled_at_boot` field. Working-config copies contain PSKs and must be tightly scoped and wiped; routine status/local UI must use secret-free snapshots.
|
||||
|
||||
## Local display and controls
|
||||
|
||||
@@ -1,71 +1,126 @@
|
||||
# Current project state
|
||||
|
||||
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), [architecture](architecture.md) and [decisions](design-decisions.md).
|
||||
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), then [architecture](architecture.md) and [decisions](design-decisions.md).
|
||||
|
||||
## Phase 12 COMPLETE — explicit user validation
|
||||
## Phase 9D continuation — IDF 5.5.3 integration — 2026-09-18
|
||||
|
||||
- User states: “I just validated Phase 12 successfully.” Record acceptance, not a pending phase. Canonical device evidence and reusable regression guidance: [Phase 12](../roadmap.md#phase-12--advanced-network-integration). No unreported DHCP capture, DNS-zone update, exhaustive address/service transition, fault/soak or byte-integrity pass is implied.
|
||||
- User validated IPv6 ping/HTTPS and Avahi AAAA; Fedora authselect dual-family mDNS resolved the normal hostname lookup issue. Fresh-boot and full-mix captures show healthy reported service lifecycle. At 230400 baud 8N1 RTS/CTS: two SSH sessions with IPv6 ULA peers (user observer/admin console), two serial WebSockets (writer/observer), active browser admin, USB observer; four broker clients, one writer, zero pending/event counts at capture.
|
||||
- Memory table preserved in roadmap. Fresh internal8/DMA/PSRAM free: 65,880 / 58,124 / 8,196,732 B. Full-mix free: 35,408 / 27,652 / 8,111,952 B; lifetime minima: 8,528 / 772 / 8,072,612 B; largest blocks: 22,528 / 22,528 / 7,995,392 B. Capability pools overlap; minima are conservative per-region lifetime sums, not simultaneous reserves. DMA772 remains a watch item, not proof of OOM or an acceptance blocker. SSH stack20,480B minimum-free18,476→16,284B.
|
||||
- SSH two successful handshakes, no auth/handshake/timeouts/I/O failures; RX68 accepted65 rejected3, TX161,209B, broker revocations2. Web serial RX27B accepted, TX318,230B across782 frames; send/queue/protocol failures0. Browser admin RX106/TX3,721B with no reported send/queue failures. Do not reinterpret nonzero rejected bytes/writer denials as proven transport loss or claim zero UART/observer drops without their counters.
|
||||
- Boot TLS -0x004C receive errors and auth-failure counts have no demonstrated cause. Snapshots are non-atomic and counts are not an aligned interval. This handoff changes documentation only; no new build/test/upload/erase/device operation/commit; hardware directory untouched. Prior latest build94,444B RAM/1,854,485B flash and UI174+CSP remain historical validation.
|
||||
- Initial working tree already contained platform6.13/framework3.50503/toolchains14.2.0+20251107 migration, exact version guards/hash rebases and new httpd_ws override; preserved/audited rather than overwrote. Complete official packages archived/verified under .pio/idf-candidate-5.5.3; all WiFi/PHY/coex/supplicant trees match pinned package. PMF/WPA3 unchanged. Vendor radio correction integrated in candidate, not hardware exploit/closure proof.
|
||||
- Existing eightC+header corrections all retained after semantic audit; new ninth C override corrects five WS signed-receive/unsigned-size comparisons introduced upstream. Dedicated982guard/canary cases plus fivecompile+fivebehavior mutations PASS. HTTPD private layout/owner and heap extent contracts re-audited5.5.3; no weakening guards. Root dependencies.lock updated only IDF5.5.0→5.5.3 to match actual resolver result; six managed dependencies unchanged.
|
||||
- Default root pio run timed out200s during sharedpackage installation before compilation; no normalrootbuildPASS claim/retry. Separate fresh app-validated stage uses complete verified archives and isolatedcore. Agent buildPASS50.51s **95,552 B linked RAM / 1,749,493 B flash** (+1,212RAM/−19,408flash vs old). Staging fresh-only explicitdestination avoids prior app overwrite; manifest includes transformedconfig hashes and records sole resolver lock change. All ninegeneratedC+PUBLICheader actualregistration verified; WS vendororiginal absent.
|
||||
- Final parent tools/validate_phase9.py with --build-dir .pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8 --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --platformio-core-dir .pio/idf-candidate-5.5.3/core --interop --web-performance **24/24PASS**. Pre/post3237sourceinputs equal hash3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031. Earlier5webcookie failures were stale5.5.0 fixture updated5.5.3; allpassnow. Parent helper5/runner16fixturesPASS. Independent recheck noactionableblocker, reran WS/performance/runner, verified archive/linkpaths/sourceequality/rootlock. No device validation.
|
||||
- Notice catalog semanticallyrebased9entries, other66unchanged;75inputs36testsPASS twoactualbundles77files4433930bytes. Evidence/docs updated with candidatenotrootbuild distinction and historical baselines. No full environmentclosure or legalclearance claim.
|
||||
- **Next:** normal package installation/build completion remains unverified after timeout; ask user before retrying longer. Isolated source/currentconfig technically validated; wholePhase9 radio/resource/recovery/noEXTclient/abuse/operations targetgates stillpending, release source/notice/legal obligations open. Candidate5.5.3 is newest available official PlatformIO5.5 package verified, not latest upstream maintenance or alladvisoriesclosed. No upload/erase/assets/secureboot/encryptedNVS changes or commits.
|
||||
|
||||
## Follow-up — IPv6 addresses in web Network settings
|
||||
## Phase 9D continuation — broader snapshot / reproducible validation — 2026-09-16
|
||||
|
||||
- User confirms direct IPv6 ping/HTTPS and Avahi AAAA lookup for `sak-1024.local` succeed. Client NSS uses `mdns4_minimal [NOTFOUND=return]`; systemd-resolved explicit mDNS reports no eligible networks. This supports a client resolver integration issue, not failed firmware AAAA publication on the tested Avahi path. The user subsequently enabled dual-family mDNS through Fedora authselect and confirmed success; see acceptance above.
|
||||
- Added bounded runtime `ipv6_addresses` (<=3 fixed-width lowercase eight-hextet strings) to existing admin Network snapshot, grouped as link-local/ULA/GUA in Settings. Overview/OLED remain flags-only. No new netif call, allocation, task or snapshot storage. Backend/client JSON bound 2048→2304 (+256B response stack); maximum-escaped fixture with all three addresses is 2067B. Strict UI validation/text-only output and existing session fences preserved; generated assets untouched.
|
||||
- `pio run` PASS **94,444 B linked RAM / 1,854,485 B flash**, +0/+800 versus prior CLI-address build. Network cookie/owner regressions and UI174+CSP PASS. Initial cookie fixture had an obsolete 2048B output buffer; fixed to use the production bound and rerun PASS. Empty/full address lists, byte order, bad counts, malformed/injected UI entries and clearing covered. No upload or hardware operations; hardware directory untouched.
|
||||
- Initial Git clean. `docs/dependency_advisory_coverage.md` completes finite dated search of all22official IDF advisories/3pages plus specified managed/browser channels. **New current-profile blocker:** PMF-capable mixed WPA2/WPA3 SoftAP matches vendor management-frame memory-corruption path; installed7S3WiFi archives match original5.5. No exploit demonstrated. `docs/wifi_security_update_plan.md` verifies fix/release bundle differences and both callback uint8_t*→size_t* ABI changes. Verified reference IDF5.5.3 commit2c211b236707889e8400c4dc5644dd5c4ee071e0 contains fix; not deployment approval or automatic recommendation over newer releases. Do not C-only patch/swap one archive/disable PMF as guessed mitigation. Next coherent framework/toolchain/source/header/radio update evaluation with audited override rebase; PHY/coexistence/privateABI compatibility unresolved. Direct SDK AES-DMA/PSRAM applicability remains explicit review gap.
|
||||
- `tools/validate_phase9.py` fixed23command host plan, optional --build/--interop, strict existing build inputs, timeouts/process cleanup, no installs/device commands/permanent log captures. New10fixture tests. Actual run revealed stale admin_console_boundary extraction ending at removed ssh_malloc; replaced with reviewed consume_external_close endpoint and eight unique ordered markers, all existing assertions retained.
|
||||
- **Final parent default validation23/23PASS and orchestrator fixtures10PASS.** Default intentionally skips build/interop; no firmware rebuild or new device/interoperability claim this slice. Prior firmware94340RAM1768901flash remains historical. Additional boundary accounts/lifecycle/psram suites passed agent. Run full opt-ins only deliberately with prerequisites.
|
||||
- Exact retained USB/WiFi SVGs verified byte-identical official@mdi/svg7.4.47 commit9e04201d4557e729822fb57f62a316c3dea1d4a8; archive source/license/metadata/tree hashes in third_party/material-design-icons/upstream-7.4.47 and docs/icon_provenance.md. USB Google/WiFi Simran; fullApache text present. Mockup WiFi differs and firmware bitmap manual derivation not mechanically proven. Notice catalog75inputs preserving oldinputs except editedscopeREADME pins,36testsPASS; two actual agent bundles77files4433930bytes deterministic. No assets regenerated.
|
||||
- Globaldocs/code map reflect fresh blocker, finite review boundaries and final parent results. Remaining release/source/radiolegal/wolf packaging/bitmap preferred-source delivery questions not cleared. Whole-phase hardware still deferred; secureboot/encryptedNVS excluded. No upgrades/device operations/commits. Continue concrete WiFi correction evaluation, not repeat completed finite snapshots.
|
||||
|
||||
## Follow-up — browser Wi-Fi controls and IPv6 diagnosis
|
||||
## Phase 9D continuation — deferred applicability / notice tooling — 2026-09-16
|
||||
|
||||
- User requests browser Admin shell Wi-Fi parity with SSH/typed settings. Removed only the Wi-Fi status-only policy gate; canonical settings/lifecycle/persistence/diagnostics and hidden secret prompts now work. mDNS remains status-only and unrelated restrictions stay intact. Disruptive commands may cut off the response; owner admission is not peer acknowledgement or cancellation on disconnect.
|
||||
- User now confirms the CLI displays an IPv6 address, direct IPv6 ping works, and the web interface opens using IPv6. This validates those reported unicast operations, not all Phase 12 checks. `ping sak-1024.local -6` fails on the client with “Die Adressfamilie für Hostnamen wird nicht unterstützt.” That earlier client failure was subsequently resolved through Fedora authselect dual-family mDNS; Avahi AAAA lookup and normal IPv6 hostname access succeeded. Do not reopen this as an established firmware discovery defect.
|
||||
- Added three bounded preferred numeric addresses to manager snapshot, copied/cleared with the same TCP/IP observation, and labelled output in `wifi status`. Web/OLED schema unchanged. +48 bytes per snapshot/settings copy and shared static storage; compile rejects >3 lwIP slots. No new allocation/task/socket. Latest `pio run` PASS **94,444 B RAM / 1,853,685 B flash** (+48/+660 vs initial Phase12). No upload/device operations.
|
||||
- PASS: browser policy, new actual Wi-Fi secret-handler/browser-prompt fixture (cancellation/revocation/wiping/history/role guards), account/lifecycle boundary, preferred-address lifecycle/byte-order/rendering/capacity, Network settings, cookie Network and UI171+CSP, diff check. Broader `admin_console_boundary/run.py` passes console/certificate then fails existing SSH-adapter compilation due missing `web_firmware_update_reserve_reboot` fake; left unrelated fixture unchanged. ASan/UBSan unavailable at host link. Hardware directory untouched.
|
||||
- Initial Git clean. `tools/security_overrides.py` now requires exact ssh-userauth service, exact bounded CHANNEL_FAILURE recipient with existing fatal policy, and length-first exact names for all nine channel requests. Supported bodies and unknown-request ACK/consumption retained. No ordering/crypto/version changes. `docs/ssh_parser_remaining_review.md` closes finite PR899/902/918/919 and generic current signature caller trace: client key-skip functions remain unpatched but blocked by current server ordering; forwarding absent; no current short-digest/OID trigger found. Not exhaustive parser certification.
|
||||
- `docs/idf_security_review.md` adds six named advisory dispositions with original/generated/config evidence: ECDH callers sized safely, driver-only PK path excluded, no untrusted CA promotion, privileged-local ECC sidechannel excluded not fixed, server tickets off despite compiled support, fixed CN avoids ASN zero-length replacement. No additional current-path source fix established; generic library defects remain explicitly recorded.
|
||||
- `tools/release_notices.py` + `third_party/release-notices/inputs.json` assemble62 exact hash-pinned allowlisted notice inputs offline into fresh explicit output, descriptor no-follow traversal, bounded preflight, exclusive creates/no overwrite, deterministic names/bytes. No secret scanning/full repo/config/build collection. Tests30PASS; agent+independent reviewer verified two actual deterministic bundles and hashes. Historical64files541147bytes not general future promise. `docs/release_packaging.md` separates notices from source/recipient/legal obligations. No distribution clearance: radio source exception question, wolf package discrepancy, exact icon provenance and recipient delivery remain open.
|
||||
- Parent `pio run` PASS **94,340 B linked RAM / 1,768,901 B flash** (+200flash). Parent seven suites PASS: parser3,258x2 plus channel production2,737x2 and fouralternate profiles, 11parser+18name+2appgate mutations; release notices30; auth135; protocol; strict crypto; SDK actual8C+header registration; ordering8,028/7mutations and12OpenSSH sessions each256KiB exact with rekey/cleancloseEOF. Independent review no actionable scoped defects, reran parser/notices and actual bundle determinism. Host evidence not hardware/resource/signoff.
|
||||
- README/roadmap/hardening/wolf/license/code map integrated latest evidence and finite closure. **Next:** use hardeningdoc remaining implementation/maintenance, release/source/delivery and whole-phase target gates rather than repeatedly reopening completed finite reviews. Broader advisory coverage/unpatched unused primitives not certified; release packaging does not solve legal/source/provenance questions. User whole-phase validation remains deferred; secureboot/encryptedNVS excluded. No device/asset regeneration/dependency upgrade/commit.
|
||||
|
||||
## Phase 12 implementation history — superseded by acceptance above
|
||||
## Phase 9D continuation — restricted SSH ordering correction — 2026-09-16
|
||||
|
||||
- User authorized the agreed DHCPv4 hostname / dual-stack SLAAC / STA DNS-SD baseline. Another agent owns `hardware/`; this work did not read or edit it. Do not commit or revert that agent's work. No upload, erase or device operation performed.
|
||||
- Wi-Fi owner applies full `sak-<suffix>` before DHCP and on rename for future exchanges, using permanent TCP/IP-owned storage to preserve the existing 59-byte hostname maximum beyond IDF's setter limit. Preferred IPv6 link-local/ULA/GUA can establish ONLINE without IPv4; link-local-only can retire fallback AP. One-second reconciliation handles stale/missed events. Web/OLED expose availability; the follow-up above adds actual preferred addresses to CLI output.
|
||||
- HTTPS default listener verified dual-stack; shared authority parser now strictly canonicalizes bracketed IPv6 without zone IDs. SSH uses one explicitly dual-stack listener with scope-safe peers, preserving two slots. Service owners publish availability through short portMUX sections; Wi-Fi owner reconciles two DNS-SD records and address families. No new task/socket/broker slot/serial payload buffer, dependency version, partition or generated asset change.
|
||||
- mDNS action API can silently drop queued work; missing families disable each pass, available families repair/re-probe every 30 seconds. Transient stale/zero A window remains until processed; AAAA follows valid-address semantics including deprecated addresses. Upstream calls can block, so polling is not a hard deadline. The later user acceptance establishes the reported device/AAAA behavior, not exhaustive multicast fault/transition testing.
|
||||
- New **narrow** `cmake/mdns_membership.*` overlay fixes verified mDNS 1.12.0 per-family multicast reference imbalance and failed-PCB join cleanup. Only a build-local source copy is changed; version/source hash guarded, managed source immutable. This is not the abandoned Phase 9 patch set. Membership/CMake tests include negative controls and repeated transitions.
|
||||
- Final `pio run` PASS: **94,396 B RAM / 1,853,025 B flash** (+176 / +5,380 versus recorded Phase 10); not runtime headroom. Local saved sdkconfig mDNS capacity changed to 2; durable defaults pin IPv4/IPv6 and two services. Initial bool-atomic target failure fixed with portMUX; final target build includes overlay. Existing SDK Kconfig notes remain.
|
||||
- Host suites PASS: Wi-Fi, mDNS, membership, SSH dual-stack and existing management/runtime/security, auth parser689 (host + actual lwIP), cookie variants, Network settings (after updated netif fakes), UI171+CSP, HTTPS lifecycle45+status8+identity, firmware88+SDKcontract, broker diagnostics and session-store/serial. No network packet, hardware, fault-injection or new high-speed serial pass is implied. Canonical contracts, limits and device checklist: [Phase 12](../roadmap.md#phase-12--advanced-network-integration).
|
||||
- Initial Git status clean. Implemented restricted-profile CVE-2025-14942 correction without upgrading managed pins. `tools/wolfssh_order/` archives exact PR793/819/840/855/921 bytes, hashes/commits, authoritative consolidated delta and scope. Generator now replaces eight C sources plus one internal header. PUBLIC forced overlay and stale-header marker preserve ABI across all consumers; joined `-include/path` avoids PlatformIO argument sorting/dedup failure.
|
||||
- Independent SELF/PEER keying bits, exact expected-message tracking, wrong-role/preauth/repeated-message rejection, nonblocking NEWKEYS continuation and EOF/window/exit send fences. Only current Curve25519/P256 KEX supported. Existing auth/password/parser/crypto fixes retained. **EXT_INFO deliberately disabled** in advertise/send/receive paths to eliminate skipped continuation: no server-sig-algs on wire. Tested Ed25519/P256/password OpenSSH combinations work; no RSA or broad-client compatibility claim. This is not wholesale upstream backport or unrestricted CVE closure.
|
||||
- Review caught misplaced EOF guard (on exit-status); corrected real SendChannelEof and retained explicit CHANNEL_REQUEST exit guard. Added actual shutdown/pending-buffer/stale-WANT_WRITE tests, zero output/state mutation during keying. Independent recheck found no remaining scoped blockers. Initial build flag integration failure and obsolete constant warning fixed; no guards weakened.
|
||||
- Parent `pio run` PASS **94,340 B linked RAM / 1,768,701 B flash** (−248 B vs prior slice). Final parent six suites PASS: ordering `--interop` (8,028 checks, seven rejected mutations, 12 sessions each exact256KiB with completed rekey/channel close/transportEOF), SDK actual build registration, auth135, protocol, strict crypto and parser3,124x2. Agent also passed36 consecutive interop sessions. Real generated wolfSSH/wolfCrypt tested with both KEX and three rekey directions; production authentication/broker integration and target timing still separate gates.
|
||||
- Initial parent interop exposed harness early exit/false-early PASS before peer channel close; corrected harness independently owns server over AF_UNIX fd-passing, waits channel closure/EOF and requires both exit0 plus exact binary data. Final parent AF_UNIX test needed explicit unsandboxed approval; no remote network or device operation. Remaining suites sandboxed.
|
||||
- Updated review/hardening/roadmap/license/code map with effective restricted scope and evidence. **Next:** finish remaining advisory/parser applicability and release notice/source obligations, then whole-Phase9 target validation including cleanup during rekey, no-EXT client compatibility and validation CPU/heap/stack costs. Secure boot/encrypted NVS excluded; no device operations/assets/version upgrades/commits. Phase9 remains in progress, not production sign-off.
|
||||
|
||||
## Phase 10 COMPLETE — explicit user acceptance, 2026-09-18
|
||||
## Phase 9D continuation — SSH parser / key validation — 2026-09-15
|
||||
|
||||
- User confirmed after firmware upload implementation and the concise-UI fix: “That works perfectly. And the usual operation is also verified.” Acceptance establishes that upload works and normal operation is verified. Do not infer specific fault-injection, NVS before/after comparisons, power-loss or recovery passes. The roadmap's compact regression guidance is reusable, not an acceptance blocker.
|
||||
- Latest concise-UI change was copy-only; reported UI regression **169 groups PASS**. No rebuild after that text change. The integration build below is historical, not validation of a newly rebuilt UI or this documentation update.
|
||||
- Initial Git status clean; previous 9D work already retained. Current slice keeps managed pins and seven-source override mechanism unchanged. `tools/security_overrides.py` now bounds IGNORE/service/helper parsing, rejects window-add overflow, fixes ECC/Ed25519 label predicates, and enforces ECC nested r/s plus outer signature exact consumption and Ed25519 exact signature-field consumption. Password wipe/async and state ordering unchanged.
|
||||
- `cmake/wolf_crypto_policy.*` PUBLIC-propagates `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO`, with fail-closed resolved guards. Verified prior P256 peer point reaches scalar multiplication without equivalent validation; nontrivial low-order X25519 inputs bypassed old precheck. Real vendor tests now reject these inputs. Evidence in `docs/ssh_key_validation_review.md`; generic digest/OID API hardening remains separate, no current short-digest trigger found in inspected callers.
|
||||
- Parent `pio run` PASS **94,340 B linked RAM / 1,768,949 B flash** (+1,732 flash vs prior9D). Strict crypto suite initially rejected concurrent parser changes; corrected independent exact-delta expectations, not weakened provenance. Final parent five suites PASS: `wolf_crypto_policy`, `wolfssh_parser_contract` (3,124 cases in each of two stack modes, six guard-removal mutations), `wolfssh_auth_contract`135cases, `ssh_protocol_policy`, `sdk_security_overrides --build-dir .pio/build/esp32-s3-devkitc-1-n16r8`. Independent review no scoped blockers; strict crypto/parser suites rerun PASS. Real vendor arithmetic tests and parser doubles remain separate, not live SSH transactions.
|
||||
- **Ordering CVE-2025-14942 still unresolved.** Official registry queries returned404 for wolfSSH1.5.0/wolfSSL5.9.2; upstream releases exist, so a full upgrade requires deliberate pinned component integration. Temporary-only PR793/819/840/855/921 backport evaluation found manual context adaptation plus unresolved SendNewKeys WANT_WRITE / skipped SendExtInfo continuation and extInfoSent rekey semantics. No partial ordering/header-layout patch installed. Next choose coherent tested nonblocking source+header backport or upstream component integration; preserve all local auth/wiping/parser contracts. Full provenance/rekey/negative-order tests required. Details and official links in wolf review.
|
||||
- Other remaining work: deferred parser/API applicability, release notices/source obligations, and whole-phase hardware validation. Review recommends a valid-but-inconsistent private/public ECC fixture and parser-to-real-crypto integration gate. No hardware/latency/resource/handshake claims, no device operations/assets/upgrades/secure-boot/encrypted-NVS changes. Do not mark Phase9 complete or require intermediate hardware signoff.
|
||||
|
||||
- User authorized simple OTA implementation; initial Git clean. New web_firmware_update module with standard SDK APIs, raw POST /api/firmware, admin cookie/Origin/CSRF and final principal currentness. Settings → HTTPS / Reboot has File/XHR upload progress, confirmation, session fencing and no automatic retries. Only inactive app/otadata writes; NVS/layout untouched. No signatures/antirollback/automaticrollback, dependencies or generated assets changed. Consolidated procedure, contract and regression guidance: [Phase 10](../roadmap.md#phase-10--simple-admin-web-firmware-upload); standalone guide removed.
|
||||
- 4KiB internal buffer + transient2048B-stack reboot owner allocated before erase; 10s stall/120s receive-loop budget, not totalflashdeadline. HTTPD synchronously blocks other web work during upload; networkserial maystall/drop, reboot disruptsall. No task/request/socket lifetime capture after handler. Service/identity reservation and atomic ordinary-reboot gate cover UI/UART0/SSH/browser/localbutton paths. Failed response after bootselect schedules noautomaticreset; selected latch rejects further uploads409, manual reboot available. Successful response schedules500ms reboot retaining reservations.
|
||||
- Review fixed two actualSDK5.5.0 edge cases: failed esp_ota_begin maypublishlivehandle beforeeraseerror (abortthat handle); rawContentLength64 canwrapHTTPDsize_t32 (overflow-safe actualslotbound/strictdecimal/equality check beforebody/erase). End consumes handle evenerror. SDKvalidation followed by exactparsedimage length includingSHA; basic header requiresS3appdescriptor/hash. Unrelated old/privateSDK code unpatched.
|
||||
- Parent final pio PASS **94,220 B RAM / 1,847,645 B flash**, +24RAM/+19,080flash vsPhase9, not runtimeheadroom. Parent newbackend88cases+actualSDKbeginfailurecontract, UI169groups+CSP, serverlifecycle44, admin25, consolelifecycle, SSHruntime, cookielifecyclePASS. Additionalbase/admin/display/lifecyclecookie, idle18, SSHmanagement/runtime/security agentPASS after adding missing rebootfake to adminfixture (no productionchange). Independent review final noactionablefindings; realbuilt firmware parsed with SDKmetadata bothOTAoffsets (notdeviceflashproof).
|
||||
- **Scope decision (2026-09-18):** User removed the BLE transport/provisioning proposal entirely because it no longer fits the project concept. BLE is not planned; retain the existing USB, HTTPS/WebSocket and SSH transport scope. This earlier scope decision did not authorize implementation; Phase 12 was subsequently authorized and implemented as recorded above. Do not resurrect Phase 9 patches. This update changed documentation only; no build, test, upload, erase, device operation or commit was performed.
|
||||
## Phase 9D — advisory mitigation / operational review — 2026-09-15
|
||||
|
||||
## Session logging scope decision
|
||||
- Work in progress; user validates Phase 9 as a whole. Secure boot/encrypted NVS excluded. No device operations or dependency upgrades.
|
||||
- New `security_operations.md`, `dependency_licenses.md`, `wolf_security_review.md`, and `idf_security_review.md` record runbooks, bounded license inventory, and advisory applicability. Phase 9 is not release-ready: wolfSSH message ordering/parser issues and distribution/source-notice questions remain unresolved.
|
||||
- Current implementation adds consistent Xtensa small X25519/Ed25519 policy and fail-closed compiler guards; seven pinned overrides now include DHCP option bounds, TLS EMS error propagation, and certificate-name allocation failure handling. All generated copies carry dated project modification notices. Nested mbedTLS targets are selected explicitly; installed dependencies remain untouched.
|
||||
- Parent `pio run` PASS: **94,340 B linked RAM / 1,767,217 B flash** (same RAM, −64,092 B flash vs 9C; not runtime headroom). Parent SDK override suite with actual seven-source registration, strict production wolf crypto policy/vector suite, wolfSSH auth contract135cases, and SSH protocol/context suite all PASS with `CCACHE_DISABLE=1`. Independent review found no actionable implementation defects and repeated the first two suites successfully. DHCP includes196,623 guard-page/equivalence cases; extracted EMS/X509 error tests do not establish full handshake/persistence fault behavior.
|
||||
- Review docs now distinguish original baseline from implemented mitigations. Seven generated modification/date notices resolved; broader license packaging/source questions remain. No target timing/interoperability, fault-injection or legal-clearance claim. **Next:** coherent wolfSSH message-order/parser correction or reviewed upgrade (1.5.0 candidate, not verified managed compatibility), ECC validation review, remaining advisory inventory and release notices. Do not call Phase9 complete or wait for per-slice target approval.
|
||||
|
||||
- User removed the filesystem-backed session recording proposal entirely. On-device session logging/transcripts are out of scope: users capture relevant output in their SSH or web terminal instead. Avoid adding RAM pressure, flash-write interference or potentially incomplete recordings to the serial path. The reserved `storage` partition remains unchanged and unmounted; this decision does not authorize partition migration.
|
||||
## Phase 9C — library cleanup / protocol policy — 2026-09-15
|
||||
|
||||
## Accepted state — 2026-09-18
|
||||
- User requested continuation; hardware still deferred to **whole Phase 9**, no per-slice approval gate. Initial Git status clean. Secure boot/encrypted NVS excluded; no eFuse/partition/dependency-version/asset changes, no SDK/managed source mutation.
|
||||
- `tools/security_overrides.py` + `cmake/security_overrides.cmake` included after root `project()`: require exact IDF5.5/version/originalSHA/edit matches, generate four full notice-preserving source copies in build tree, replace exact component source preserving flags/includes. Compile inputs are pinned originals PLUS checked-in edits. Missing/changed/ambiguous target/source fails; reconfigure tracks originals/script/generated. Never edit derived files or blindly repin. Original sources remain unchanged; compiled dependency behavior intentionally changes.
|
||||
- Overrides: HTTPS post-handshake allocation-failure TLS deletion, complete failed-start destruction and raw key pre-free wipe (failedstop owns live state); HTTPD scratch allocate/copy/wipe/free preserve old on failure, finalwipe, null first-read/nullable parserpointer fix; server-local TLS1.2 ECDHEECDSA AES128/256GCM, no renegotiation, no change clientdefaults/global primitives; wolfSSH GetSize both password lengths, failed newpassword framing skips callback, checked method suffix wiped before responses with prefix/canaries preserved, library asyncpending retains payload (project synchronous).
|
||||
- `ssh_memory.{c,h}` installed as globalwolfSSL hooks before initialization, PSRAMpreferred/internalfallback, usableextent securefree/noheaders, shrink wipes tail retains capacity, growth old+new allocation failure preservesold. Guards unpoisonedIDF5.5; dynamicIDF TLS buffers compile-rejected for cleanup contract. These costs need actual peak/latency evidence; liveinline/compaction/stack/hardware intermediates not allwiped.
|
||||
- `ssh_protocol_policy.{c,h}` applies checked staticlists before contextpublication: Curve25519/P256KEX, P256hostkey, AES128/256GCM, hmacsha256 advertisement, Ed25519/P256userkey advertisement (DB enforcesauth). LegacyCBC/CTR/removedKEX-only clients fail; no identitymigration. TLSpolicy is serveronly so future outboundHTTPS RSAclients unaffected.
|
||||
- `web_cookie_auth`: nonconsuming earlyquota/epoch probe before receive, final postparse reservation unchanged; JSONwipe beforeKDF, credentialsafter/beforeerrors; handler-lifetime RetryAfter. Existing5verification/60s fixedwindow/restartreset/malformednotcharged retained; no challengefairness/generalrequestlimit claim. All10domain modes passed implementation.
|
||||
- Final parent `pio run` PASS **94,340 B linked RAM / 1,831,309 B flash** (sameRAM/+1,384flash vs9B). Final fivefocused suites PASS incl installedSDK allocationextent, SDKcleanup/TLS/generator/nullfirstread+actual4source registration, SSHpolicy actual15contextintegration+KEXINIT, generatedwolfSSH135cases, web early admission. 17 related regressioncommands PASS before finalnullablefirstreadpatch; patchedSDKsuite+firmware rerunafter. Two independent reviews no blocking issues; inherited null-pointer subtraction found/fixed/tested. Standard UBSan linking unavailable earlier; new parser/allocator trap instrumentation passed in focused development. No realnetwork/hardware/cryptohandshake or reserveclaims.
|
||||
- Bounded review in `docs/security_library_review.md`: normal inspected mbedTLS record/MPI/PK/HMAC and wolfECC scalar paths alreadywipe; newhooks cover observed retired DER/buffer gaps, not proof everycopy erased. Password12–64ASCII/PBKDF2SHA25650k/generated24of64 unchanged pending costmeasurement; P256selfsigned2025–2049/trust verification retained; CSP/headers reviewed no blindHSTS or crypto-global removal.
|
||||
- **Next: Phase9D current upstream advisory/license review and provisioning/rotation/reset/backup/recovery/decommissioning runbooks.** No external advisory/CVE review or full license audit performed by9C; do not describe pinned versions/localfixes as certified current. Any versionupgrade now must re-audit/source-rebase overrides. Target checklist in hardeningdoc adds modern/legacy suite negotiation, rekey, malformed encryptedpassword packets, TLS/scratch failurecleanup, securefree CPU and old+new allocationheadroom under fullmix. Do not wait for9Ctarget signoff tocontinue.
|
||||
|
||||
- **Reduced Phase 9 complete by explicit user sign-off.** User waived a new whole-phase device check based on prior Phase 8 validation. Application code was unchanged, but no-core-dump/silent-panic defaults changed. Do not record the waived check as executed or claim a new panic/hardware pass.
|
||||
- Small scope: standard `sdkconfig.defaults` options, source-reviewed [operational checklist](../security_operations.md), README/roadmap guidance. No dependency patches, crypto policy replacement, allocator hooks, SDK migration, encryption, eFuse or partition changes. User abandoned the extensive earlier Phase 9 and restored baseline `f40c09c`; do not resurrect it.
|
||||
- Historical Phase 9 build: normal `pio run` PASS on PlatformIO6.12.0 / IDF5.5.0 / original20241119 toolchains, **94,196 B linked RAM / 1,828,565 B flash**. Generated configuration confirmed no dumps/silent panic; compilation inputs had no abandoned overlays/crypto guard. Existing generated config already selected these options. Defaults do not override saved sdkconfig; README explains verification. No new build is implied by this documentation consolidation.
|
||||
- **Phase 8 complete:** 8A–C target validated; explicit 8D.22 user acceptance on 2026-09-13. Completion/telemetry and legacy compatibility now live in [roadmap acceptance](../roadmap.md#phase8-acceptance-evidence) and [storage compatibility](../roadmap.md#phase8-legacy-credential-compatibility). Separate acceptance/legacy history documents removed; active [web contracts](../web_administration.md) and [regression procedures](../user_administration_tests.md) retained.
|
||||
- Post-acceptance baseline includes PSRAM-only ping payload/user snapshot allocations and refined web quick panels. Prior focused host/geometry/build checks passed; those are not additional hardware claims. Preserve lazy-allocation failure isolation and unchanged serial hot path.
|
||||
## Phase 9B — SSH admission / credential handling — 2026-09-15
|
||||
|
||||
## Evidence limits and follow-ups
|
||||
- User requested continued Phase 9 work and will validate **the phase as a whole**. Do not pause between slices for target approval; all target gates remain unrun and collected in `docs/security_hardening.md`. Secure boot/encrypted NVS remain excluded; Phase 8 sign-off stays closed. Initial Git status for this slice was clean.
|
||||
- `ssh_auth_policy.{c,h}`: 72-byte owner-only boot-lifetime state, independent handshake and password/signed-key buckets (capacity6, refill1/10s), unsigned-probe bucket(capacity12, refill1/5s). No waits, allocations, per-peer maps or NVS writes. Reconnect, stop/start/rotation/counter clear do not replenish; no refunds, idle saturation/no excess credit, clock regression fails closed. Global starvation is a deliberate documented tradeoff; natural refill only after hostile traffic subsides, not fairness/zeroCPU protection.
|
||||
- `ssh_transport` gates handshake before wolfSSH allocation and credentials before database/ordinary signature work; keeps existing per-slot three-counted-attempt closure and 15s deadline. Explicit pending-result marker fences duplicate/unexpected completion. wolfSSH1.4.20 and certificates/none-disabled guard; keyboard prompt rejection callback/context prevents unregistered callback dispatch while keeping password/publickey advertisement. New aggregate admission/probe/throttle/limit/backend/method counters via `ssh counters`; `add_counter` saturates (do not generalize to unrelated direct lifecycle increments).
|
||||
- Admin RX consumed spans / TX positively accepted spans are securely wiped; pending retry and serial hot-path bytes unchanged. Whole retired slot securely wiped before generation/fd restoration. `console_input` and shared remote hidden prompts reject overflow/unsupported bytes on submit, sticky across editing; visible CLI behavior preserved; existing callers prevent prefix persistence. Input errors wipe output and return zero length.
|
||||
- Source audit verified actual pinned wolfSSH auth callback order and `SendChannelData` positive copied/consumed behavior. Tests pin `internal.c` SHA256 and execute extracted vendor parser/send functions with crypto/IO doubles plus actual compiler-feature preprocessing. Followup resolved reviewer concern about keyboard error-path one-byte write: inline buffer initialized, framed packets retain padding reserve, exact-sized protocol-identification pending state cannot reach auth, rejection purges without advancing length. This is a narrow invariant audit, not library security certification.
|
||||
- Validation: parent `pio run` PASS **94,340 B linked RAM / 1,829,925 B flash** (+144RAM/+1,360flash vs9A; not runtime reserve). Parent and independent review PASS all four new suites `ssh_auth_policy`, `ssh_auth_transport`, `wolfssh_auth_contract` (35 vendor cases+resolved feature profile), `hidden_input`; token policy UB-sanitizer trap mode passed, standard UBSan runtime absent. Related 11 command suite PASS: SSH management/security/runtime, console boundary/accounts/lifecycle, admin SSH policy, web cookie SSH/accounts, web admin transport+tickets, security build policy18. `git diff --check` PASS. No blocking review findings; no upload/erase/device operations/deps/generated assets/commit.
|
||||
- 9B's planned library/protocol and early web-admission follow-up is implemented and bounded by 9C above. Challenge fairness/full-memory wiping are not guaranteed. External maintenance/lifecycle work is next; no intermediate target sign-off needed.
|
||||
- Final target checklist includes real SSH clients offering several keys, bad signatures/passwords, keyboard decline, each pool/refill/restart-clear persistence, established mixed transport/USB/UART0 responsiveness and reserve measurements, hidden-input errors/CRLF timing and crash recovery. UART0 paired/delayed LF relies on next-prompt flush; host UART fake does not model timing. No real-crypto/live packet-network or target claims from extracted-function tests.
|
||||
|
||||
- Previously accepted combined binary WS send: CPU160MHz / 230400 baud full mix including browser admin. The earlier Phase 8 telemetry had very low internal/DMA lifetime minima (2,052/460 B); these are nonblocking headroom follow-ups, not approved reserves or proof of simultaneous allocation failure. Full table, capture workload and counter limits are preserved in the roadmap. The latest Phase 12 full-mix minima are 8,528/772 B, with the same evidence limits.
|
||||
- TLS `-0x004C` means generic NET_RECV_FAILED, not OOM. Historical authentication/admission symptoms do not establish a cause. Do not invent fault, soak, timing or power-loss passes.
|
||||
- Credentials remain unencrypted; old flash contents are not erased. Intermittent trusted-network operation reduces exposure, not physical-extraction risk. Upstream upgrades are separate deliberate tasks, not an endless local backport programme.
|
||||
- Phase 10 is complete by the explicit acceptance above; detailed unreported regression scenarios remain unevidenced, not completion blockers. Device operations, branch/reset, commits and dependency upgrades remain outside this documentation task.
|
||||
## Phase 9A — crash/debug baseline — 2026-09-15
|
||||
|
||||
## Contracts to preserve
|
||||
- User requested Phase 9 and explicitly excluded secure boot and encrypted NVS. Roadmap now marks Phase 9 in progress; Phase 8 acceptance remains closed. Physical extraction/firmware replacement stay outside the threat model even after hardening; no encryption/eFuse/partition changes.
|
||||
- `src/security_build_policy.c` enforces resolved no-core-dump + silent-panic-reboot settings and rejects panic/register output, runtime/panic GDB stubs and OCD-aware handling. Registered unconditionally in CMake; explicit defaults. No runtime task/buffer added. This intentionally removes decoded panic traces, not ordinary boot/status logging or physical debug access.
|
||||
- `docs/security_hardening.md` defines shared operational profiles, secret-bearing artifact handling, evidence limits and target gates. Reserved partitions unchanged; old dump/credential copies are not erased. No generated assets/dependency changes.
|
||||
- Validation: `pio run` PASS **94,196 B linked RAM / 1,828,565 B flash**. Host policy matrix PASS17; actual generated SDK header PASS as eighteenth case. Initial host test hit read-only ccache storage; `CCACHE_DISABLE=1` rerun passed. Independent review found no actionable issues and repeated both host modes and diff check. No upload, erase, hardware validation or commit.
|
||||
- **9A target gate remains open:** synthetic-secret controlled panic, no register/UART/flash dump, reboot rather than halt, UART0/USB/network recovery and broker behavior. No test-only panic command was added to production.
|
||||
- 9A changed no authentication behavior; its planned SSH follow-up is now implemented in 9B above. Whole-phase target validation remains deferred.
|
||||
|
||||
- One UART1 writer, isolated observers and binary transparency. UART0 is administrative recovery; native USB is network-independent UART1, not administration or uninterrupted reboot.
|
||||
- Typed operations carry original-login IDs to the existing dispatcher. Owner-reserved generations fence stale/ABA changes; revocation/timeout does not cancel already admitted work. HTTPS commits before stop/restart; SSH stops before commit/restart; committed identity is not rolled back on lifecycle failure. Lost acknowledgement means uncertainty, not automatic replay.
|
||||
- Preserve private IDF HTTPD version guards, one outstanding owner-work reservation through failed destruction, SSH context lifetime until slots retire, bounded queues/buffers and secret-free metadata. Canonical recovery survives conditional-token exhaustion.
|
||||
- Removed Phase 8 scope stays removed: dedicated typed network diagnostics and ordinary browser-session/USB controls. No implicit browser-shell parity or browser identity reset/recovery/export.
|
||||
## Web popup cosmetics — 2026-09-14
|
||||
|
||||
- `src/web_ui.c`: contextual buttons now occupy entire status cards; bounded grid columns and clipped/ellipsized values prevent narrow-window overflow. Quick panels preserve the visible terminal title, admin toolbar and selected-view indication; full-page promotion retains drafts and restores Settings presentation.
|
||||
- Quick panels hide full-page explanations and duplicate detail readouts while preserving live status, errors and operation results. Wi-Fi exposes configured profile selection/enabled state and AP policy, plus Apply/Save/Refresh/Result; empty profile slots and advanced editors remain full-page-only. Profile selection is an editing target, not a direct-connect command; existing backend operations are unchanged.
|
||||
- Validation: all 161 UI behavior groups and C/HTML checks PASS; 36 Chromium geometry fixtures PASS at 320/600/900/1200px. `pio run` PASS (94,212 B linked RAM / 1,830,329 B flash). No generated-asset regeneration, upload, erase or hardware validation. Updated regression coverage in `tests/web_ui_session/`.
|
||||
|
||||
## Focused cleanup / PSRAM review — 2026-09-13
|
||||
|
||||
- User-authorized post-acceptance code review: removed superseded `admin_ssh_console_open()` SSH-only wrapper; production already uses available-slot admission. Updated adapter regression to actual production entry. Wrapper was already linker-discarded: no binary saving attributed to removal.
|
||||
- Moved ping queue payload4,200 B and public user snapshot2,156 B to lazy PSRAM-only lifetime allocations, no fallback. Queue control/internal locks unchanged; allocation failure affects only ping or status/list/show, not UART0 registration/mutations/recovery. Snapshot fully wiped on success/error. No serial hot-path, stacks, CPU, queue bounds or external-BSS config changes.
|
||||
- Baseline pio PASS23.79s100,556 RAM/1,828,573 flash. Final parent pio PASS22.11s **94,212 RAM/1,828,809 flash: −6,344 B linked internal RAM/+236 B flash**. Requested lazy PSRAM6,356 B plus allocator overhead; target pointer sizes4 B, controls84 B verified. This is not measured runtime-minimum improvement. CPU160 retained.
|
||||
- Focused ping allocation/callback/end-capacity and accounts allocation/full-wipe/failure/retry tests PASS; console boundary/lifecycle/policy and SSH management/runtime suites PASS. Independent review found no actionable bugs and reran ping/accounts/boundary/diff PASS. Sanitizer linking unavailable (missing host runtimes), no sanitizer or hardware pass. No upload/erase/commit. Next target check: ping/user list/show repeatedly alongside NVS writes and full mix230400, collect serial/broker counters and memory before/after first allocations.
|
||||
- Further audited opportunities, not implemented: OLED framebuffer1,024 B (internal I2C staging retained), local/remote completion scratch1,024 B each, optional web diagnostic ring2,816 B, remote console output payload8,192 B. Console rings need explicit secret-output wiping/lifecycle and admission-failure isolation; do not relocate mixed state wholesale. Leave authoritative user database, driver/DMA buffers, locks and task stacks internal. Global external-BSS enable is not surgical: it also changes SDK library placement.
|
||||
|
||||
## Accepted state — 2026-09-13
|
||||
|
||||
- **8D.22 explicitly signed off by the user:** “Yep, I tested the firmware thats a 8d.22 signoff.” The retained Phase 8D scope is complete; earlier per-slice pending target/review/integration gates are superseded. Roadmap already records 8A/B/C as complete and target-hardware validated, so **Phase 8 is complete**. Acceptance does not manufacture individual unreported test passes.
|
||||
- [Roadmap](../roadmap.md#phase-8--role-based-users-and-administrative-access--complete) holds the completed-phase gist; [web administration](../web_administration.md) holds current API/owner/recovery contracts; [acceptance evidence](../web_administration_acceptance.md) holds the latest report and limits. The old plan/baseline/per-slice histories are consolidated, not archived as another timeline. Test READMEs now link directly to current contracts and evidence; obsolete forwarding notes were removed too.
|
||||
- Final prior production build **PASS: 100,556 B linked RAM / 1,828,573 B flash, CPU 160 MHz**. No build/test/device execution is implied by this documentation update. The prior combined binary WebSocket-send fix was separately user-accepted at **160 MHz / 230400 baud with full mix including browser admin**; preserve combined send and bounded failed-send isolation.
|
||||
- Latest loaded capture: two serial WS, USB, two SSH roles with SSH serial writer; browser admin used then closed, not active in the capture. Internal/DMA/PSRAM free **31,508 / 23,752 / 8,136,624 B**, minima **2,052 / 460 / 8,065,972 B**, largest **18,432 / 18,432 / 7,995,392 B**; SSH minimum-free stack **15,028 B**. Full boot/loaded table is in acceptance evidence. Web send/queue/protocol and SSH IO errors zero; one SSH handshake failure/session revocation. Missing latest broker/serial counters prohibit an exact zero-drop inference.
|
||||
|
||||
## Follow-ups, not acceptance blockers
|
||||
|
||||
- Extremely low internal/DMA lifetime minima warrant correlated transient-headroom investigation; overlapping capability pools and conservative/non-simultaneous region minima do not prove allocation failure. Numeric reserves, HTTPD/dispatcher stack margins, peak correlation and detailed soak/fault evidence remain unapproved/unreported. Do not reopen functional sign-off or invent a reserve threshold.
|
||||
- SDK TLS `-0x004C` is generic NET_RECV_FAILED, not OOM. Two boot auth failures plausibly involve stale cookies, but causation is unconfirmed. Prior intermittent web admission issues and accepted idle cleanup do not justify claiming every admission failure fixed.
|
||||
- Real DNS/reannouncement, NVS power-loss, browser geometry/accessibility, individual fault cases and exact duration claims require explicit evidence if investigated; retained [regression procedures](../user_administration_tests.md) are not execution records.
|
||||
- The earlier documentation-only handoff is superseded by the user's Phase 9 request; current scope and evidence are recorded above. Device operations remain unperformed.
|
||||
|
||||
## Scope and safety to retain
|
||||
|
||||
- 8D.15 dedicated typed network diagnostics was removed; shell diagnostics remain subject to frontend policy. Unimplemented 8D.19 ordinary browser-session/native-USB controls were removed; SSH settings remain. No implicit full browser-shell parity or browser identity reset/recovery/export.
|
||||
- One UART1 broker writer, isolated observers and binary transparency. UART0 is administrative recovery; native USB is network-independent UART1, not administration or uninterrupted reboot.
|
||||
- Typed operations carry original-login IDs to the existing dispatcher. Owner-reserved generations fence stale/ABA changes; later revocation/timeout does not cancel admitted work. HTTPS commits before stop/restart; SSH stops before commit/restart; committed identity never rolls back on lifecycle failure. Lost ACK/result means uncertainty, never automatic replay.
|
||||
- Preserve private IDF HTTPD version guards, at-most-one owner-work reservations through failed destruction, retained SSH context until all slots retire, bounded queues/buffers and secret-free metadata. Canonical recovery survives conditional-token exhaustion.
|
||||
|
||||
## Previous documentation consolidation handoff
|
||||
|
||||
Initial Git status was clean. This task changes root `README.md`, `docs/` and five test-directory READMEs; executable source/tests/config/generated assets remain untouched. Independent documentation review checked acceptance scope, owner contracts and local links. It restored explicit pointer-backed HTTPD response-header lifetime and same-version SDK-patch audit warnings, updated test README links, and removed obsolete forwarding notes without reopening sign-off.
|
||||
|
||||
Validation completed: independent Python local-link/anchor audit PASS across 29 authored Markdown files (122 local links, 53 Markdown fragments); 213 authored source/test/document files checked with zero obsolete Phase 8D filename references. Earlier path audit resolved127 expanded navigation references. GPT logs, vendored/generated trees and remote URL fetching excluded. Twenty obsolete phase documents removed, with no forwarding stubs/archive dump. Git diff/scope checks confirm documentation-only changes. No firmware tests/build/device commands run.
|
||||
|
||||
@@ -104,6 +104,18 @@ Only constraints supported by implementation or current project documentation be
|
||||
|
||||
**Consequence:** Shared remote-console slots require transport-qualified tokens and immutable owner adapters. Validate owner currentness outside console locks, then recheck identity. Owner-side HTTPD/SSH IO and generation-safe cleanup remain mandatory; session liveness checks do not cancel executing handlers. Browser-shell permissions are parsed and narrower than typed Settings. [Authentication](../web_administration.md#authentication-and-admission), [console policy](../web_administration.md#browser-shell-policy).
|
||||
|
||||
## SSH admission budgets survive service and session lifetimes
|
||||
|
||||
**Decision:** Three fixed-size owner-only token buckets independently admit handshakes, password/signed-key requests and unsigned probes. Budgets last for the boot, not the slot/service/counter epoch. Rejection closes without sleeping; no per-account/IP storage or persistent lockout is added. Signed-key completion has an explicit pending-result marker and retains authoritative principal checks. Pinned library version/feature/source-contract tests protect callback order; keyboard-interactive is explicitly rejected.
|
||||
|
||||
**Consequence:** This bounds admitted work but permits global-budget starvation; restarting SSH is not an immediate recovery override. Established streams bypass admission, while actual load/latency still needs whole-phase device evidence. Counters are observations, never enforcement state. [Policy and tests](../security_hardening.md#9b-ssh-admission-and-credential-handling).
|
||||
|
||||
## Hidden input must not accept a truncated credential
|
||||
|
||||
**Decision:** Hidden console prompts reject overflow/unsupported bytes at submit, wiping output; rejection stays sticky after edits. Submission, Backspace/Delete and Ctrl-C retain their roles. Visible CLI editing is unchanged. Consumed SSH admin RX and accepted TX spans are wiped without touching pending retry bytes; slot retirement securely wipes before restoring generation/sentinels.
|
||||
|
||||
**Consequence:** Overlong/unsupported pastes must be retried; caller errors prevent prefix persistence. Application wipes do not establish library/stack/PSRAM zeroization. The pinned wolfSSH positive-send contract is copied/consumed bytes, not peer receipt. UART0 paired-CRLF timing remains a target check. Tests: `tests/hidden_input/run.py`, `tests/ssh_auth_transport/run.py`, `tests/wolfssh_auth_contract/run.py`.
|
||||
|
||||
## Typed serial mutations share the administration dispatcher
|
||||
|
||||
**Decision:** Typed domains queue IDs to the existing serialized dispatcher, never CLI strings or secrets. One original-login slot per domain and a nonreused ID fence stale work; session/deadline checks precede canonical admission. Results are replaceable observations, not durable history/idempotency.
|
||||
@@ -122,19 +134,31 @@ Only constraints supported by implementation or current project documentation be
|
||||
|
||||
**Consequence:** Re-audit SDK assumptions on upgrade; never patch around Origin `null` by weakening same-origin policy. Browser authentication POST uses CORS mode with fixed same-origin URLs/credentials because no-referrer non-CORS POST can serialize Origin as null. Digest-only cookie/challenge sessions replace Basic without fallback or live-record eviction. CSP loader hashes and authored scripts change atomically. Navigation preserves terminals/lease, while session-identity changes require a clean document before showing retained buffers. [Authentication and terminal contracts](../web_administration.md#authentication-and-admission).
|
||||
|
||||
## Dependency corrections are reproducible build inputs, not local SDK edits
|
||||
|
||||
**Decision:** Root CMake installs four exact-source-hash corrections after IDF target creation, rendering copies in the build tree and replacing each original target source exactly once. Original notices, includes and source compile properties are retained. Changed hashes/versions/missing/ambiguous sources fail configuration, with no unpatched fallback. Tests verify generated bytes and actual compiler inputs. [Registry and audit](../security_library_review.md).
|
||||
|
||||
**Consequence:** Upgrades need source/lifetime/feature re-audit, not just refreshed pins. Installed vendor code alone is not authoritative for overridden functions. Corrections cover HTTPS failure cleanup/key wiping, HTTPD scratch ownership, TLS server-local policy and SSH password packet bounds/wiping. TLS client defaults and global primitives remain unchanged. This is normal reproducibility checking, not tamper-resistant attestation.
|
||||
|
||||
## Retired library storage and protocol defaults have explicit policies
|
||||
|
||||
**Decision:** wolfSSL/wolfCrypt hooks use reviewed unpoisoned IDF5.5 usable allocation extents to wipe before free. No header overhead; shrink wipes tail but retains capacity; growth allocates/copies/wipes, retaining the old block on failure. Poisoned heaps and dynamic IDF TLS buffers are compile-rejected pending separate lifetime audits. Explicit static TLS/SSH allowlists replace negotiation defaults, without identity migration or weakening user-database authorization.
|
||||
|
||||
**Consequence:** Whole-buffer wipes and old-plus-new allocation peaks need combined target measurement; no blanket claim covers live inline residue, compiler spills or every crypto temporary. Legacy-only cipher/KEX clients can lose access; policy setter failure must free unpublished context rather than fall back. Web's early quota check does not change verification counts, service-restart resets or challenge fairness. [Exact algorithms, cleanup and limits](../security_library_review.md).
|
||||
|
||||
## Security material and configuration use bounded, versioned NVS records
|
||||
|
||||
**Decision:** Application settings, users, and identities use separate fixed/versioned NVS blobs. Serial, Wi-Fi, mDNS-hostname, and local-UI working edits are RAM-only until explicitly saved. User mutations and HTTPS/SSH identity changes commit directly as part of the operation. Invalid ordinary configuration generally selects RAM defaults without erasing storage; malformed security material fails closed and needs explicit reset.
|
||||
|
||||
**Rationale/evidence:** Serial, Wi-Fi, local UI, web security, users, and SSH security each validate schema/size and own their namespace. User/security mutations build and validate candidate state before committing it; security modules avoid silently replacing an established identity. The live user database remains internal while its 5,360-byte candidate is a persistent PSRAM-preferred allocation with internal fallback and is wiped after every transaction.
|
||||
|
||||
**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration and per-blob commit-before-live-install for user and identity mutation. Keep candidate ownership mutex-local and wipe/free it on initialization or recovery failure. Recheck external-buffer staging in the flash/NVS implementation when upgrading from the pinned ESP-IDF 5.5 baseline. Legacy credential synchronization and reconciliation are removed. Missing user storage commits empty; valid user v1 bytes remain compatible, with private `v1_admin_marker` derived from admin count, not a public bootstrap contract. HTTPS v1 (1,392 bytes) migrates through a private validated reader to TLS-only v2 (1,340 bytes), preserving exact DER/fingerprint/generation and committing before publication. Failures fail closed without fallback regeneration or overwriting rejected records. See [legacy compatibility](../roadmap.md#phase8-legacy-credential-compatibility).
|
||||
**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration and per-blob commit-before-live-install for user and identity mutation. Keep candidate ownership mutex-local and wipe/free it on initialization or recovery failure. Recheck external-buffer staging in the flash/NVS implementation when upgrading from the pinned ESP-IDF 5.5 baseline. Legacy credential synchronization and reconciliation are removed. Missing user storage commits empty; valid user v1 bytes remain compatible, with private `v1_admin_marker` derived from admin count, not a public bootstrap contract. HTTPS v1 (1,392 bytes) migrates through a private validated reader to TLS-only v2 (1,340 bytes), preserving exact DER/fingerprint/generation and committing before publication. Failures fail closed without fallback regeneration or overwriting rejected records. See [legacy compatibility](../legacy_credential_removal.md).
|
||||
|
||||
**Relevant files:** `src/serial_config.c`, `src/wifi_config.c`, `src/mdns_config.c`, `src/mdns_service.c`, `src/local_ui_config.c`, `src/web_security.c`, `src/user_database.c`, `src/ssh_security.c`
|
||||
|
||||
## NVS is persistence, not a physical security boundary
|
||||
|
||||
**Decision:** The current firmware stores Wi-Fi credentials and TLS/SSH private keys in unencrypted application NVS. The reserved NVS-key partition does not enable encryption.
|
||||
**Decision:** The current firmware stores Wi-Fi credentials and TLS/SSH private keys in unencrypted application NVS. The reserved NVS-key partition does not enable encryption. Phase 9 explicitly excludes secure boot and encrypted NVS by user preference; physical extraction and firmware replacement remain outside the supported threat model, not deferred guarantees of this phase.
|
||||
|
||||
**Rationale/evidence:** `partitions.csv`, README security notes, and current code show no NVS-encryption setup. Original rationale for deferring encryption is outside the implementation; the observable limitation is explicit.
|
||||
|
||||
@@ -142,15 +166,21 @@ Only constraints supported by implementation or current project documentation be
|
||||
|
||||
**Relevant files:** `partitions.csv`, `README.md`, `src/web_security.c`, `src/ssh_security.c`, `src/wifi_config.c`
|
||||
|
||||
## Supported builds do not export crash memory
|
||||
|
||||
**Decision:** Phase 9A requires disabled core dumps and silent panic reboot, and forbids panic/runtime GDB stubs and OCD-aware panic handling. `src/security_build_policy.c` checks resolved SDK configuration at compile time; defaults alone are insufficient. Development, test and production handling profiles share this baseline, with no bypass flag.
|
||||
|
||||
**Consequence:** Decoded panic traces/register output are deliberately lost; ordinary boot/status diagnostics remain and are not universally sanitized by this policy. No eFuse, physical JTAG restriction, partition change or retroactive dump clearing is implied. Target panic/recovery validation remains necessary. [Policy and tests](../security_hardening.md).
|
||||
|
||||
## Wi-Fi callbacks enqueue; the manager owns policy
|
||||
|
||||
**Decision:** ESP event callbacks copy bounded event data into the Wi-Fi manager queue. A permanent manager task performs driver operations, profile/AP policy, deadlines, reconciliation, and station mDNS announcement transitions. mDNS initializes at most once, remains allocated across transient disconnects, and treats failure as nonfatal. IPv4 or preferred IPv6 including link-local-only establishes ONLINE; route/Internet reachability is not implied. Full DHCP hostname storage and IPv6 state access belong to TCP/IP context. HTTPS/SSH owners only publish availability with short critical sections; Wi-Fi owns DNS-SD record and family reconciliation. Public mDNS actions are not acknowledged, so absent-family disables repeat every pass and healthy enables re-probe on a slower 30-second repair cadence. Record/address withdrawal is eventual, and upstream AAAA includes deprecated-but-valid addresses.
|
||||
**Decision:** ESP event callbacks copy bounded event data into the Wi-Fi manager queue. A permanent manager task performs driver operations, profile/AP policy, deadlines, reconciliation, and station mDNS announcement transitions. mDNS initializes at most once, remains allocated across transient disconnects while its component handlers withdraw/re-enable the STA interface, and treats failure as nonfatal.
|
||||
|
||||
**Rationale/evidence:** Callback paths avoid blocking, NVS, and policy work. Manager deadlines consult authoritative driver/netif state so dropped events are recoverable.
|
||||
|
||||
**Consequence for future changes:** Keep callbacks short and nonblocking. Add state transitions to the manager rather than directly invoking Wi-Fi policy from consoles, UI, or callbacks. Preserve queue-drop observability.
|
||||
|
||||
**Relevant files:** `src/wifi_manager.{h,c}`, `src/wifi_config.{h,c}`, `src/mdns_service.{h,c}`, `src/mdns_config.{h,c}`. `cmake/mdns_membership.*` is a narrowly reviewed exception for mDNS 1.12.0 multicast join/leave imbalance: patch only a build-local networking source with strict version/hash guards, never the managed source. Dependency upgrades must review/remove the overlay; do not bypass its guards or restore the abandoned Phase 9 patches.
|
||||
**Relevant files:** `src/wifi_manager.{h,c}`, `src/wifi_config.{h,c}`, `src/mdns_service.{h,c}`, `src/mdns_config.{h,c}`
|
||||
|
||||
## Optional local UI cannot become a core dependency
|
||||
|
||||
|
||||
@@ -4,18 +4,16 @@ UART0 and authenticated `admin` SSH sessions use the same registered command imp
|
||||
|
||||
Browser admin uses the same dispatcher with a [narrower parsed frontend policy](web_administration.md#browser-shell-policy), independent of typed Settings permissions. It supports bounded deferred `reboot`, `web stop`, exact `web certificate rotate --force` and owner-relative `exit`. Drain (up to ten seconds plus 200 ms) is best-effort application-buffer acknowledgement, not peer receipt or an execution deadline; pending input is discarded. Certificate work runs on the existing dispatcher through the shared service-before-identity reservation, commits before stop/restart and never rolls back a committed identity after lifecycle failure. Failed stop retains ownership and skips start. Verify changed trust through UART0 `web certificate info`, recover with UART0/admin SSH `web stop` / `web start`, then sign in freshly. HTTPS-only actions leave SSH/native USB/UART0 independent; reboot affects every transport and loses unsaved RAM.
|
||||
|
||||
Browser `web` allows only status/stop/exact forced certificate rotation; `mdns` allows status only. Browser `wifi` supports the canonical commands, including bare `wifi` status, settings, hidden secret prompts and explicit `wifi ap show-secret`. Secret input is not echoed, retained in history or completed. Wi-Fi changes can disconnect the browser before a result arrives, without deferred drain or cancellation; reconnect and inspect before retrying, or recover through UART0. Browser `user` allows status/list/show and interactive add/password plus forced role/delete for **other accounts**, not self/generated/key/recovery commands. Restricted SSH stop/disconnect/reset/host-key mutation remains unavailable in the browser shell. Typed Accounts/Network/SSH settings separately provide their documented bounded workflows; this is not shell parity. See [web administration](web_administration.md) for lifecycle/API ownership and uncertainty.
|
||||
Browser `web` allows only status/stop/exact forced certificate rotation; `wifi`/`mdns` allow status only. Browser `user` allows status/list/show and interactive add/password plus forced role/delete for **other accounts**, not self/generated/key/recovery commands. Restricted SSH stop/disconnect/reset/host-key mutation remains unavailable in the browser shell. Typed Accounts/Network/SSH settings separately provide their documented bounded workflows; this is not shell parity. See [web administration](web_administration.md) for lifecycle/API ownership and uncertainty.
|
||||
|
||||
## System
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `memory` | Show free memory, minimum free memory, and largest blocks for internal RAM, DMA-capable RAM, and PSRAM. |
|
||||
| `reboot` | Drain console output briefly and restart the ESP32; refused while a firmware upload or another reboot owns exclusion. |
|
||||
| `reboot` | Drain console output briefly and restart the ESP32. |
|
||||
| `exit` | Close the current administrative SSH or browser session after its acknowledgement drains; unavailable on UART0. Browser `exit` leaves serial connected. Ctrl+D on an empty administrative command line does the same. |
|
||||
|
||||
Firmware upload is an admin **Settings → HTTPS / Reboot** action, not a shell command. Ordinary UART0/admin-SSH/browser-shell/local-display and typed reboot paths are gated against an active upload. If upload boot selection succeeded but its response failed, no automatic restart is scheduled: the selected image remains, another admissible upload returns 409 until reset, and a deliberate manual `reboot` is permitted after reservations release. Inspect first; a lost response is not cancellation. See [firmware update and wired recovery](roadmap.md#phase10-update-and-recovery). Do not erase for an ordinary update; native USB is UART1 access, not recovery administration.
|
||||
|
||||
## Role-based users
|
||||
|
||||
| Command | Description |
|
||||
@@ -34,7 +32,7 @@ Firmware upload is an admin **Settings → HTTPS / Reboot** action, not a shell
|
||||
| `user key clear <username> --force` | Delete all public keys for an account. |
|
||||
| `user recover --force` | When normal user-database initialization failed, explicitly replace only its blob with an empty database; UART0-only, refuses a healthy database. |
|
||||
|
||||
Usernames must match `[a-z][a-z0-9_-]{0,15}`. Passwords contain 12–64 printable ASCII characters. The fixed database supports eight users and three SSH keys per user; initial key types are `ssh-ed25519` and `ecdsa-sha2-nistp256`. A key may be assigned to multiple accounts but cannot be duplicated within one account. Password verifiers, salts, raw key blobs, and passwords are absent from ordinary status output. `Ctrl-C` cancels a password or key prompt, and generated passwords are shown once.
|
||||
Usernames must match `[a-z][a-z0-9_-]{0,15}`. Passwords contain 12–64 printable ASCII characters. Hidden console prompts reject overflow or unsupported bytes rather than accepting a truncated/normalized prefix, even if later editing reduces the length; submit or cancel and start again. CR/LF submits, Backspace/Delete edits, and Ctrl-C cancels. The fixed database supports eight users and three SSH keys per user; initial key types are `ssh-ed25519` and `ecdsa-sha2-nistp256`. A key may be assigned to multiple accounts but cannot be duplicated within one account. Password verifiers, salts, raw key blobs, and passwords are absent from ordinary status output. `Ctrl-C` cancels a password or key prompt, and generated passwords are shown once.
|
||||
|
||||
Missing `user_db/database` storage is committed empty. On UART0 run `user add <username> admin`, optionally with `--generate`, to create the first administrator. There is no bootstrap command, imported shared credential, or synchronization with HTTPS material. Existing valid v1 user databases load unchanged, including previously migrated role-`user` accounts; no account is silently promoted.
|
||||
|
||||
@@ -123,9 +121,7 @@ Opening `/dev/ttyACM*` with DTR asserted creates the `usb-cdc` broker client, st
|
||||
| `mdns save` / `mdns load` | Save the working suffix to its independent NVS record or load it. |
|
||||
| `mdns defaults` / `mdns reset` | Restore the MAC-derived suffix in RAM, or restore and persist it. |
|
||||
|
||||
When the Wi-Fi station has IPv4 or a preferred IPv6 address, the Wi-Fi manager announces `sak-<suffix>.local` and advertises available HTTPS/SSH services through DNS-SD. This is STA-only, local-link discovery, not certificate or host-key trust. The default suffix is the lower-case hexadecimal STA MAC address. Suffixes may contain lowercase ASCII letters, digits, and internal hyphens only. Changing a suffix queues a best-effort reannouncement and updates the DHCPv4 hostname (`sak-<suffix>`, without `.local`) for subsequent DHCP exchanges; it does not force a lease restart. A configured DHCP/DNS server may publish that name in its own zone.
|
||||
|
||||
`wifi status` and browser Network status distinguish IPv4 absence and IPv6 link-local/ULA/GUA availability. `wifi` / `wifi status` and the web Network settings dialog additionally list up to three actual preferred IPv6 addresses, labelled link-local, ULA or GUA; these are copied with the flags, not inferred from enabled IPv6 support. A link-local destination needs the client's interface as its zone. To separate client address preference from IPv6 reachability, run `ping -6 -c 3 sak-1024.local` on an IPv6-capable client; choosing IPv4 with plain `ping` does not mean the device lacks IPv6. If lookup fails, inspect AAAA resolution (for example `avahi-resolve-host-name -6 sak-1024.local` where Avahi is installed), then test the numeric address from `wifi status` directly. Link-local-only connectivity counts as `ONLINE` and can retire the fallback AP after the existing stability interval; it does not establish Internet access. HTTPS/WebSocket and SSH support both families; browser IPv6 literals require brackets and cannot contain interface zones. Use the `.local` hostname for link-local browser access where supported by the client. Existing ping selects the first usable resolver result without family racing, and traceroute remains IPv4-only. mDNS failures do not stop Wi-Fi, UART0, UART1, or native USB access. See [Phase 12](roadmap.md#phase-12--advanced-network-integration) for discovery convergence limits and pending device validation.
|
||||
When the Wi-Fi station receives an IPv4 address, the Wi-Fi manager announces `sak-<suffix>.local`. The default suffix is the lower-case hexadecimal STA MAC address. Suffixes may contain lowercase ASCII letters, digits, and internal hyphens only. Changing a suffix while online causes a best-effort reannouncement; mDNS failures do not stop Wi-Fi, UART0, UART1, or native USB access.
|
||||
|
||||
## HTTPS web terminal
|
||||
|
||||
@@ -161,6 +157,12 @@ HTTPS listens on port 443 only. Authenticate with any current user-database user
|
||||
|
||||
SSH listens on port 22 and accepts user-database passwords plus stored `ssh-ed25519` and `ecdsa-sha2-nistp256` public keys. wolfSSH verifies key possession after the database authorizes the username/key pair; unsigned key probes do not complete authentication. A `user` receives the broker-backed UART1 serial stream. An `admin` receives the administration shell instead, does not become a broker client, and cannot acquire a UART1 writer lease.
|
||||
|
||||
SSH algorithm policy is explicit: KEX `curve25519-sha256`/`ecdh-sha2-nistp256`, P-256 host key, AES-128/256-GCM ciphers and `hmac-sha2-256` MAC advertisement (GCM authenticates packets). CBC/CTR-only or excluded-KEX-only clients cannot connect. There is no CLI fallback that weakens this policy; do not rotate keys merely to address a negotiation mismatch. [Exact TLS/SSH policy and upgrade contract](security_library_review.md).
|
||||
|
||||
SSH admission uses global boot-lifetime token buckets: handshakes and password/signed-key requests each allow a burst of six and refill one token per ten seconds; unsigned-key probes allow twelve and refill one per five seconds. The existing three-counted-attempt failure closure remains per connection. Reconnect, `ssh stop`/`ssh start`, host-key rotation and `ssh clear-counters` do not replenish these budgets. Rate denial closes the authenticating connection, not an established stream. Restrict hostile traffic and allow natural refill; repeatedly reconnecting consumes shared capacity and can prevent other users from logging in.
|
||||
|
||||
`ssh counters` separates handshake/verification/probe admissions and rate rejections, attempt-limit closures, backend errors and rejected methods. Admitted work is not necessarily successful or completed; probes/rate denials are not completed `auth-attempts`. These counters expose no submitted credentials and clearing them does not change enforcement. Keyboard-interactive is explicitly rejected, not merely omitted from the advertised list. See [policy and validation](security_hardening.md#9b-ssh-admission-and-credential-handling).
|
||||
|
||||
UART0 and admin SSH submit to one bounded queue, and one dispatcher task is the sole caller of `esp_console_run()`. Consequently, SSH commands execute the canonical UART0 handlers and produce the same status and mutation behavior rather than using a second command implementation. Remote output is routed into the authenticated session's bounded output ring; only the SSH transport task accesses wolfSSH.
|
||||
|
||||
UART0 and admin SSH use shared whole-line Tab completion. A unique/common prefix expands inline; a Tab that cannot extend an ambiguous prefix prints the matching candidates and redraws the unchanged input line instead of cycling candidates. Admin SSH additionally supports four-entry per-session command history with Up/Down, inline cursor editing with Left/Right, Home/End (including Pos1/Ende terminal sequences), Backspace/Delete, Ctrl-C, and visible or no-echo interactive prompts. Its history is RAM-only, private to the session, and wiped on disconnect. Ping callbacks enqueue bounded typed results so all formatting remains on the dispatcher task.
|
||||
@@ -193,4 +195,4 @@ UART0 and admin SSH use shared whole-line Tab completion. A unique/common prefix
|
||||
|
||||
Follow the exact wiring in [Electrical tests](electrical_tests.md) before invoking diagnostics. The OLED must be powered from 3.3 V because module I²C pull-ups may connect to `VCC`; verify that all external pull-ups also terminate at 3.3 V. Display diagnostics probe the standard SSD1315-compatible 7-bit `0x3c`/`0x3d` addresses. The currently tested module acknowledges at `0x3c`, whose 8-bit write/read forms are `0x78`/`0x79`; an explicit `scan --force` is available only for the dedicated local-UI bus. Diagnostics initially run at 100 kHz and treat an absent display as nonfatal. RS-232 diagnostics that require UART1 refuse to use it until `serial stop` releases it. The RGB LED shows test state: blue idle, yellow/orange running, green passed, red failed.
|
||||
|
||||
HTTPS storage migration preserves the exact TLS identity and commits TLS-only v2 before publication. Older v1-only firmware cannot read v2. Logical NVS replacement is not secure flash erasure; no factory erase is required. See [legacy removal](roadmap.md#phase8-legacy-credential-compatibility).
|
||||
HTTPS storage migration preserves the exact TLS identity and commits TLS-only v2 before publication. Older v1-only firmware cannot read v2. Logical NVS replacement is not secure flash erasure; no factory erase is required. See [legacy removal](legacy_credential_removal.md).
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# Broader dependency advisory coverage
|
||||
|
||||
Review date: **2026-09-16**. Documentation-only, finite snapshot; **not security certification, upgrade approval, or Phase 9 sign-off**.
|
||||
|
||||
## Result and authority
|
||||
|
||||
**New actionable maintenance finding:** the installed IDF 5.5 Wi-Fi bundle predates the official 5.5.3 correction for management-frame memory corruption. The project's PMF-capable WPA2/WPA3 SoftAP supplies the relevant supported configuration. Prioritize a coherent vendor-bundle update/backport investigation; see the exact evidence and limitations below. This is a current-feature exposure candidate supported by the vendor's release/fix description, **not a demonstrated device exploit or independently audited binary implementation**.
|
||||
|
||||
No additional current-path security correction was established for `esp_tinyusb`, TinyUSB, mDNS, LED strip, or the two vendored browser packages in this finite search. That statement does not establish that these dependencies are vulnerability-free.
|
||||
|
||||
- [IDF security review](idf_security_review.md) remains authoritative for its detailed mbedTLS findings, DHCP-server/EMS/X.509 backports, WebSocket subprotocol exclusion, and first-page feature dispositions. This document completes the previously omitted **IDF advisory-index pages**, without repeating those audits or claiming a fresh override verification.
|
||||
- [Wolf security review](wolf_security_review.md), [key-validation review](ssh_key_validation_review.md), and [remaining parser review](ssh_parser_remaining_review.md) retain their scopes and outstanding gates. Wolf advisories were not re-searched here.
|
||||
- Only `docs/dependency_advisory_coverage.md` was created. No sources, pins, manifests, assets, generated outputs, configuration, builds, tests, or devices were changed/run. Existing build metadata was read, not regenerated; it does not establish executable freshness or device state.
|
||||
|
||||
## Baseline actually inspected
|
||||
|
||||
Managed manifests were read from installed component directories, not inferred from the application's semver constraints. The application requests `mdns: ^1.8.2`, but installed mDNS is **1.12.0**.
|
||||
|
||||
| Dependency | Installed/source identity | Current use |
|
||||
| --- | --- | --- |
|
||||
| ESP-IDF | Existing reviewed 5.5.0 baseline; additional original SDK sources and Wi-Fi archives inspected below | Wi-Fi AP/STA, lwIP, HTTPS; existing local security overrides remain part of effective inputs |
|
||||
| `espressif/esp_tinyusb` | **2.2.1**, manifest commit `8e779566ef71d43928cbf7e125e8eb54bab3f542`, path `device/esp_tinyusb` | CDC device wrapper |
|
||||
| `espressif/tinyusb` | **0.21.0~1**, manifest commit `7049c58a0e895acc92c6407574b05b5536eddfc8` | Espressif fork, not interchangeable with an unqualified upstream 0.21.0 claim |
|
||||
| `espressif/mdns` | **1.12.0**, manifest commit `db06b19b7be729c163d346f62ec0eba01047b7f1` | STA hostname responder; no registered DNS-SD services or browser/query application calls |
|
||||
| `espressif/led_strip` | **3.0.3**, manifest commit `7cd447361ca2f0a1c01aa3089e3031f6171b6c7e` | One WS2812, RMT, no DMA |
|
||||
| `@xterm/xterm` | **5.5.0** per [asset provenance](../web_assets/SOURCES.md); local JS SHA-256 `1f991ac3b4b283ebf96e60ae23a00a52765dd3a2e46fa6fdda9f1aab032f7495` rechecked | Serial and admin browser terminals |
|
||||
| `@xterm/addon-fit` | **0.10.0**; local JS SHA-256 `bdaefa370b1bfc42ee88d46fe6072400902a4d4b2d45cd93438dda9b23c97089` rechecked | Only loaded addon |
|
||||
|
||||
Browser version attribution is to the recorded exact-package provenance plus matching local hashes. No npm install, registry dependency resolution, asset download, decompression, or embedded-asset regeneration was performed. Generated compressed arrays were not independently re-hashed in this slice.
|
||||
|
||||
Local configuration evidence is `.pio/build/esp32-s3-devkitc-1-n16r8/config/sdkconfig.h`; compilation evidence is a filtered read of the existing compilation database. Source references under `components/` below are relative to `/home/mscholz/.platformio/packages/framework-espidf/`.
|
||||
|
||||
## Finite search ledger: URLs checked and stopping rules
|
||||
|
||||
The search ended at the following dated snapshot. Empty official repository advisories mean **no published advisories returned by that endpoint**, not no CVEs anywhere, no undisclosed defects, or a security guarantee. GitHub releases and component changelogs are different publication channels; an empty Releases list is not evidence that component versions stopped shipping.
|
||||
|
||||
### IDF advisory index: all snapshot pages
|
||||
|
||||
Fetched the official HTML index [page 1](https://github.com/espressif/esp-idf/security/advisories), [page 2](https://github.com/espressif/esp-idf/security/advisories?page=2), and [page 3](https://github.com/espressif/esp-idf/security/advisories?page=3): **10 + 10 + 2 = 22 entries**, oldest June 23, 2022, newest September 3, 2026. Page 3 is the terminal listed page. Full advisory descriptions were read from the official [repository advisory API](https://api.github.com/repos/espressif/esp-idf/security-advisories?per_page=100&page=1), not merely titles.
|
||||
|
||||
The [API page=2 request](https://api.github.com/repos/espressif/esp-idf/security-advisories?per_page=100&page=2) unexpectedly returned the same 22 entries with no Link header. **It was not treated as an empty-page proof or 22 additional advisories.** The HTML pagination and unique IDs establish the 22-entry snapshot boundary. No unbounded pagination or general web search followed.
|
||||
|
||||
### Other official advisory channels
|
||||
|
||||
Each URL below returned the stated number of public advisory records, with no next-page Link header:
|
||||
|
||||
| Official endpoint checked | Records | Disposition |
|
||||
| --- | ---: | --- |
|
||||
| [esp-usb](https://api.github.com/repos/espressif/esp-usb/security-advisories?per_page=100&page=1) | 3 | HID/UVC **host** issues; device CDC exclusion below |
|
||||
| [hathach/tinyusb](https://api.github.com/repos/hathach/tinyusb/security-advisories?per_page=100&page=1) | 0 | No published records returned |
|
||||
| [espressif/tinyusb](https://api.github.com/repos/espressif/tinyusb/security-advisories?per_page=100&page=1) | 0 | Fork checked separately |
|
||||
| [esp-protocols](https://api.github.com/repos/espressif/esp-protocols/security-advisories?per_page=100&page=1) | 0 | mDNS release fixes screened separately |
|
||||
| [idf-extra-components](https://api.github.com/repos/espressif/idf-extra-components/security-advisories?per_page=100&page=1) | 0 | LED changelog screened separately |
|
||||
| [xterm.js](https://api.github.com/repos/xtermjs/xterm.js/security-advisories?per_page=100&page=1) | 0 | Browser release fixes screened separately |
|
||||
|
||||
### Release/changelog boundary
|
||||
|
||||
- **IDF:** [release API page 1](https://api.github.com/repos/espressif/esp-idf/releases?per_page=100&page=1) returned 100 records; [page 2](https://api.github.com/repos/espressif/esp-idf/releases?per_page=100&page=2) returned 64 and no additional 5.5 tags. Screened published stable **5.5.1–5.5.5** GitHub release bodies for explicit security, vulnerability, overflow, out-of-bounds, corruption, UAF and double-free descriptions; followed the relevant additional candidates below. This is not a line-by-line audit of every bugfix or another IDF branch. [5.5.4 body](https://api.github.com/repos/espressif/esp-idf/releases/tags/v5.5.4) mainly describes a NimBLE regression. The linked full [5.5.4](https://release-notes.espressif.tools/release/5.5.4) and [5.5.5](https://release-notes.espressif.tools/release/5.5.5) release databases returned **no textual content** through the fetch tool: their database-only contents are an explicit coverage gap, not reviewed pages.
|
||||
- **esp-usb:** [Releases API](https://api.github.com/repos/espressif/esp-usb/releases?per_page=100&page=1) returned zero. Read the official [component changelog](https://raw.githubusercontent.com/espressif/esp-usb/master/device/esp_tinyusb/CHANGELOG.md) through **2.3.0**, and [current manifest](https://api.github.com/repos/espressif/esp-usb/contents/device/esp_tinyusb/idf_component.yml), also 2.3.0. This establishes a changelog ceiling, not proof of registry publication or compatibility.
|
||||
- **TinyUSB:** [upstream Releases API](https://api.github.com/repos/hathach/tinyusb/releases?per_page=100&page=1) returned 18 records, newest **0.21.0**, whose full release notes were read. [Fork Releases API](https://api.github.com/repos/espressif/tinyusb/releases?per_page=100&page=1) returned zero. [Installed fork commit API](https://api.github.com/repos/espressif/tinyusb/commits/7049c58a0e895acc92c6407574b05b5536eddfc8) identifies the merge “Necessary fixes for TinyUSB 0.21.” No later published upstream release was present. Two legacy-path requests, [upstream](https://raw.githubusercontent.com/hathach/tinyusb/master/docs/info/changelog.rst) and [fork](https://raw.githubusercontent.com/espressif/tinyusb/master/docs/info/changelog.rst), returned 404; [docs listing](https://api.github.com/repos/hathach/tinyusb/contents/docs) then [changelog directory](https://api.github.com/repos/hathach/tinyusb/contents/docs/changelog) resolved the moved layout and confirmed 0.21.0 as its highest listed version. Unreleased commits/PRs were not exhaustively searched.
|
||||
- **mDNS:** [esp-protocols release page 1](https://api.github.com/repos/espressif/esp-protocols/releases?per_page=100&page=1) returned 100 records and [page 2](https://api.github.com/repos/espressif/esp-protocols/releases?per_page=100&page=2) 37. Post-pin mDNS releases in that finite list are **1.13.0 (September 14)** and **1.13.1 (September 15)**. Their notes and both production-source fix patches were inspected; other monorepo components are outside this slice.
|
||||
- **LED:** [Releases API](https://api.github.com/repos/espressif/idf-extra-components/releases?per_page=100&page=1) returned zero. Official [LED changelog](https://raw.githubusercontent.com/espressif/idf-extra-components/master/led_strip/CHANGELOG.md) ends at **3.0.3**, matching installed version.
|
||||
- **xterm:** [Releases API](https://api.github.com/repos/xtermjs/xterm.js/releases?per_page=100&page=1) returned 89 records with no pagination. Read **5.5.0** baseline and **6.0.0 (December 22, 2025)** post-pin release notes. 5.5.0 explicitly lists addon-fit 0.10.0 as compatible. Screened 6.0.0's security-relevant addon fixes and actual integration below; did not mistake npm development/demo dependency updates for packages served by this firmware.
|
||||
|
||||
This completes the **declared finite search**, not every possible vendor/CVE channel. NVD, OSV, npm's global advisory database, private advisories, all historical SDK subdependency advisories, every unreleased commit, and all non-security-labelled release regressions were not exhaustively searched. The failed full-release-database fetches remain visible above.
|
||||
|
||||
## IDF pages 2–3: all twelve previously omitted entries
|
||||
|
||||
All linked advisory bodies below were included in the fetched official API response. “Not-current-path” does not mean the underlying library is patched.
|
||||
|
||||
| Advisory / CVE | Required feature and snapshot disposition |
|
||||
| --- | --- |
|
||||
| [GHSA-9j5x-rf36-54x9](https://github.com/espressif/esp-idf/security/advisories/GHSA-9j5x-rf36-54x9), CVE-2026-25508 | BLE provisioning ATT prepared-write length accumulation. **Not-current-path:** Bluetooth/provisioning absent. |
|
||||
| [GHSA-m2h2-683f-9mw7](https://github.com/espressif/esp-idf/security/advisories/GHSA-m2h2-683f-9mw7), CVE-2026-25532 | WPS enrollee fragment underflow requires initiating WPS PBC/PIN pairing. **Not-current-path:** no application `esp_wifi_wps_*` calls; no enabled WPS setting in the inspected generated header. Ordinary PSK/SAE association is not WPS. |
|
||||
| [GHSA-hmjj-rjvv-w8pq](https://github.com/espressif/esp-idf/security/advisories/GHSA-hmjj-rjvv-w8pq), CVE-2025-68473 | Bluedroid Classic SDP UUID-array overflow. **Not-current-path:** Bluetooth absent. |
|
||||
| [GHSA-43gh-7r4f-qp57](https://github.com/espressif/esp-idf/security/advisories/GHSA-43gh-7r4f-qp57), CVE-2025-68474 | Bluedroid Classic AVRCP vendor-command allocation/write. **Not-current-path:** Bluetooth absent. |
|
||||
| [GHSA-vcw6-jc3p-4gj8](https://github.com/espressif/esp-idf/security/advisories/GHSA-vcw6-jc3p-4gj8), CVE-2025-65092 | ESP32-P4 hardware JPEG header parser. **Not-current-path:** ESP32-S3, no JPEG driver compilation inputs. |
|
||||
| [GHSA-qhf9-vr2h-jh96](https://github.com/espressif/esp-idf/security/advisories/GHSA-qhf9-vr2h-jh96), CVE-2025-66409 | Bluedroid Classic AVRCP vendor-command read. **Not-current-path:** Bluetooth absent. |
|
||||
| [GHSA-8mg7-9qpg-p92v](https://github.com/espressif/esp-idf/security/advisories/GHSA-8mg7-9qpg-p92v), CVE-2025-64342 | Original ESP32 Bluetooth controller invalid access address. **Not-current-path:** S3 explicitly outside affected chip family; Bluetooth also absent. |
|
||||
| [GHSA-9w88-r2vm-qfc4](https://github.com/espressif/esp-idf/security/advisories/GHSA-9w88-r2vm-qfc4), CVE-2025-55297 | BluFi example credential/DH buffers. **Not-current-path:** no BluFi integration, no Bluetooth host. Not a generic flaw in this project's Wi-Fi password handling. |
|
||||
| [GHSA-hqhh-cp47-fv5g](https://github.com/espressif/esp-idf/security/advisories/GHSA-hqhh-cp47-fv5g), CVE-2025-52471 | ESP-NOW receive underflow. **Not-current-path:** no application ESP-NOW initialization or receive callback. Does not prove opaque Wi-Fi archives are globally fixed. |
|
||||
| [GHSA-wm57-466g-mhrr](https://github.com/espressif/esp-idf/security/advisories/GHSA-wm57-466g-mhrr), CVE-2024-53845 | ESPTouch v2 constant AES-CBC IV. **Not-current-path:** no SmartConfig/ESPTouch provisioning calls; PSK/SAE manager is separate. |
|
||||
| [GHSA-22x6-3756-pfp8](https://github.com/espressif/esp-idf/security/advisories/GHSA-22x6-3756-pfp8), CVE-2024-28183 | Physical flash TOCTOU against anti-rollback. **Excluded/not configured:** no enabled `CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK`; physical firmware replacement is outside current review model. No claim of secure boot or rollback protection. |
|
||||
| [GHSA-7f7f-jj2q-28wm](https://github.com/espressif/esp-idf/security/advisories/GHSA-7f7f-jj2q-28wm), CVE-2022-24893 | BLE Mesh provisioning `SegN` bounds. **Not-current-path:** Bluetooth/Mesh absent. |
|
||||
|
||||
Rechecked local evidence: target `esp32s3`/Xtensa; no enabled `CONFIG_BT_ENABLED`; compilation-entry counts `/bt/host/`, `esp_driver_jpeg`, `/esp_tee/` each zero. Targeted application searches for WPS, ESP-NOW, SmartConfig, BluFi and protocomm found no integration. These exclusions do not depend on later release tables implicitly excluding 5.5.0. Revisit before enabling these features.
|
||||
|
||||
Together with the ten entries already dispositioned in [the IDF review](idf_security_review.md#bounded-idf-advisory-index-snapshot-disabledunused-features), this accounts for all **22 unique IDs** in the dated official index.
|
||||
|
||||
## New release-note finding: Wi-Fi management-frame corruption
|
||||
|
||||
**Priority: prompt coherent vendor correction review for PMF-capable SoftAP operation. Not fixed by existing DHCP/mbedTLS overrides.** Reported to the parent during this review.
|
||||
|
||||
Official [IDF 5.5.3 release](https://github.com/espressif/esp-idf/releases/tag/v5.5.3) says “Fixed memory corruption issue by ensuring management frames are only encrypted when they are robust management frames,” referencing **`a3927641`**. Fetched [patch](https://github.com/espressif/esp-idf/commit/a3927641.patch) and [commit API](https://api.github.com/repos/espressif/esp-idf/commits/a3927641): merge commit **`a3927641bfd0aedfaab3514cba3d7fb01378ce4d`**, contained patch commit **`caf4cad3b422be5c2f93afeff4cd07796a262b46`**. The different patch-header ID is the contained commit, not an invented match to the merge hash.
|
||||
|
||||
The patch description states that when the supplicant sends an authentication response for an already-connected station with installed keys, unconditional management-packet encryption based on `bss->pmf_enable` assumed extra encryption-header space that was not present. It also mentions incorrect allocation due to RSN IE length mismatch; that second trigger was not independently established for the project's short configured IE set.
|
||||
|
||||
### Current application prerequisites and original interface
|
||||
|
||||
`src/wifi_manager.c:315–331`, `configure_ap()`:
|
||||
|
||||
```c
|
||||
wifi_config.ap.authmode = WIFI_AUTH_WPA2_WPA3_PSK;
|
||||
wifi_config.ap.max_connection = 4U;
|
||||
wifi_config.ap.pmf_cfg.capable = true;
|
||||
wifi_config.ap.pmf_cfg.required = false;
|
||||
wifi_config.ap.sae_pwe_h2e = WPA3_SAE_PWE_BOTH;
|
||||
esp_err_t error = esp_wifi_set_config(WIFI_IF_AP, &wifi_config);
|
||||
```
|
||||
|
||||
`required=false` does **not** disable negotiated PMF. AP/APSTA is a supported runtime policy (`wifi_manager.c:382,646`), not an unused SDK feature; generated `CONFIG_ESP_WIFI_SOFTAP_SUPPORT=1` and WPA3 SAE support are enabled. Exposure is conditional on AP operation and the vendor-described station/key/PMF state. No claim is made that an arbitrary unassociated peer can exploit it.
|
||||
|
||||
Original configured compilation input `components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c:146–155` still contains:
|
||||
|
||||
```c
|
||||
uint8_t *wpa_ap_get_wpa_ie(uint8_t *ie_len)
|
||||
{
|
||||
/* ... existing hostapd checks ... */
|
||||
*ie_len = hapd->wpa_auth->wpa_ie_len;
|
||||
return hapd->wpa_auth->wpa_ie;
|
||||
}
|
||||
```
|
||||
|
||||
`esp_wifi_driver.h:133` has the matching `uint8_t *len` callback signature; `esp_wpa_main.c:489–496` registers it under `CONFIG_ESP_WIFI_SOFTAP_SUPPORT`. This is an interface/provenance marker for the old bundle, **not the source location of the opaque management-encryption defect**.
|
||||
|
||||
### Exact binary-bundle provenance
|
||||
|
||||
The official fix changes **both** callback signatures to `size_t *` and replaces `components/esp_wifi/lib` with **`a4e903fe43bf09a95022f9802db43d39740ccc0b`**. A C-only signature edit is not an adequate or ABI-safe implementation of this correction.
|
||||
|
||||
A local `git rev-parse` in the packaged Wi-Fi submodule failed because its Git metadata is not usable. Instead, calculated Git blob SHA-1 values directly from all seven installed `esp32s3/*.a` files and compared them with official content metadata:
|
||||
|
||||
- [IDF v5.5 submodule pointer](https://api.github.com/repos/espressif/esp-idf/contents/components/esp_wifi/lib?ref=v5.5): **`8a1b7bbc00e895d040c5c9a6fb9d1db2bbfc7958`**.
|
||||
- [Baseline ESP32-S3 archive listing](https://api.github.com/repos/espressif/esp32-wifi-lib/contents/esp32s3?ref=8a1b7bbc00e895d040c5c9a6fb9d1db2bbfc7958): **all seven local blob IDs match** (`core`, `espnow`, `mesh`, `net80211`, `pp`, `smartconfig`, `wapi`).
|
||||
- [Corrected bundle listing](https://api.github.com/repos/espressif/esp32-wifi-lib/contents/esp32s3?ref=a4e903fe43bf09a95022f9802db43d39740ccc0b): all seven differ from installed.
|
||||
- Installed `libnet80211.a`: Git blob `6ee20bec142638dc0a442b9642de9fa62db6ddd0`, SHA-256 **`dcb524dda8563976cf1a97d4398c7b06cf024b7ef9a5f07788e84c88792d8071`**; corrected bundle's Git blob is `8ce8a2a6a9a753e31508bd609bf0a0d1d44c332f`.
|
||||
|
||||
This confirms the installed stock-baseline bundle, not a hidden same-version binary replacement carrying the published fix. It does not independently reverse-engineer the affected routine or establish a reliable remote attack.
|
||||
|
||||
**Parent action:** investigate a maintained IDF/vendor bundle carrying the complete correction, with matching supplicant ABI and all existing source-pinned overrides rebased and revalidated. Do not replace one archive or change only the callback type. Preserve USB/UART0 recovery and broker isolation. Target regression should cover PMF-capable WPA2/WPA3 SoftAP association, repeated authentication/reassociation with keys already installed, APSTA transitions and loaded service recovery. These tests were **not performed**. No blind recommendation to weaken PMF or disable recovery AP policy is made.
|
||||
|
||||
## Additional IDF release candidates screened
|
||||
|
||||
These are supplemental to, not replacements for, the existing detailed IDF review.
|
||||
|
||||
| Release candidate / fetched evidence | Actual-use disposition |
|
||||
| --- | --- |
|
||||
| 5.5.1 DHCP **client** option/HW-ID length, [bcd56c74 patch](https://github.com/espressif/esp-idf/commit/bcd56c74.patch), contained commit `1ad41e589ff7296c484ba64f206f2278a09dab37` | Original `lwip_default_hooks.c:262–269,293–300` retains old checks; generated option length **68**, vendor class ID disabled. Client path is used, but current hostname is fixed `CONFIG_LWIP_LOCAL_HOSTNAME="espressif"` and no application `esp_netif_set_hostname()` call exists. Inspected `dhcp_select()`/`dhcp_discover()` build bounded fixed options, short hostname and six-byte MAC, leaving space in 68 bytes; no current long-option trigger established. mDNS suffix is **not** the DHCP hostname. Revisit/backport before longer hostnames or extra options. Distinct from already fixed DHCP **server** CVE-2026-45160. |
|
||||
| 5.5.2 `esp_timer_dump()` overflow, [a511f3be patch](https://github.com/espressif/esp-idf/commit/a511f3be.patch), contained commit `ec71bd189f6ef42aff2daa99f35d98611bb9faa3` | Original `esp_timer.c:613,616` retains old line-size constants 90/46 (fix 103/47) and profiling branch's incorrect size addition. No application dump call; timer profiling absent. **Not-current application path**, not a globally patched timer library or a ban on ordinary timers. |
|
||||
| 5.5.3 HTTP **client** Digest OOB read, [6d0a7a01 patch](https://github.com/espressif/esp-idf/commit/6d0a7a01.patch), contained commit `bbcc13be8b38283547883bfee4920331bea80cd1` | Original `esp_http_client/lib/http_auth.c:131–132` uses fixed-length `memcmp` on server algorithm string; no application HTTP-client/Digest consumer. **Not-current-path**; the HTTPS server's cookie login is not this function. |
|
||||
| 5.5.3 AES DMA/PSRAM cache ordering, [3f10cdab patch](https://github.com/espressif/esp-idf/commit/3f10cdab.patch), contained commits `525ef3a2eab239e30cd4cc62f6d23b61743fe4c1`, `0742f3fce3a1779c63ec286342f6d9799e12822f` | Original `esp_aes_dma_core.c:1121–1128` invalidates after DMA; file is a configured compilation input. S3 has `SOC_PSRAM_DMA_CAPABLE=1`, so **target name alone does not exclude it**. Generated `CONFIG_MBEDTLS_HARDWARE_AES` absent; `esp_config.h:150–153` therefore undefines `MBEDTLS_AES_ALT`, and no application `esp_aes_*` call was found. No current application mbedTLS hardware-AES path established. Direct SDK/opaque-radio consumers and their destination buffers were not exhaustively traced: retain this as a **residual SDK applicability question**, not global exclusion or a verified current HTTPS corruption finding. |
|
||||
| Bluetooth, provisioning, JPEG, TREL/OpenThread, other-chip secure-boot/flash-encryption release fixes | Bluetooth/JPEG/provisioning exclusions above apply; project is not an OpenThread/TREL application. Physical security and other-chip configuration changes do not establish an S3 network attack. These release categories were screened, not their entire implementations audited. |
|
||||
|
||||
## Managed USB applicability
|
||||
|
||||
Published esp-usb records are [GHSA-gp8r-qjfr-gqfv](https://github.com/espressif/esp-usb/security/advisories/GHSA-gp8r-qjfr-gqfv) (HID host close double-free race), [GHSA-2pm2-62mr-c9x7](https://github.com/espressif/esp-usb/security/advisories/GHSA-2pm2-62mr-c9x7) (HID host descriptor UAF), and [GHSA-g65h-9ggq-9827](https://github.com/espressif/esp-usb/security/advisories/GHSA-g65h-9ggq-9827) (UVC host descriptor-printing stack overflow). Official affected components are `usb_host_hid` through 1.0.4 and `usb_host_uvc` through 2.3.1, **not similarly numbered esp_tinyusb releases**.
|
||||
|
||||
`src/usb_cdc_transport.c:763–781` calls `TINYUSB_DEFAULT_CONFIG(device_event_callback)`, `tinyusb_driver_install(&usb_config)`, and `tinyusb_cdcacm_init(&cdc_config)` for `TINYUSB_CDC_ACM_0`, using the S3 internal full-speed PHY. Generated settings enable one CDC, 1,024-byte RX/TX buffers, 512-byte CDC endpoint buffer, no HID/MIDI/vendor instances, no DFU/network class. `esp_tinyusb/include/tusb_config.h:88` enables device mode and maps class counts at lines 192–201. Filtered compilation database contains **zero** HID-host, UVC-host, or TinyUSB `src/host` entries. Thus all three published host advisories are **not-current-path**, without relying on “USB requires physical access” as an exclusion.
|
||||
|
||||
Post-pin wrapper changelog 2.3.0 describes MTP, IDF6 MSC compatibility and power-management/light-sleep integration, not a newly identified CDC memory-safety fix. MSC/MTP is not used; `CONFIG_PM_ENABLE` absent. Upstream TinyUSB's newest published 0.21.0 notes include EP0 OUT copy clamping, HID descriptor bounds and MSC callback-size caps. These are baseline-release fixes rather than evidence that a later release must be backported. Spot-check: installed `src/device/usbd.c:881,920` limits `data_len` to `wLength` and clamps transferred bytes to remaining control data. SHA-256 **`38de54351d81878e6543f2b022d0c39f45d28c8f0888491796398dec45e727be`**. This is not a complete USB control-request audit or proof every upstream fix is in the fork.
|
||||
|
||||
## mDNS post-pin fixes
|
||||
|
||||
mDNS is network-reachable when STA discovery starts: `src/wifi_manager.c:117` calls the application service, whose `src/mdns_service.c:154–160` calls `mdns_init()`, `mdns_hostname_set()` and `mdns_instance_name_set()`. It is **not globally disabled**. The generated profile enables only predefined STA, maximum one interface/service, and has no `CONFIG_MDNS_ENABLE_BROWSE`. No application `mdns_service_add*`, `mdns_query_*` or `mdns_browse_*` calls were found.
|
||||
|
||||
1. **1.13.0 duplicate DNS-SD question expansion:** fetched [c56c725dd5e77c83c1a0683fce6dd3521e05cea2](https://github.com/espressif/esp-protocols/commit/c56c725d.patch). Upstream test explicitly calls repeated `_services._dns-sd._udp.local` PTR questions “heap amplification”: each expands the registered service list. Installed `mdns_receive.c:711–716` lacks the new `if (parsed_packet->discovery) continue;` guard and allocates inside `while (a)` after `a = mdns_priv_get_services()`. **Primitive unpatched, no current amplification trigger:** no services registered; `mdns_responder.c:38–45` zeroes the server and `:102–104` returns its initially empty service list. A hostname/instance label is not a service registration, and `MAX_SERVICES=1` is a capacity, not one active service. Reassess before advertising `_https`, `_ssh`, or any DNS-SD service; the guard is a sensible narrow defense-in-depth candidate, not a demonstrated current responder DoS fix.
|
||||
2. **1.13.1 staged-IP NUL termination:** fetched [b370a9abc29390b4adc612d251cc2dbbb325ff6f](https://github.com/espressif/esp-protocols/commit/b370a9ab.patch). Adds `new_staged_ip->hostname[MDNS_NAME_BUF_LEN - 1] = '\0';`; commit describes a GCC truncation warning. The installed receiver does **not** contain `rx_staged_ip_add()`; browsing is also disabled. Do not assert an installed unterminated-buffer vulnerability simply from version ordering or the release-note title. No current-path defect established.
|
||||
|
||||
Installed receiver SHA-256: **`9ada93eb2f89080a28bf2a849871e054032f06889c40397555de527af0e3d7bd`**. Earlier mDNS release notes mention parser/browse hardening, but this slice did not re-audit every pre-pin fix or equate the absence of a GHSA with a safe parser.
|
||||
|
||||
## LED strip and offline browser assets
|
||||
|
||||
**LED:** `src/status_led.c:15–36` fixes `max_leds=1`, WS2812/GRB, RMT, `with_dma=false`; `:72–77` sets pixel index zero then refreshes. No peer-controlled strip allocation or index is supplied. No later release/security correction was identified in the declared official channels; no blanket driver safety claim.
|
||||
|
||||
**Browser integration:** `src/web_ui.c:1507–1519` creates a terminal with `allowProposedApi:false`, loads only `FitAddon`, and `:1552–1554` does the same for admin. `:1531` feeds received bytes to `target.write(new Uint8Array(data), ...)`. Serial-device output is not automatically trustworthy just because a browser session authenticated; escape-sequence rendering remains a real boundary.
|
||||
|
||||
Post-pin 6.0.0 candidate dispositions:
|
||||
|
||||
- [PR5020 patch](https://github.com/xtermjs/xterm.js/pull/5020.patch), “Escape Unsafe HTML Characters in addon-serialize,” changes **addon-serialize HTML output**, not the core terminal renderer. That addon and `serializeAsHTML()` integration are absent. **Not-current-path**, not an assertion that all xterm 5.5 HTML rendering was repaired.
|
||||
- [PR5355 patch](https://github.com/xtermjs/xterm.js/pull/5355.patch), “Add note about reverse tabnapping,” is **documentation-only in addon-web-links typings**, warning about iframe/custom-handler behavior. The addon is not loaded. Do not report this as a core security fix missing from 5.5.0.
|
||||
- Core OSC 8 links exist even without addon-web-links. Checked [5.5.0 upstream provider](https://raw.githubusercontent.com/xtermjs/xterm.js/5.5.0/src/browser/OscLinkProvider.ts) and targeted matching snippets in hash-verified local JS: absent `allowNonHttpProtocols`, protocols are limited to `http:`/`https:`; default activation confirms navigation and sets `newWindow.opener=null` before assigning location. Application supplies no custom `linkHandler`. This rebuts an automatic arbitrary-protocol/tabnapping finding from these release notes, not all phishing/social-engineering risks or browser implementation issues.
|
||||
- Other release-note changes include OSC parsing/correctness, readonly behavior for disabled input, selection/renderer fixes, and new clipboard/progress functionality. Only fit is installed; search, image, serialize, web-links, ligatures, clipboard and WebGL addons are not supplied. Build/demo updates to Express, ws, webpack, axios, etc. are not automatically firmware/browser runtime dependencies. No additional current security trigger was established from that release-note screen.
|
||||
|
||||
A future xterm upgrade would need deliberate addon compatibility, offline asset provenance/regeneration and browser regression review; no asset or version change is authorized by this document.
|
||||
|
||||
## Validation and handoff limits
|
||||
|
||||
Performed: official bounded advisory/release fetches; all 22 IDF index-entry accounting; targeted source/config/caller and configured-input inspection; managed manifest checks; two local web-asset hash checks; seven Wi-Fi archive Git-blob comparisons; source snippet/hash capture. Final checks passed for document whitespace/final newline, six local links, dated scope, and twelve additional IDF plus three USB advisory IDs. Document-scoped `git diff --check` also passed (the new untracked file was explicitly checked by Python). Final Git status showed concurrent changes in release-notice/validation/icon-provenance files outside this ownership scope; none were edited or reverted here. No firmware build, host regression execution, browser test, USB transaction, radio packet test, fault injection, upload, monitor, erase, or target resource measurement was performed.
|
||||
|
||||
**Parent summary:** carry forward the Wi-Fi bundle correction investigation and the residual direct-SDK AES-DMA applicability question. Do not describe either as implemented, or the AES question as an established current-path vulnerability. Preserve prior detailed wolf/IDF review authority and all existing mitigations. The omitted official IDF index pages and named managed/browser publication channels now have a finite dated coverage record; full release-database contents, unpublished issues, opaque implementation analysis and hardware acceptance remain outside the completed evidence. Re-run a bounded publication check before release or when dependencies, enabled features, callers, or trust boundaries change.
|
||||
@@ -0,0 +1,207 @@
|
||||
# Dependency license inventory — Phase 9D
|
||||
|
||||
## Implementation addendum — updated 2026-09-18
|
||||
|
||||
**Missing generated modification/date notices: resolved; current count is nine C sources plus one header.** `tools/security_overrides.py` prepends the baseline project modification notice dated **2026-09-15**, identifying the editable generator and warning against editing derived copies. The wolfSSH `internal.c`, added `ssh.c` and generated `wolfssh/internal.h` also receive a prominent **2026-09-16** ordering-profile/provenance notice. Exact original hashes and exact-once edits remain required; upstream notices are retained. The header is a PUBLIC forced build input, not a C source. Both new mbedTLS copies retain their original Mbed TLS Contributors copyright and **`Apache-2.0 OR GPL-2.0-or-later`** SPDX header; the Apache-2.0 selection in this inventory is unchanged. This resolves the narrow notice-generation finding, **not release packaging or license clearance**.
|
||||
|
||||
Offline notice assembly tooling is implemented; actual firmware/device/browser delivery, radio-blob corresponding-source/exception questions, preferred-source packaging, wolfSSH license-package discrepancy, manual bitmap/mockup provenance limits and release-specific runtime/bootloader review remain open; [exact retained SVG provenance](icon_provenance.md) is resolved. [Offline notice assembly](release_packaging.md) is implemented by [tools/release_notices.py](../tools/release_notices.py): 75 mandatory hash/size-pinned inputs (nine IDF/toolchain notice entries semantically rebased for the candidate; the other 66 unchanged), deterministic bounded outputs, fail-closed preflight and no overwrite/fetch/build/device access. Supplied agent evidence: **36 notice fixture tests PASS; two actual bundles verified deterministic, each 77 files / 4,433,930 bytes**. This includes retained SVGs and [exact icon provenance](icon_provenance.md), not corresponding-source delivery or legal clearance. These are agent measurements, not a parent rebuild or reassembly in this documentation turn. Include `cmake/wolf_crypto_policy.{cmake,h}` and the ordering inputs described below with the reproducible build/source package. The IDF 5.5.3 migration is now integrated in the isolated validated candidate; this documentation update performs no upgrade, asset regeneration or device operation. Root pins select platform 6.13.0 / framework 3.50503.0 / both toolchains 14.2.0+20251107; see [integration and build/closure limits](idf_candidate_integration.md). The detailed 5.5.0 SDK/runtime inventory below remains historical, not a newly exhaustive 5.5.3 license audit.
|
||||
|
||||
## Historical inventory baseline — 2026-09-15
|
||||
|
||||
The original audit scope and artifact counts below describe the audit-time snapshot; generated-source status and the release checklist are updated explicitly where noted. Original research evidence is retained, not a claim that all artifacts were rebuilt or re-audited here.
|
||||
|
||||
Local, bounded audit: **2026-09-15**. This is an engineering inventory and release checklist, not legal advice, a legal compatibility opinion, an upstream currency check, or exhaustive file-by-file coverage. No network requests, dependency changes, asset regeneration, firmware build, tests, or device operations were performed. Hardware validation remains deferred for **Phase 9 as a whole**.
|
||||
|
||||
## Evidence and counting boundaries
|
||||
|
||||
- Project license: [root LICENSE](../LICENSE), [README license declaration](../README.md#license), and representative `src/` SPDX headers establish **`GPL-3.0-only`**, not “or later.” Dependencies keep their own notices and grants.
|
||||
- Resolution: [application manifest](../src/idf_component.yml), [dependencies.lock](../dependencies.lock), [platformio.ini](../platformio.ini), all six installed managed manifests and top-level license files.
|
||||
- **7 lock entries = 6 managed packages + IDF**. Managed packages comprise **5 direct + 1 transitive**. IDF is also a direct requirement. wolfSSH uses wolfSSL in this integration, although its resolved manifest declares no dependency edge; both are explicitly pinned by the application.
|
||||
- Platform: `platformio/espressif32@6.12.0`; installed SDK package `framework-espidf` **3.50500.0**, SDK **5.5.0**, compiler package `toolchain-xtensa-esp-elf` **14.2.0+20241119**. Installed package metadata, not merely requested ranges, was checked.
|
||||
- Existing `.pio/build/esp32-s3-devkitc-1-n16r8/project_description.json` names **113 nonempty configured components**. Its adjacent `esp32_serial_swiss_army_knife.map` has **68 distinct archive basenames** in the initial “Archive member included” region, before “Discarded input sections.” These are build-selection evidence, **not** 113 independently audited packages or proof all selected bytes survive section garbage collection. These pre-existing artifacts were not rebuilt or certified fresh.
|
||||
- Below: **6 managed rows, 18 SDK/runtime inventory groups, 2 browser packages, and 1 icon collection containing 2 SVGs**. Groups are not a complete SBOM and must not be summed into a unique package count (e.g. bundled libraries are inside IDF).
|
||||
- [Web provenance](../web_assets/SOURCES.md): all **8 recorded SHA-256 values** matched (3 JS/CSS, 2 license files, 3 gzip artifacts). Two SVG IDs and the attribution in `src/local_status_ui.c:158–161` were inspected for provenance only. Logo ownership is the project's recorded claim, not independently established authorship.
|
||||
- Initial Git status was clean. An unrelated untracked `docs/security_operations.md` appeared during this audit and was left unread and untouched. This audit creates only this file.
|
||||
|
||||
### Local path notation
|
||||
|
||||
Repository paths below are relative to the repository root. Installed paths are expressed using these **audit-time roots**, not environment variables that must already be set:
|
||||
|
||||
- `SDK/` = `/home/mscholz/.platformio/packages/framework-espidf/`
|
||||
- `TC/` = `/home/mscholz/.platformio/packages/toolchain-xtensa-esp-elf/`
|
||||
- `BUILD/` = `.pio/build/esp32-s3-devkitc-1-n16r8/`
|
||||
|
||||
`SDK/package.json` identifies <https://github.com/espressif/esp-idf>; `SDK/version.txt` says `5.5.0`. `SDK/LICENSE` is Apache-2.0. **There is no `SDK/COPYRIGHT` in this installed package**, and its `README.md` contains no license/copyright section. Do not assume the root Apache text covers every bundled library or is a complete attribution list. Component license files and source notice headers supply important exceptions.
|
||||
|
||||
## Managed components: complete lock-level inventory
|
||||
|
||||
All rows have a checked `managed_components/<directory>/idf_component.yml`. Versions below match the lock and installed manifests. Exact component hashes remain in `dependencies.lock`; a manifest-only assertion is not a cryptographic revalidation of all installed package contents.
|
||||
|
||||
| Package / resolved version | Relationship | Local license evidence | SPDX interpretation / disposition |
|
||||
| --- | --- | --- | --- |
|
||||
| `espressif/esp_tinyusb` **2.2.1** | Direct; requires TinyUSB | `managed_components/espressif__esp_tinyusb/LICENSE` | `Apache-2.0`; retain notices and license. |
|
||||
| `espressif/led_strip` **3.0.3** | Direct | `managed_components/espressif__led_strip/LICENSE` | `Apache-2.0`. |
|
||||
| `espressif/mdns` **1.12.0** | Direct; requested `^1.8.2` | `managed_components/espressif__mdns/LICENSE` | `Apache-2.0`; inventory resolved 1.12.0, not minimum 1.8.2. |
|
||||
| `espressif/tinyusb` **0.21.0~1** | Transitive through esp_tinyusb | `managed_components/espressif__tinyusb/LICENSE` | `MIT`, copyright 2012–2026 hathach. Nested `hw/bsp/espressif/components/led_strip/LICENSE` is Apache-2.0: it is not a seventh lock package. Retain nested notices if distributing the full package. |
|
||||
| `wolfssl/wolfssl` **5.8.2~1** | Direct | `managed_components/wolfssl__wolfssl/LICENSE.txt`, `README.md`, `wolfssl/version.h` | `GPL-3.0-or-later` open-source option; GPLv3 can be selected with this project. The commercial alternative is not needed merely because the application is GPLv3-only. |
|
||||
| `wolfssl/wolfssh` **1.4.20** | Direct | `managed_components/wolfssl__wolfssh/LICENSE.txt`, `README.md`, `src/internal.c` opening notice, `wolfssh/version.h` | **Packaging discrepancy:** LICENSE/README describe *wolfSSL/wolfCrypt*, GPLv2-or-later, and include GPLv2 text; inspected wolfSSH headers explicitly grant **`GPL-3.0-or-later`**. Preserve both evidence sets; select GPLv3 for this integration, and seek upstream packaging clarification before release. Do not label wolfSSH GPL-2.0-only or silently rewrite vendor files. |
|
||||
|
||||
Provenance references (not fetched): installed manifests point to [esp-usb](https://github.com/espressif/esp-usb), [esp-protocols](https://github.com/espressif/esp-protocols), [Espressif TinyUSB](https://github.com/espressif/tinyusb), [wolfSSL](https://github.com/wolfSSL/wolfssl), and [wolfSSH](https://github.com/wolfSSL/wolfssh). Installed repository metadata pins esp_tinyusb commit `8e779566ef71d43928cbf7e125e8eb54bab3f542`, mDNS `db06b19b7be729c163d346f62ec0eba01047b7f1`, and TinyUSB `7049c58a0e895acc92c6407574b05b5536eddfc8`. Use the LED strip manifest's repository metadata rather than infer a repository from its package name.
|
||||
|
||||
## SDK core, bundled libraries, and compiler runtime
|
||||
|
||||
“Selected” means archive-selection evidence in the existing application map, not a full object/license attribution trace. Unversioned rows mean **the installed IDF 5.5.0 snapshot**, not an invented upstream release. Header versions do not identify every Espressif patch; preserve the resolved SDK package itself.
|
||||
|
||||
| Group / version evidence | Checked local license / notice paths | SPDX / scope and action |
|
||||
| --- | --- | --- |
|
||||
| IDF core and Espressif drivers **5.5.0** | `SDK/LICENSE`, `SDK/package.json`, `SDK/version.txt`; original notices retained in the SDK overlays below | `Apache-2.0` baseline, not an umbrella claim over bundled code. Core archives selected. Include attribution and relevant notices, including bootloader distribution inputs. |
|
||||
| FreeRTOS **10.5.1**, Espressif port | `SDK/components/freertos/FreeRTOS-Kernel/LICENSE.md`; version in `include/freertos/task.h` under that kernel directory | `MIT`; selected. License text does not substitute for copyright notices in individual files. |
|
||||
| lwIP **2.2.0 development**, Espressif fork | `SDK/components/lwip/lwip/COPYING`; `src/include/lwip/init.h` under that directory has `LWIP_RC_DEVELOPMENT` | `BSD-3-Clause`; selected. Preserve Swedish Institute of Computer Science attribution; do not call it stock release 2.2.0. |
|
||||
| Mbed TLS **3.6.3** | `SDK/components/mbedtls/mbedtls/LICENSE`; `include/mbedtls/build_info.h` under that directory | `Apache-2.0 OR GPL-2.0-or-later`; choose Apache-2.0 here. `libmbedtls.a`, `libmbedcrypto.a`, `libmbedx509.a` selected. Framework has a separate `framework/LICENSE` when shipping the full source tree. |
|
||||
| HTTP parser **2.7.0** | `SDK/components/http_parser/LICENSE.txt`, `http_parser.h` in that component | `MIT`; selected. Preserve NGINX/Igor Sysoev and Joyent/Node attribution in the actual text. |
|
||||
| argtable3, IDF snapshot | `SDK/components/console/argtable3/LICENSE` | **Aggregate**, including `BSD-3-Clause`, `BSD-2-Clause`, and `TCL` terms. It includes Stewart Heitmann, NetBSD getopt, Tcl, C Hash Table, and Better String notices. Do not reduce the entire file to one BSD label. `libconsole.a` selected; sublibrary/object attribution not exhaustively traced. |
|
||||
| linenoise, IDF snapshot | `SDK/components/console/linenoise/LICENSE` | `BSD-2-Clause`, Salvatore Sanfilippo and Pieter Noordhuis; console selected. |
|
||||
| TLSF allocator, IDF snapshot | `SDK/components/heap/tlsf/include/tlsf.h`, `SDK/components/heap/tlsf/tlsf_block_functions.h` | `BSD-3-Clause` SPDX headers, Matthew Conte 2006–2016; heap selected. No standalone TLSF license text appeared in the historical inventory; offline assembly now retains a scoped full TLSF rendering and provenance. Final object coverage/delivery still require review, not a generic BSD text with another author's name. |
|
||||
| Xtensa HAL, IDF snapshot | `SDK/components/xtensa/include/xtensa/hal.h` opening notice | `MIT`-style full permission notice, Cadence 1999–2015; `libxt_hal.a`/`libxtensa.a` selected. Header evidence is not an audit of every HAL object or ROM implementation. |
|
||||
| Espressif prebuilt radio libraries, IDF snapshot | `SDK/components/esp_wifi/lib/LICENSE`, `SDK/components/esp_phy/lib/LICENSE`, `SDK/components/esp_coex/lib/LICENSE` | All three files are byte-identical to `SDK/LICENSE` (`Apache-2.0`). Map selects Wi-Fi `libcore.a`, `libespnow.a`, `libmesh.a`, `libnet80211.a`, `libpp.a`, and PHY `libbtbb.a`, `libphy.a`. `libesp_coex.a` is selected but that does not prove `libcoexist.a` was selected. **Corresponding-source/system-library question remains open**, despite permissive binary redistribution terms. |
|
||||
| Newlib **4.3.0** and IDF libc integration | `TC/xtensa-esp-elf/include/_newlib_version.h`; `TC/share/licenses/newlib/COPYING.NEWLIB`; `SDK/components/newlib/COPYING.NEWLIB` | Mixed per-file licenses; **no single SPDX expression established**. Toolchain notice file has 1,293 lines vs SDK copy's 952; preserve the actual runtime package's notices, not just the older SDK summary. `libc.a` and `libnewlib.a` selected. Target-specific LGPL/GPL sections for other architectures/Linux are not proof they apply to ESP32-S3; trace selected objects before making that claim. |
|
||||
| GCC runtime / libstdc++ **14.2.0**, package **14.2.0+20241119** | `TC/share/licenses/gcc/COPYING.RUNTIME`, `TC/share/licenses/gcc/gcc/COPYING3`; `TC/xtensa-esp-elf/include/c++/14.2.0/xtensa-esp-elf/esp32s3/bits/c++config.h` | Header explicitly supports `GPL-3.0-or-later WITH GCC-exception-3.1`; `libgcc.a`/`libstdc++.a` selected. Check exception eligibility and per-object terms; preserve exception and GPL notices. Package-level `GPL-2.0-or-later` metadata is not a runtime license inventory. `COPYING3.LIB` is LGPL text, **not** the GCC runtime exception. |
|
||||
| cJSON **1.7.18** | `SDK/components/json/cJSON/LICENSE`, `cJSON.h` there | `MIT`; configured SDK component, no `libjson.a` selection in the inspected initial map region. Retain when distributing the SDK source; not asserted live in this image. |
|
||||
| FatFs **R0.15 w/patch2** | `SDK/components/fatfs/src/ff.c` opening full notice | `LicenseRef-FatFs-local-notice` (descriptive local identifier, not an official SPDX ID): source-retention permission/disclaimer, ChaN 2022. Configured, no `libfatfs.a` selection observed. |
|
||||
| SPIFFS, IDF snapshot | `SDK/components/spiffs/spiffs/LICENSE` | `MIT`, Peter Andersson; configured, no `libspiffs.a` selection observed. |
|
||||
| protobuf-c, IDF snapshot | `SDK/components/protobuf-c/protobuf-c/LICENSE` | `BSD-2-Clause`; configured, no `libprotobuf-c.a` selection observed. |
|
||||
| ESP-MQTT, IDF snapshot | `SDK/components/mqtt/esp-mqtt/LICENSE` | `Apache-2.0`; configured, no `libmqtt.a` selection observed. |
|
||||
|
||||
The eighteenth group is **wpa_supplicant**, recorded separately because its license requires reading two files: `SDK/components/wpa_supplicant/COPYING` redirects to `SDK/components/wpa_supplicant/README`, whose full grant is **`BSD-3-Clause`**. The historical GPLv2 option was retired in 2012 according to COPYING; do not flag a GPLv2-only conflict. `libwpa_supplicant.a` is selected; an independent upstream version was not established (use the IDF snapshot).
|
||||
|
||||
The installed license-path inventory also found OpenThread, NimBLE/Bluetooth, micro-ecc, Unity/CMock, and tool/test-specific licenses and NOTICE files. They were not promoted to fully reviewed linked dependencies. A full SDK/source or toolchain redistribution must preserve their applicable texts too. No inference that all of these are absent from every bootloader, ROM, or future build is made. The bootloader map, every bundled file, and every selected runtime object were not audited. Newlib, argtable3, radio blobs, ROM attribution, and nested package contents remain aggregation boundaries needing release-specific review.
|
||||
|
||||
## Checked-in assets and existing notice sufficiency
|
||||
|
||||
| Asset | Version / local evidence | Finding |
|
||||
| --- | --- | --- |
|
||||
| `@xterm/xterm` | **5.5.0**; `web_assets/xterm.js`, `xterm.css`, `xterm.LICENSE`; [SOURCES.md](../web_assets/SOURCES.md) provides exact npm URLs/hashes | `MIT`. Full notice retained in repository; CSS also retains a notice. Minified JS has no `copyright`, `permission is hereby`, or `license` marker. |
|
||||
| `@xterm/addon-fit` | **0.10.0**; `web_assets/addon-fit.js`, `addon-fit.LICENSE`; same provenance document | `MIT`. Full notice retained in repository; minified JS lacks those notice markers. |
|
||||
| Pictogrammers Material Design Icons | Official **7.4.47**, immutable commit `9e04201d4557e729822fb57f62a316c3dea1d4a8`; [exact evidence](icon_provenance.md) | Retained SVGs are byte-identical to upstream; metadata credits **Google (USB)** and **Simran (Wi-Fi)**. Apache-2.0 package evidence, full license text and a pinned-tree check finding no NOTICE are retained. Manual firmware bitmap derivation and the distinct Wi-Fi mockup path remain unresolved; source identity is not complete derivative or release clearance. |
|
||||
| Project logo | `web_assets/logo.png`, derived from `images/logo.png` per SOURCES.md | Project-owned according to provenance document; no independent third-party license assigned. Generator's MIT banner must not be treated as proof that the logo was separately relicensed MIT. |
|
||||
|
||||
Historical 2026-09-15 `git ls-files '*LICENSE*' '*COPYING*' '*NOTICE*'` found **4 tracked license files**: root GPLv3, two web MIT texts, and the icon summary. That historical count predates the retained upstream icon evidence and full Apache text now included in the 75-input catalog. Neither repository retention nor assembly proves recipient delivery or release compliance.
|
||||
|
||||
`web_assets/generate_embedded_assets.py` embeds three gzip files and the logo, **not either MIT license file**. Its generated-source banner points back to provenance/licenses; it is not the upstream MIT notice itself. This proves the embedding path does not carry the separate notice texts, not that every possible product-delivery channel lacks notices. Coordinate a release notice bundle and a way for recipients of the browser assets to obtain the notices (for example a bundled/served third-party license resource); verify the actual delivery path. Do not assume repository-only notices accompany a standalone firmware/device or a downloaded JS response. No assets or web endpoints were changed in this audit.
|
||||
|
||||
Preferred-source availability is a separate issue from retaining MIT notices. For a GPL-covered combined release, preserve exact upstream preferred-form browser sources and build inputs where required; the checked-in minified JS distributions and gzip/C arrays are not automatically the preferred form for modification. Source-map references alone do not provide that source. Whether independent browser packages are mere aggregation should be assessed for the actual distribution, not assumed either way.
|
||||
|
||||
## GPLv3 distribution and build-overlay obligations
|
||||
|
||||
The inspected MIT, BSD and Apache-2.0 grants provide generally GPLv3-compatible routes; the project's `GPL-3.0-only` choice can use wolfSSL/wolfSSH's version-3 alternatives. This is **not** permission to remove upstream notices or declare all files GPL-only. No commercial wolfSSL/wolfSSH license is recommended on the evidence here. Commercial arrangements would be a separate decision if a planned distribution cannot satisfy the available open-source terms, not a cure for unrelated project/SDK obligations.
|
||||
|
||||
For conveyance, review GPLv3 §§1, 4–6, 7 and 10 in [LICENSE](../LICENSE):
|
||||
|
||||
1. Supply required license/copyright/warranty notices and use a §6-compliant corresponding-source delivery method for binaries. A dependency name, registry URL, generic upstream source link, or this inventory alone is not corresponding source or a compliant written offer.
|
||||
2. Preserve exact application source, lock/manifests, relevant SDK and managed source, configuration/build/link inputs, asset preferred sources and generation scripts. Include the scripts controlling compilation/installation. Generic unmodified build tools may fall outside corresponding source, but record precise versions needed for reproduction; if distributing those tools themselves, satisfy their separate licenses.
|
||||
3. The Phase 9C–9D firmware compiles **pinned originals plus project edits**. Include `tools/security_overrides.py`, `cmake/security_overrides.cmake`, root CMake integration, `cmake/wolf_crypto_policy.{cmake,h}`, the complete `tools/wolfssh_order/` inputs, original hash-matching C/header sources, and the means to reproduce all nine C copies and the PUBLIC forced header. A stock IDF/wolfSSH tarball alone omits the compiled modifications. Generated copies alone are not a substitute for the preferred editable patch/generator inputs.
|
||||
4. **Resolved; updated 2026-09-18:** all nine generated C files plus one header below retain upstream notices and carry explicit project modification/date notices from the generator (baseline 2026-09-15, plus 2026-09-16 ordering notices on wolfSSH outputs). The historical four-file audit found the notice missing; this narrow defect is repaired. GPLv3 §5(a) calls for a relevant date, and Apache-2.0 §4(b) requires prominent change notices. Continue including notices and reproducible generator inputs in distribution; do not hand-edit generated copies or vendor sources.
|
||||
5. If distributing a GPLv3 “User Product,” assess and provide required Installation Information under §6. Do not publish device passwords, private keys, Wi-Fi material, account verifiers, tickets, NVS images, or real credential backups as build/source artifacts. Installation compliance should use appropriate documented procedures, not routine secret disclosure. Secure boot was not enabled or altered here.
|
||||
6. **Prebuilt radio libraries are the principal unresolved compatibility/source risk.** Apache-2.0 permits binary redistribution, but does not itself satisfy GPL corresponding-source obligations for a combined firmware. Establish whether required preferred source is obtainable, or whether a defensible GPL System Library/other exception applies to the actual linked artifacts. Static linkage is not automatically “mere aggregation.” Do not declare distribution cleared or definitely prohibited from this bounded audit. Escalate this before a public binary/device release.
|
||||
|
||||
Current generated paths and retained licenses (nine C sources plus one header). Here `BUILD/` denotes the validated candidate build, `.pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8/`, rather than the historical audit root:
|
||||
|
||||
- `BUILD/security_overrides/https_server/https_server.c` — Espressif Apache-2.0.
|
||||
- `BUILD/security_overrides/httpd_parse/httpd_parse.c` — Espressif Apache-2.0.
|
||||
- `BUILD/security_overrides/httpd_ws/httpd_ws.c` — Espressif Apache-2.0; five signed fixed-header receive-size comparisons, with generated modification/rebase notices.
|
||||
- `BUILD/security_overrides/esp_tls_mbedtls/esp_tls_mbedtls.c` — Espressif Apache-2.0.
|
||||
- `BUILD/security_overrides/wolfssh_internal/internal.c` — wolfSSH GPL-3.0-or-later header.
|
||||
- `BUILD/security_overrides/wolfssh_ssh/ssh.c` — wolfSSH GPL-3.0-or-later header.
|
||||
- `BUILD/security_overrides/wolfssh_include/wolfssh/internal.h` — wolfSSH GPL-3.0-or-later header; ABI overlay propagated BEFORE PUBLIC and as a PUBLIC forced include, not a C compilation unit.
|
||||
- `BUILD/security_overrides/dhcpserver/dhcpserver.c` — retained Espressif Apache-2.0 header.
|
||||
- `BUILD/security_overrides/mbedtls_ssl_tls/ssl_tls.c` — retained Mbed TLS Contributors, `Apache-2.0 OR GPL-2.0-or-later`.
|
||||
- `BUILD/security_overrides/mbedtls_x509_create/x509_create.c` — retained Mbed TLS Contributors, `Apache-2.0 OR GPL-2.0-or-later`.
|
||||
|
||||
### Archived ordering patches and license context
|
||||
|
||||
[Ordering provenance](../tools/wolfssh_order/README.md) documents the restricted CVE-2025-14942 correction, not a complete upstream backport or upgrade. [`delta.json`](../tools/wolfssh_order/delta.json) is the preferred editable, executable consolidated delta; archived upstream mail patches are provenance/prerequisite evidence, not a statement that all their hunks compile into the firmware. [`provenance.json`](../tools/wolfssh_order/provenance.json) retains the original URLs and every full embedded commit ID. Archived bytes fetched 2026-09-16 are pinned as follows; future PR responses may differ:
|
||||
|
||||
| Archive under `tools/wolfssh_order/` | SHA-256 |
|
||||
| --- | --- |
|
||||
| `793.patch` | `66aa999521800371f97b340db2bff162be4e8ffab4b4b953f9d32b8e33f56cea` |
|
||||
| `819.patch` | `8e63c2b24679a7d831f7dba12e412d2f8a7fdc391fc4e8fd33f029487110219b` |
|
||||
| `840.patch` | `23e25873cb4dfa36063357111019e0960dfc008a68cc29d6e2d5c37bde47ec94` |
|
||||
| `855.patch` | `b94393df9528f66f6df1aba94015331fee3110c9ea2007a0eff369d8887b4b29` |
|
||||
| `921.patch` | `f6c88598d7f3c94d92c31cecc4ef1e6d779b49f9eff6556ac1c57426d272d504` |
|
||||
|
||||
Preserve upstream mail authorship, commit metadata and applicable file notices with these archives. They contain upstream wolfSSH code/context, not newly project-authored material automatically relicensed by the generator's `GPL-3.0-only` SPDX line. The compiled wolfSSH C/header derivatives retain the upstream **GPL-3.0-or-later** grants; select GPLv3 for this integration. Patch hashes prove identity, not license permission or full archive-level clearance. Retain the upstream license texts and package discrepancy evidence, and review applicable per-file terms for any archived but unapplied example/test/workflow content when assembling the source package. No standalone blanket license grant for every patch byte is inferred from mail-patch format. Source/notice delivery and release packaging remain open.
|
||||
|
||||
For Apache-2.0 components, retain required attribution and any applicable upstream NOTICE contents under §4(d), **if present**; do not fabricate NOTICE text or assert every Apache package must have a NOTICE file. For MIT/BSD components, distribute the actual copyright, permission/conditions and disclaimer, not merely an SPDX name. Preserve mixed notices such as argtable3 and Newlib rather than replacing them with generic templates.
|
||||
|
||||
## Actionable release work (not performed)
|
||||
|
||||
Notice assembly is implemented; the remaining release actions below have not been completed.
|
||||
|
||||
| Priority | Action / completion evidence |
|
||||
| --- | --- |
|
||||
| Release blocker to resolve | Document a defensible corresponding-source/exception basis for the **selected radio blobs** and other source-unavailable linked content. This is an unresolved question, not a legal verdict. |
|
||||
| Assembly tooling implemented; delivery pending | [Offline tool and procedure](release_packaging.md) assemble 75 pinned inputs, retaining GPL/Apache/MIT/BSD texts, mixed SDK/toolchain Newlib and argtable3 notices, GCC exception and a scoped full TLSF rendering. Supplied agent fixture suite: 36 PASS; two actual bundles independently verified deterministic, each 77 files / 4,433,930 bytes. Validate final firmware/device/browser recipient delivery; assembly is not corresponding-source delivery or legal clearance. |
|
||||
| Before distributing icon derivatives | [Exact retained SVG provenance](icon_provenance.md) is resolved at official 7.4.47; full Apache-2.0 text and attribution are retained, with no NOTICE found in the pinned distribution. Preserve manual bitmap preferred sources and resolve the distinct mockup Wi-Fi path/derivation limits. Recipient delivery remains open; do not invent a NOTICE or rasterizer. |
|
||||
| Resolved in generator; retain in distribution | Prominent modification notices cover **nine C sources plus one header** (baseline **2026-09-15**, wolfSSH ordering **2026-09-16**), with original notices retained. Release packaging must still include them, archived patches/provenance, consolidated delta and reproducible build inputs. |
|
||||
| Upstream clarification | Record/resolve wolfSSH 1.4.20 package LICENSE/README vs source-header mismatch without editing the managed package in place. GPLv3 is supported by the inspected headers and is also within the package prose's “or later” wording. |
|
||||
| Release packaging verification | Archive exact corresponding source/build inputs and required asset preferred sources; validate source delivery/offer and any User Product installation information. Recheck bootloader, runtime objects and aggregation boundaries against the final release map. |
|
||||
|
||||
## Minimal offline repeat check
|
||||
|
||||
Run from the repository root against an already-resolved local environment. This only reads manifest/license/provenance metadata; it does **not** invoke PlatformIO, install dependencies, regenerate assets, read configuration secrets, or validate legal compliance. It requires Python 3 and PyYAML (the latter is normally available in the component-manager environment). A missing prerequisite must be reported, not automatically downloaded. Adjust the two package roots if auditing another machine.
|
||||
|
||||
```sh
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import yaml
|
||||
|
||||
sdk = Path('/home/mscholz/.platformio/packages/framework-espidf')
|
||||
tc = Path('/home/mscholz/.platformio/packages/toolchain-xtensa-esp-elf')
|
||||
expected = {
|
||||
'espressif/esp_tinyusb': ('2.2.1', 'LICENSE'),
|
||||
'espressif/led_strip': ('3.0.3', 'LICENSE'),
|
||||
'espressif/mdns': ('1.12.0', 'LICENSE'),
|
||||
'espressif/tinyusb': ('0.21.0~1', 'LICENSE'),
|
||||
'wolfssl/wolfssh': ('1.4.20', 'LICENSE.txt'),
|
||||
'wolfssl/wolfssl': ('5.8.2~1', 'LICENSE.txt'),
|
||||
}
|
||||
lock = yaml.safe_load(Path('dependencies.lock').read_text())
|
||||
assert set(lock['dependencies']) == set(expected) | {'idf'}
|
||||
assert str(lock['dependencies']['idf']['version']) == '5.5.0'
|
||||
assert sdk.joinpath('version.txt').read_text().strip() == '5.5.0'
|
||||
assert json.loads(sdk.joinpath('package.json').read_text())['version'] == '3.50500.0'
|
||||
assert json.loads(tc.joinpath('package.json').read_text())['version'] == '14.2.0+20241119'
|
||||
for name, (version, license_name) in expected.items():
|
||||
directory = Path('managed_components') / name.replace('/', '__')
|
||||
manifest = yaml.safe_load((directory / 'idf_component.yml').read_text())
|
||||
assert str(lock['dependencies'][name]['version']) == version
|
||||
assert str(manifest['version']) == version
|
||||
license_path = directory / license_name
|
||||
assert license_path.stat().st_size > 0
|
||||
print(name, version, license_path,
|
||||
hashlib.sha256(license_path.read_bytes()).hexdigest())
|
||||
for name, digest in re.findall(r'\| `([^`]+)` \| `([a-f0-9]{64})`',
|
||||
Path('web_assets/SOURCES.md').read_text()):
|
||||
assert hashlib.sha256((Path('web_assets') / name).read_bytes()).hexdigest() == digest
|
||||
for path in [Path('LICENSE'), Path('third_party/material-design-icons/LICENSE'),
|
||||
sdk / 'LICENSE', sdk / 'components/wpa_supplicant/README',
|
||||
sdk / 'components/console/argtable3/LICENSE',
|
||||
sdk / 'components/newlib/COPYING.NEWLIB',
|
||||
tc / 'share/licenses/newlib/COPYING.NEWLIB',
|
||||
tc / 'share/licenses/gcc/COPYING.RUNTIME']:
|
||||
assert path.stat().st_size > 0
|
||||
print(path, hashlib.sha256(path.read_bytes()).hexdigest())
|
||||
for name, digest in {
|
||||
'dependencies.lock': '2d1c62093cafdb54cd209813c526c35e2e470b62cc6f5b0cde9a040569662726',
|
||||
'src/idf_component.yml': '7fe8cfd25db36dd23907cd1aa7645f3d5b5f34f7ed587cd2599690b5ffbda053',
|
||||
'platformio.ini': 'b0139667b13c0e254b5ffa67df6cb323f4554a3657fb100330067d6110d57518',
|
||||
}.items():
|
||||
assert hashlib.sha256(Path(name).read_bytes()).hexdigest() == digest, name
|
||||
print('PASS: scoped manifest versions, notice presence, and recorded hashes')
|
||||
PY
|
||||
```
|
||||
|
||||
This is a **drift tripwire**, not a full component-content verifier or complete license scanner. On changes, inspect new notices and nested components, trace final application/bootloader maps, revisit the build overlays and regenerate a release inventory deliberately. Do not simply repin expected values. Network/upstream authentication, advisory review, legal aggregation analysis, complete notice assembly and hardware validation remain outside this check.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bounded ordinary HTTPS idle retention
|
||||
|
||||
Current owner/SDK contract. The user accepted idle cleanup as working; [overall acceptance](roadmap.md#phase8-acceptance-evidence) records current status and evidence limits. No individual unreported soak, fault or reserve result is inferred.
|
||||
Current owner/SDK contract. The user accepted idle cleanup as working; [overall acceptance](web_administration_acceptance.md) records current status and evidence limits. No individual unreported soak, fault or reserve result is inferred.
|
||||
|
||||
## Evidence and scope
|
||||
|
||||
@@ -53,7 +53,7 @@ Detach first prevents submissions, then waits at most one second for any submitt
|
||||
|
||||
`tests/web_httpd_idle/run.py` exercises production lifecycle/sweep paths, installed SDK request cleanup, host socketpair/readiness/shutdown/fd reuse and deterministic TLS/timer/queue doubles. `tests/web_admin_transport/server_lifecycle.py` checks server composition. Historical host validation passed; these references do not claim a new run or real target scheduler/TLS timing.
|
||||
|
||||
Bounded storage: six observation rows (144 bytes), one persistent timer (installed non-profiled layout 32 bytes before allocator overhead), lifecycle/generation flags and one reserved probe. SDK control UDP/mailbox allocation is transient and separate. No per-connection payload/task/stack/socket growth. Actual HTTPD/timer margins and runtime overhead/reserve floors require target measurement, not host sizes. See [latest firmware resources](roadmap.md#phase8-acceptance-evidence).
|
||||
Bounded storage: six observation rows (144 bytes), one persistent timer (installed non-profiled layout 32 bytes before allocator overhead), lifecycle/generation flags and one reserved probe. SDK control UDP/mailbox allocation is transient and separate. No per-connection payload/task/stack/socket growth. Actual HTTPD/timer margins and runtime overhead/reserve floors require target measurement, not host sizes. See [latest firmware resources](web_administration_acceptance.md#latest-firmware-evidence).
|
||||
|
||||
## Regression procedure — not execution evidence
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# Exact USB and Wi-Fi icon provenance
|
||||
|
||||
Reviewed 2026-09-16. This record resolves the identity of the **two retained
|
||||
preferred-form SVG sources at Material Design Icons 7.4.47**, not all project
|
||||
artwork, historical authorship of manual edits, or release compliance.
|
||||
No firmware, mockup, web asset, or generated asset was changed or regenerated.
|
||||
|
||||
## Official pinned source and licenses
|
||||
|
||||
Official SVG distribution: <https://github.com/Templarian/MaterialDesign-SVG>.
|
||||
Its upstream README identifies this as the production SVG distribution and
|
||||
points to the main MaterialDesign repository for issues. The main repository
|
||||
URL with tag `v7.4.47` returned 404; it is not used as version evidence.
|
||||
|
||||
- Distribution tag: `v7.4.47`.
|
||||
- Annotated tag object: `5edde266e281d26a03dcfa89fb651183cbab0f2e`.
|
||||
- Peeled commit: `9e04201d4557e729822fb57f62a316c3dea1d4a8`.
|
||||
- Upstream `package.json`: `@mdi/svg`, version `7.4.47`, license `Apache-2.0`.
|
||||
- Tag timestamp: `2023-12-26T23:33:26Z`; GitHub reports it **unsigned**.
|
||||
HTTPS retrieval and content pins are evidence, not signature authentication.
|
||||
|
||||
Unmodified upstream SVGs, LICENSE, README, package metadata, complete `meta.json`,
|
||||
and GitHub tag/tree responses are retained in
|
||||
[`third_party/material-design-icons/upstream-7.4.47/`](../third_party/material-design-icons/upstream-7.4.47/).
|
||||
`downloads.json` records exact retrieval URLs, sizes and SHA-256 hashes. Raw
|
||||
source URLs use the immutable commit, not a moving branch or tag. API responses
|
||||
are retained as fetched; future API formatting is not assumed stable.
|
||||
The full metadata/tree files intentionally preserve the evidence behind the
|
||||
per-icon selections and absence check, rather than only project-written claims.
|
||||
|
||||
The existing `third_party/material-design-icons/LICENSE` is byte-identical to
|
||||
the pinned upstream Pictogrammers Free License summary and remains unchanged.
|
||||
It distinguishes icon Apache-2.0/respective grants from code MIT terms. The
|
||||
package declares Apache-2.0; the two metadata entries contain authors but no
|
||||
per-icon alternate grant. This is the scoped upstream licensing evidence for
|
||||
these two SVGs, not a blanket conclusion about every icon in the collection.
|
||||
`upstream-7.4.47/Apache-2.0.txt` supplies the **full Apache-2.0 text**, fetched
|
||||
from <https://www.apache.org/licenses/LICENSE-2.0.txt> (SHA-256
|
||||
`cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30`).
|
||||
It is a separately sourced license text, not a file falsely attributed to the
|
||||
upstream icon tree. The retained non-truncated recursive tree has no path
|
||||
containing `notice` (case-insensitive); no upstream NOTICE was found in this
|
||||
pinned distribution and none is fabricated here. This does not audit unrelated
|
||||
repositories or assert that a NOTICE is mandatory for every Apache work.
|
||||
|
||||
| Icon | Metadata attribution | Stable icon ID | First-version metadata | SVG SHA-256 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `usb` | Google | `25033E0B-3AD4-414D-9972-559F2690FC1D` | `1.5.54` | `c9918e9a983fbd788378ca4c524e7a07a0d5eedcaeff73d19814e6f6ae221f22` |
|
||||
| `wifi-strength-4` | Simran | `41B86B22-7245-4A97-9BAA-3E9EBD44CEB0` | `2.3.50` | `89d14daf863076b0f73c76d913212875f2e9bcddaaf49c9e5d1825e0b2dc2d5f` |
|
||||
|
||||
The metadata `version` fields describe icon introduction versions, **not** a
|
||||
contradiction of package 7.4.47. Matching these sources to that release does not
|
||||
prove they originated in that release or were originally downloaded from it.
|
||||
|
||||
## Exact comparison to project derivatives
|
||||
|
||||
1. **Retained SVGs:** `third_party/material-design-icons/usb.svg` (403 bytes)
|
||||
and `wifi-strength-4.svg` (219 bytes) are each byte-for-byte identical to the
|
||||
pinned upstream originals, including every path command, coordinate, arc
|
||||
flag and viewBox. Each has one path, `viewBox="0 0 24 24"`, and no transform
|
||||
on the root or path. No coordinate rounding, normalization or visual-match
|
||||
inference was used. The files remain unchanged.
|
||||
2. **USB mockup:** `docs/phase7c_icon_mockup.svg` symbol `usb` has exactly the
|
||||
upstream `d` string and 24-by-24 viewBox, without symbol/path transforms.
|
||||
Its two direct use definitions are `(x,y,width,height)=(150,4,30,30)` inside
|
||||
reusable `status`, and `(8,72,30,30)` on the broker page. These apply uniform
|
||||
scale `30/24 = 1.25` and the stated translations. Status is instantiated in
|
||||
page groups translated by `(62,98)`, `(592,98)`, `(62,438)`, `(592,438)`;
|
||||
the broker use is in `(62,438)`. Thus status USB geometry maps as
|
||||
`(X,Y)=(pageX+150+1.25*x, pageY+4+1.25*y)` and broker USB as
|
||||
`(X,Y)=(62+8+1.25*x,438+72+1.25*y)`. CSS adds presentation fill/stroke;
|
||||
this is path geometry equivalence, not pixel/rendering equivalence.
|
||||
3. **Wi-Fi mockup:** symbol `wifi` in that same mockup uses a different,
|
||||
segmented path starting `M1 8q11-9 22 0`. It is **not** an exact copy of
|
||||
`wifi-strength-4` (which starts `M12,3C7.79,3`). No exact affine derivation
|
||||
or upstream provenance for that distinct mockup path is established here.
|
||||
4. **Actual firmware:** `src/local_status_ui.c` explicitly calls the USB mask
|
||||
hand-rasterized and the Wi-Fi masks compact segmented adaptations. The exact
|
||||
editable row bytes are:
|
||||
|
||||
```text
|
||||
s_icon_usb: 18 3c 18 5a 3e 18 3c 18
|
||||
s_icon_wifi_full: 7e 81 3c 42 18 24 00 18
|
||||
s_icon_wifi_three: 00 00 3c 42 18 24 00 18
|
||||
s_icon_wifi_two: 00 00 00 00 18 24 00 18
|
||||
s_icon_wifi_one: 00 00 00 00 00 00 00 18
|
||||
```
|
||||
|
||||
These manual 8-by-8 masks are not SVG coordinates or exact scaled vector
|
||||
paths. No deterministic rasterizer, sampling/threshold rule or edit history
|
||||
establishes an exact upstream-to-mask transform. Their declared design
|
||||
provenance is supported by the now-verified retained sources, but **exact
|
||||
mechanical derivation of the firmware masks remains unresolved**. Preserve
|
||||
the C arrays as the preferred editable form of those manual modifications,
|
||||
together with both original SVGs; do not substitute an invented generator.
|
||||
The existing source comment is the adaptation notice. This dated record
|
||||
documents the inspected modifications; it does not invent their edit date.
|
||||
|
||||
## Offline checks and release boundary
|
||||
|
||||
`python3 tests/release_notices/run.py` verifies the pinned source bytes and Git
|
||||
blob identities, version/author metadata, catalog inclusion, exact SVG geometry,
|
||||
USB mockup placement/ancestry, distinct Wi-Fi mockup path, and current manual
|
||||
mask bytes. Negative checks reject coordinate and transform changes. Tests do
|
||||
not regenerate artwork or claim raster equivalence.
|
||||
|
||||
The notice catalog retains all previous inputs and adds the two existing SVGs,
|
||||
these source originals, licensing/metadata evidence and this record. The bundle
|
||||
is still **not corresponding source or release clearance**. Firmware C arrays
|
||||
and the full mockup remain in the project, not copied into this bounded notice
|
||||
bundle; include them in a separately reviewed corresponding-source delivery
|
||||
where required. Radio-blob/source-exception questions, wolfSSH packaging,
|
||||
recipient notice/source delivery and other release gates remain open.
|
||||
|
||||
Older global inventory/packaging/memory documents describe the pre-verification
|
||||
state and counts. They were intentionally not edited in this scoped task;
|
||||
this record and the current catalog provide the narrower updated evidence.
|
||||
@@ -0,0 +1,190 @@
|
||||
# IDF 5.5.3 uncommitted rebase review
|
||||
|
||||
Review date: **2026-09-18**. Baseline Git HEAD: `cdc4d4a8df02c2c44180b9b6d17bf6104c9fbcd2`.
|
||||
|
||||
**Current disposition: existing protected corrections are semantically retained; the initial stale compiled WS input and missing receive-error regression blockers are resolved by the supplied final fresh-build/24-suite evidence below.** No evidence found that a protected correction should be removed as superseded. This is a bounded rebase review, not advisory recertification, device acceptance or release clearance.
|
||||
|
||||
Initial reviewer-owned change: **this document only**. Existing working changes were preserved. No SDK installation, firmware build, upload, erase, asset regeneration or production edit was performed. A concurrent change to `third_party/release-notices/inputs.json` appeared during review; it was left untouched and was not reviewed in that initial pass. The later supplied notice review semantically rebased nine entries, retaining the other 66 unchanged; this does not turn the initial review into a catalog audit.
|
||||
|
||||
## Final follow-up — 2026-09-18
|
||||
|
||||
Supplied final parent command, run from the repository root:
|
||||
|
||||
```sh
|
||||
CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py --build-dir .pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8 --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --platformio-core-dir .pio/idf-candidate-5.5.3/core --interop --web-performance
|
||||
```
|
||||
|
||||
**PASS 24/24.** This selects the actual fresh isolated build, SDK and toolchain core; it is not a default-root-build execution. The separate fresh `.pio/idf-candidate-5.5.3/app-validated` build **PASS** reports **95,552 B linked RAM / 1,749,493 B flash**, versus historical **94,340 / 1,768,901 B** (**+1,212 B RAM / −19,408 B flash**). The default parent `pio run` timed out after **200 seconds during installation, before compilation**; no normal root build PASS is claimed.
|
||||
|
||||
Pre/post source equality: **3,237 files**, SHA-256 **`3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031`**. Actual generated-input registration covers **nine C sources plus one forced header**. WS receive tests exercise generated code: **982 cases / 10 mutation checks**, including the five signed `sizeof` corrections. The stale web-cookie fixture asserting IDF 5.5.0 was corrected, not bypassed. Historical stale-WS compilation/coverage failures in the [rebase review](idf_553_rebase_review.md) are resolved by this final snapshot, not hidden or retroactively called passes.
|
||||
|
||||
The [fix-bearing Wi-Fi bundle](wifi_security_update_plan.md) is integrated in the candidate, with unchanged PMF/WPA3; radio-hardware vulnerability closure and full target/resource/recovery gates remain pending. Nine notice catalog entries were semantically rebased, the other 66 unchanged (75 total); supplied notice evidence is **36 fixtures PASS**, two actual deterministic bundles each **77 files / 4,433,930 bytes**. Archive pins and source equality do not prove complete immutable root/ancillary/Python dependency closure, legal clearance or Phase 9 acceptance. This documentation update records supplied parent evidence; it did not rerun these builds, suites, bundles or hardware tests.
|
||||
|
||||
## 1. Historical review inputs, not assumed SDK paths
|
||||
|
||||
Paths below are relative to the repository except the explicitly absolute shared SDK:
|
||||
|
||||
| Role | Observed path / identity |
|
||||
| --- | --- |
|
||||
| Old SDK | `/home/mscholz/.platformio/packages/framework-espidf`, package **3.50500.0** / IDF 5.5.0 |
|
||||
| Candidate archive | `.pio/idf-candidate-5.5.3/archives/framework-espidf-3.50503.0.tar.gz` |
|
||||
| Candidate installed SDK | `.pio/idf-candidate-5.5.3/core/packages/framework-espidf`, package **3.50503.0** / IDF 5.5.3 |
|
||||
| Isolated application | `.pio/idf-candidate-5.5.3/app` |
|
||||
| Existing candidate build | `.pio/idf-candidate-5.5.3/app/.pio/build/esp32-s3-devkitc-1-n16r8` |
|
||||
| Candidate compiler in compile database | `.pio/idf-candidate-5.5.3/core/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32s3-elf-gcc` |
|
||||
|
||||
There is no literal `.pio/idf-candidate` directory. The shared SDK is still **old**, not an installed production 5.5.3 SDK. There was no root `.pio/build/esp32-s3-devkitc-1-n16r8` build to validate. Tests defaulting to the shared SDK must explicitly select the candidate.
|
||||
|
||||
Recomputed **size and SHA-256 of all four complete archives** against `tools/idf_candidate/artifacts.json`: all matched (platform 6.13.0, framework 3.50503.0, Xtensa and RISC-V 14.2.0+20251107). Framework archive SHA-256: `8353f6fd5030dd7e662500891428fad15d46efd7e4b718cab2fe6bfb9e7f13fc`. Current `platformio.ini` selects that version pairing. Registry version pins in the root configuration are not themselves enforcement of these archive hashes; the isolated application uses local archive URLs.
|
||||
|
||||
Loaded the protected-entry registry from **Git HEAD**, verified every old original against its HEAD hash, then compared it with the working registry and actual candidate sources. Every working original hash and exact edit application passed. Also compared candidate protected IDF originals and `esp_httpd_priv.h` directly with members of the hash-verified framework archive: byte-identical. This separates old baseline, downloaded package, installed candidate and generated compilation inputs.
|
||||
|
||||
## 2. Historical blockers — resolved in final follow-up
|
||||
|
||||
### B1 — Historical candidate firmware does not contain the new WS correction
|
||||
|
||||
The staged `app/tools/security_overrides.py` lacks `Entry("httpd_ws", ...)`; the working generator includes it. The staged test runner also differs from the working web-performance runner. The candidate compile database compiles:
|
||||
|
||||
```
|
||||
.../core/packages/framework-espidf/components/esp_http_server/src/httpd_ws.c
|
||||
```
|
||||
|
||||
not `security_overrides/httpd_ws/httpd_ws.c`. The map includes `libesp_http_server.a(httpd_ws.c.o)`. The working SDK override test with this actual build fails:
|
||||
|
||||
```
|
||||
AssertionError: ('httpd_ws', [])
|
||||
```
|
||||
|
||||
All eight previously protected generated C files and the wolfSSH forced header independently match the current generator's bytes. Their compile paths are present; representative wolfSSH and application commands retain the joined forced-header and crypto-policy includes. This is a **specific stale ninth-C-source build**, not evidence that all replacements are missing.
|
||||
|
||||
An ELF/bin/map exists, but this reviewer did not build it. Snapshot identities:
|
||||
|
||||
- `firmware.elf`: 15,558,488 bytes; SHA-256 `39d12988d80c44787f1d9587d7ba685c990bfac940ba10180b86e60313b60fad`.
|
||||
- `firmware.bin`: 1,749,888 bytes; SHA-256 `0477958591e0e21269b21fdd62ba855d507a6dd460107647f4f38739f9264e96`.
|
||||
- `esp32_serial_swiss_army_knife.map`: SHA-256 `346abf8ce619742edd38274eef6267d6d8e0750fea6b1e2c8ffdcc34e5bc0085`.
|
||||
|
||||
**Required:** stage the final reviewed inputs in an ownership-safe isolated workspace, rebuild, and verify all **nine C overrides plus one forced header**, exact owners, original-source absence and generated bytes against that build. Do not attribute the current WS correction to the existing binary.
|
||||
|
||||
### B2 — Historical vendor-code tests and missing signed-error behavioral matrix
|
||||
|
||||
`tests/web_serial_performance/run.py` reads installed `httpd_ws.c`, not generated `httpd_ws.c`. Its enum-aware receive double is a necessary adaptation, but the suite fails compiling `httpd_ws_get_frame_type` against candidate vendor source:
|
||||
|
||||
```
|
||||
error: comparison of integer expressions of different signedness
|
||||
[-Werror=sign-compare]
|
||||
```
|
||||
|
||||
The new five-edit override is included in generic generator/CMake fixtures. However, `tests/sdk_security_overrides/run.py` has no dedicated WS receive behavior or mutation suite. The web-performance receive double always supplies a successful first byte and the control-frame receiver is a double; it cannot establish the newly changed real frame-receive error paths.
|
||||
|
||||
**Required:** consume verified generated WS code without suppressing the warning or editing vendor files. Exercise actual `httpd_ws_get_frame_type`, `httpd_ws_recv_frame` and, where needed, the real `httpd_recv_with_opt` against bounded IO doubles: negative fail/timeout, EOF, short lengths and success at all five fixed-header reads; split/pending bytes; two- and eight-byte lengths; four-byte mask; automatic control-frame failure/close and no subsequent payload/send on failed framing. Require each removed signed cast to be detected by a negative regression/mutation. Passing generic generation alone is insufficient.
|
||||
|
||||
### B3 — Historical documentation gap: final snapshot required
|
||||
|
||||
At the initial review, `docs/idf_candidate_integration.md` described preparation only, production untouched and smoke not run, while the worktree already had production configuration/guard changes and an isolated application ELF. The generated modification notice pointed to that preparation document as rebase evidence without per-entry dispositions. The integration record now links the semantic dispositions and supplied final build/test evidence. This review's initial artifact inspection is not retroactively a fresh build pass.
|
||||
|
||||
Broader wolf ordering/parser/crypto/authentication, browser admission and full phase validation were **not rerun by this reviewer**. Existing historical 5.5.0 results and a stale candidate build cannot stand in for final-snapshot integration validation. Hardware/radio/resource/recovery and distribution/source obligations remain separate established gates, not reasons to repeat completed broad advisory research.
|
||||
|
||||
## 3. Per-entry semantic disposition
|
||||
|
||||
All old entries preserve exactly their HEAD edit sequences; changed IDF file hashes reflect the vendor differences below. Exact successful replacement is necessary but was not the only check: old/new source differences and relevant ownership/error paths were inspected, and existing behavioral fixtures were executed against candidate-generated sources.
|
||||
|
||||
| Protected entry | Disposition and reason |
|
||||
| --- | --- |
|
||||
| `dhcpserver` | **Retain unchanged original and six edits.** Vendor source is byte-identical across these SDKs; bounded option header/body, PAD/END handling and minimum lengths are not supplied by this upgrade. Extracted parser passes 196,623 guard-page/equivalence cases. |
|
||||
| `mbedtls_ssl_tls` | **Rebase original hash; retain EMS error return.** Vendor changes include handshake-state helpers, dynamic-buffer reset handling, PRF label-length plumbing and exporter support. The `calc_verify` error branch still logs without returning; the local return remains necessary before PRF use. SHA256/SHA384 failures, success, PRF failure, non-EMS and resumption fixtures pass. Dynamic TLS buffers remain rejected. This is not a review of every new exporter/TLS1.3 path. |
|
||||
| `mbedtls_x509_create` | **Rebase original hash; retain OID allocation check.** Vendor now rejects a prepopulated output list instead of clearing it; it still dereferences an unchecked newly allocated `oid.p`. Local OOM return remains needed. Subject/issuer OOM, cleanup and retry fixtures pass. Project certificate generation sets subject and issuer separately once, consistent with the changed empty-list precondition; do not generalize this to repeated setter calls elsewhere. |
|
||||
| `https_server` | **Rebase original hash; retain all four edits.** Vendor delta adds hardware ECDSA curve/block configuration, not post-handshake allocation-failure cleanup, copied-key wiping or failed-start secure-context destruction. Local TLS delete, wipe-before-free and restored/null-cleared ownership still apply. Existing allocation/handshake/start/stop matrix passes. Failed stop continues to retain live ownership. |
|
||||
| `httpd_parse` | **Rebase original hash; retain all six edits.** Vendor delta fixes cookie length reporting on truncation, not scratch ownership. Null initial pointer arithmetic, failed `realloc` ownership and unwiped retired scratch still require local fixes. Allocate/copy/wipe/free, preserve-old-on-failure, nullable parser pointer and final wipe remain; null/grow/shrink/failure/pending-unread fixtures pass. |
|
||||
| `esp_tls_mbedtls` | **Rebase original hash; retain guards and server-only policy.** Vendor adds dynamic-buffer strategy, hardware ECDSA configuration, RSA-ALT cleanup and error/certificate reporting changes. None supplies the project server TLS1.2 ECDHE-ECDSA AES-GCM allowlist or no-renegotiation policy. Injection remains in server configuration; clients retain their defaults. Server/client isolation, feature and dynamic-buffer guard fixtures pass. |
|
||||
| `wolfssh_internal` | **Retain original hash and all 53 edits.** Managed source matches HEAD. Ordering delta, restricted no-EXT_INFO profile, password bounds/wiping, parser/name/service/signature/window corrections are unchanged; this IDF migration does not supersede them. The initial semantic review did not rerun full wolf behavior; the final supplied 24/24 run includes the established wolf suites. |
|
||||
| `wolfssh_ssh` | **Retain original hash and ordering edits.** Generated candidate bytes match working output; exact generated compilation path present. |
|
||||
| `wolfssh_internal_header` | **Retain original hash and ABI/order overlay.** Generated header bytes match; candidate library and representative application commands retain the forced include and overlay include path. PUBLIC direct/transitive consumer fixture passes. |
|
||||
| `httpd_ws` (new) | **New candidate-specific correction, not a repin.** IDF 5.5.3 changes fixed header reads from nonblocking `<= 0` checks to blocking `< sizeof(...)`. `int` negative receive results convert to unsigned `size_t`, making the checks false. The five `(int)sizeof(...)` changes correctly restore signed rejection for sizes 1, 2, 8, 4 and 1; they retain vendor blocking reads and do not alter payload framing policy. Behavioral regression and current-build evidence were initially blocked by B1/B2; the final follow-up resolves both with 982 cases / 10 mutation checks and actual generated-source registration. |
|
||||
|
||||
No protected correction was demonstrated to be **superseded**. Native pre-handshake support is not a reason to delete the application's admission adapter: it is disabled in the candidate configuration and the application deliberately registers the two upgrade routes as ordinary GET handlers.
|
||||
|
||||
## 4. Heap extent and HTTPD private boundary
|
||||
|
||||
### Heap: guarded 5.5.3 extent contract is supported
|
||||
|
||||
`components/heap/heap_caps.c`, `multi_heap.c`, `multi_heap_platform.h` and `tlsf/tlsf.c` are byte-identical old-to-candidate. The audited chain remains:
|
||||
|
||||
1. `heap_caps_get_allocated_size` removes any owner offset, locates the containing heap and subtracts owner metadata from the reported size.
|
||||
2. Unpoisoned `multi_heap_get_allocated_size` aliases the implementation returning `tlsf_block_size`.
|
||||
3. TLSF obtains the allocated payload block's rounded size, not a containing-block extent for an arbitrary interior pointer.
|
||||
|
||||
Candidate resolved config enables `CONFIG_HEAP_POISONING_DISABLED`, disables task tracking and leaves dynamic mbedTLS buffers off. `ssh_memory` still rejects poisoning and all other IDF versions. Actual candidate-source contract checks and host allocation/free/realloc wipe tests pass, including rounded capacity, retained shrink, growth and allocation failure. No new allocator layout/header mechanism was introduced. Target wipe latency and old-plus-new allocation peaks remain unmeasured.
|
||||
|
||||
### HTTPD: relevant layouts retained; receive API changed
|
||||
|
||||
The private-header diff adds shared control-message declarations, `HTTPD_RECV_OPT_*`, the enum receive parameter and a direct-LRU-close declaration. It does **not** change the adapter-used `httpd_data`, `httpd_req_aux` or `sock_db` field layouts. The application compiles against candidate headers rather than embedding an old SDK struct definition.
|
||||
|
||||
Relevant behavior review:
|
||||
|
||||
- Session completion still performs request creation/handling, request deletion/purge, then updates the LRU completion marker. New sessions initialize the counter from the server; the adapter already tracks session identity. All 18 idle lifecycle groups pass against candidate completion/purge sources.
|
||||
- `httpd_main` still processes control work before connection data. New direct LRU close is conditional; application `lru_purge_enable` remains false.
|
||||
- Header/scratch parser delta is the cookie reporting fix, not a layout/termination change. Right-aligned pending bytes and application wipe preservation remain applicable.
|
||||
- Optional URI registration still publishes before `strdup` failure cleanup; the application's allocate-both-before-publication correction remains necessary.
|
||||
- Vendor URI dispatch adds optional pre-handshake callback support. Candidate config disables it; application ordinary-GET upgrade handlers continue to authorize/admit before manually sending 101.
|
||||
- SDK WS sending remains separate header/payload sends through session `send_fn`; the bounded combined binary adapter still uses that TLS override and replaces it with reject-only behavior after incomplete output. No raw socket bypass was introduced.
|
||||
- `httpd_recv_with_opt` changes from Boolean to enum and loops for blocking fixed-header reads; `httpd_recv` uses `NONE`. Async completion now wakes the owner with a control message. Application owner/async exclusion rules remain relevant. The receive double was correctly changed to the enum, and the initial WS receive-error/test-input blockers B1/B2 are now resolved as recorded in the final follow-up.
|
||||
|
||||
This supports the narrow guard update; it does not certify arbitrary configurations, async use or every HTTPD API.
|
||||
|
||||
## 5. Whole radio bundle identity and actual link inputs
|
||||
|
||||
Compared **every regular archive member** under these component trees with the corresponding installed candidate file; all were byte-identical:
|
||||
|
||||
| Component | Files compared |
|
||||
| --- | ---: |
|
||||
| `esp_wifi` | 163 |
|
||||
| `esp_phy` | 114 |
|
||||
| `esp_coex` | 41 |
|
||||
| `wpa_supplicant` | 301 |
|
||||
|
||||
This includes packaged source, headers, CMake inputs and all target/optional archives, not only the seven currently discussed S3 Wi-Fi blobs. It establishes coherent delivery from the one locked framework archive, **not** independent upstream PHY/coexistence Git submodule provenance, vendor signature validation or opaque binary correctness.
|
||||
|
||||
S3 Git blob identities recomputed from installed bytes:
|
||||
|
||||
| Archive | Git blob SHA-1 |
|
||||
| --- | --- |
|
||||
| Wi-Fi `libcore.a` | `9f7b14a8bf6eec64973da8adc65d35b5ba9bee49` |
|
||||
| Wi-Fi `libespnow.a` | `132b4f67e339ca2081d2add91c14eefa39476ff9` |
|
||||
| Wi-Fi `libmesh.a` | `2e9dc1c8c7afbf033337b4175032e9b1161e3262` |
|
||||
| Wi-Fi `libnet80211.a` | `2800d447ec385d33869373696ba8191292647694` |
|
||||
| Wi-Fi `libpp.a` | `8944bcad7371621045f376cf74c62fde6f368cbb` |
|
||||
| Wi-Fi `libsmartconfig.a` | `4dc759e25617aa00b9e12887fd092a1d5780a170` |
|
||||
| Wi-Fi `libwapi.a` | `65655b6feab0572a6e8a1200946d53a21f3f4722` |
|
||||
| PHY `libphy.a` | `75d3c8756c8e0310066fa9a1e5c24df50e6c04e3` |
|
||||
| PHY `libbtbb.a` | `e94e3c2b476d8abb2757bcb53519ebcfba92b213` |
|
||||
| PHY `libbttestmode.a` | `06389c391ea55278df6205b6195b6d8182c7b8be` |
|
||||
| PHY `librfate.a` | `05dc4b69e02dac8e6c75ab8f9dc49c1d33d66c32` |
|
||||
| PHY `librftest.a` | `45c6dc240287332c8c8790e84a80475dbfe3a842` |
|
||||
| Coexistence `libcoexist.a` | `21fb35634fbdd421519bc985072fdc0ab54dd1df` |
|
||||
|
||||
The seven Wi-Fi identities agree with the already recorded 5.5.3 **release-point** column in `docs/wifi_security_update_plan.md`; they are not a mixed fix-point/release-point bundle. Candidate `esp_wifi_driver.h` declares `wpa_ap_get_wpa_ie(size_t *len)` and `esp_wpa_main.c` defines `wpa_ap_get_wpa_ie(size_t *ie_len)` and assigns that callback. The compile database uses candidate supplicant sources.
|
||||
|
||||
Actual candidate map `LOAD` entries select **all seven Wi-Fi blobs plus `libphy.a` and `libbtbb.a` from the candidate SDK path**, and the candidate-built component wrappers/supplicant archive. No old shared-SDK radio path was observed in those inputs. `libcoexist.a` is delivered and verified but is **not a LOAD input** in this configuration: `esp_coex/CMakeLists.txt` adds it only for software/external coexistence, not merely `CONFIG_ESP_COEX_ENABLED`. Optional RF/test PHY archives likewise are packaged identities, not claimed linked inputs. A map `LOAD` line does not prove all members survive link garbage collection.
|
||||
|
||||
The whole-package approach avoids a 5.5.0 C/header/blob transplant. This provides stronger delivery/path evidence than the earlier seven-Wi-Fi-only check, but does not demonstrate runtime PMF/WPA3 behavior, radio stability, target reserves or deployment vulnerability closure.
|
||||
|
||||
## 6. Historical validation executed and handoff
|
||||
|
||||
Commands run from the repository root:
|
||||
|
||||
```sh
|
||||
CCACHE_DISABLE=1 python3 -B tests/ssh_memory/run.py --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf
|
||||
IDF_PATH=/home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/framework-espidf CCACHE_DISABLE=1 python3 -B tests/web_httpd_idle/run.py
|
||||
IDF_PATH=/home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/framework-espidf CCACHE_DISABLE=1 python3 -B tests/web_serial_performance/run.py
|
||||
CCACHE_DISABLE=1 python3 -B tests/sdk_security_overrides/run.py --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --build-dir .pio/idf-candidate-5.5.3/app/.pio/build/esp32-s3-devkitc-1-n16r8
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
- **PASS:** SSH allocator behavior, six invalid poisoning profiles, explicit disabled profile, seven rejected IDF versions, real candidate extent-source checks and compile with candidate version header.
|
||||
- **PASS:** all 18 idle lifecycle groups and candidate completion/owner-order guards.
|
||||
- **FAIL:** web serial performance compile against original WS source, B2.
|
||||
- **PARTIAL PASS / overall FAIL:** SDK generator, HTTPS/scratch/TLS/DHCP/EMS/X509 behavior and CMake replacement/forced-header/extension fixtures pass; actual build registration rejects missing generated WS input, B1. Do not report this command as passing.
|
||||
- **PASS:** read-only HEAD/current original-hash and edit comparisons; all four archive identities; protected candidate/archive comparisons; all four radio component trees; candidate compile/map path inspection.
|
||||
|
||||
The initial next actions were generated-WS regression coverage, a fresh staged snapshot/build and final-input integration validation. Those actions are now complete in the supplied final follow-up; the historical failures above remain failures of the old snapshot, not current blockers or retroactive passes. No production defect was found in the retained old overrides or reviewed heap/private-layout assumptions. Normal root build completion, immutable full dependency closure, target/radio/resource/recovery and release gates remain open. Preserve PMF/WPA3, broker isolation, native USB independence and UART0 recovery.
|
||||
@@ -0,0 +1,100 @@
|
||||
# Official PlatformIO IDF 5.5.3 candidate integration
|
||||
|
||||
Initial package preparation: **2026-09-16**, Linux x86_64; integration update: **2026-09-18**. Preparation-only statements below describe the earlier stage. Root `platformio.ini` and reviewed guards/overrides are now migrated; shared-SDK installation is not claimed complete and no device/deployment acceptance is claimed.
|
||||
|
||||
## Final integration evidence — 2026-09-18
|
||||
|
||||
Supplied final parent command, run from the repository root:
|
||||
|
||||
```sh
|
||||
CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py --build-dir .pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8 --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --platformio-core-dir .pio/idf-candidate-5.5.3/core --interop --web-performance
|
||||
```
|
||||
|
||||
**PASS 24/24.** This selects the actual fresh isolated build, SDK and toolchain core; it is not a default-root-build execution. The separate fresh `.pio/idf-candidate-5.5.3/app-validated` build **PASS** reports **95,552 B linked RAM / 1,749,493 B flash**, versus historical **94,340 / 1,768,901 B** (**+1,212 B RAM / −19,408 B flash**). The default parent `pio run` timed out after **200 seconds during installation, before compilation**; no normal root build PASS is claimed.
|
||||
|
||||
Pre/post source equality: **3,237 files**, SHA-256 **`3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031`**. Actual generated-input registration covers **nine C sources plus one forced header**. WS receive tests exercise generated code: **982 cases / 10 mutation checks**, including the five signed `sizeof` corrections. The stale web-cookie fixture asserting IDF 5.5.0 was corrected, not bypassed. Historical stale-WS compilation/coverage failures in the [rebase review](idf_553_rebase_review.md) are resolved by this final snapshot, not hidden or retroactively called passes.
|
||||
|
||||
The [fix-bearing Wi-Fi bundle](wifi_security_update_plan.md) is integrated in the candidate, with unchanged PMF/WPA3; radio-hardware vulnerability closure and full target/resource/recovery gates remain pending. Nine notice catalog entries were semantically rebased, the other 66 unchanged (75 total); supplied notice evidence is **36 fixtures PASS**, two actual deterministic bundles each **77 files / 4,433,930 bytes**. Archive pins and source equality do not prove complete immutable root/ancillary/Python dependency closure, legal clearance or Phase 9 acceptance. This documentation update records supplied parent evidence; it did not rerun these builds, suites, bundles or hardware tests.
|
||||
|
||||
|
||||
|
||||
## Concrete result
|
||||
|
||||
**Select official `platformio/espressif32@6.13.0` + `platformio/framework-espidf@3.50503.0` (IDF 5.5.3) + `platformio/toolchain-xtensa-esp-elf@14.2.0+20251107`.** The official adapter also selects `platformio/toolchain-riscv32-esp@14.2.0+20251107` for ESP32-S3 ULP; include it even when evaluating the Xtensa application.
|
||||
|
||||
This is an explicitly released supported pairing, not a speculative package override. Exact downloaded archive hashes below make the selected inputs content-pinned; tags/version labels alone are not treated as immutable.
|
||||
|
||||
Official channels checked:
|
||||
|
||||
- [Platform registry](https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32): latest stable **7.1.3**, published September 11, 2026. [Latest release API](https://api.github.com/repos/platformio/platform-espressif32/releases/latest) agrees (`prerelease=false`). Its [manifest](https://raw.githubusercontent.com/platformio/platform-espressif32/v7.1.3/platform.json) selects `~4.60100.0`, **IDF 6.1**, not IDF 5.5.
|
||||
- [Framework registry](https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf): newest published 5.5 package is **3.50503.0**, published February 18, 2026. Returned 5.5 versions are 3.50503.0, 3.50502.0 and 3.50500.0. No 5.5.4/5.5.5 package appears in that response. Thus 5.5.3 is the **latest available official PlatformIO 5.5 candidate**, not the latest upstream Espressif 5.5 maintenance release. Registry framework metadata labels its tier `community` but its owner is `platformio`; the platform itself is tier `official`.
|
||||
- [6.13.0 release](https://github.com/platformio/platform-espressif32/releases/tag/v6.13.0), [release API](https://api.github.com/repos/platformio/platform-espressif32/releases/tags/v6.13.0): explicitly adds IDF **5.5.3** and updates IDF toolchains to **14.2.0+20251107**. Stable release, February 26, 2026.
|
||||
- [6.13.0 manifest](https://raw.githubusercontent.com/platformio/platform-espressif32/v6.13.0/platform.json): framework `~3.50503.0`, Xtensa `14.2.0+20251107`. [Adapter](https://raw.githubusercontent.com/platformio/platform-espressif32/v6.13.0/platform.py) removes the legacy chip-specific Xtensa toolchains for standalone IDF, enables unified Xtensa for S3, and selects the same-date PlatformIO RISC-V package for S3 ULP.
|
||||
- [Xtensa registry](https://api.registry.platformio.org/v3/packages/platformio/tool/toolchain-xtensa-esp-elf) and [RISC-V registry](https://api.registry.platformio.org/v3/packages/platformio/tool/toolchain-riscv32-esp) both publish the selected Linux x86_64 artifacts.
|
||||
|
||||
If the requirement is specifically upstream **5.5.5**, rather than the newest officially delivered 5.5 maintenance release, that requirement remains **blocked on official packaging/support**. Do not substitute 7.1.3 plus an arbitrary 5.5 override or a raw GitHub source archive.
|
||||
|
||||
## Actual downloaded identities
|
||||
|
||||
All four complete archives were downloaded into `.pio/idf-candidate-5.5.3/archives/` and their **local bytes** passed both registry size and SHA-256 checks. These are measured download checks, not just registry advertisements. No archive was unpacked into the shared PlatformIO SDK. Total compressed size: **992,746,039 bytes**.
|
||||
|
||||
| Artifact | Bytes | SHA-256 |
|
||||
| --- | ---: | --- |
|
||||
| `espressif32-6.13.0.tar.gz` | 1,009,115 | `5d1032b43828773ba87cf2e509432202c0bfe64f7304b58c9d669f13b116c6e0` |
|
||||
| `framework-espidf-3.50503.0.tar.gz` | 76,402,966 | `8353f6fd5030dd7e662500891428fad15d46efd7e4b718cab2fe6bfb9e7f13fc` |
|
||||
| `toolchain-xtensa-esp-elf-linux_x86_64-14.2.0+20251107.tar.gz` | 322,439,270 | `a5de49ce3299b0d9253ab6a423648bc23113db96b34a7cc8e57702cae1bb190e` |
|
||||
| `toolchain-riscv32-esp-linux_x86_64-14.2.0+20251107.tar.gz` | 592,894,688 | `1af8e233931500b8712079808e4974413d95d3601d03275dff79665c436e9d33` |
|
||||
|
||||
Machine-readable registry URLs, artifact URLs, versions, system selectors, sizes and hashes: [`artifacts.json`](../tools/idf_candidate/artifacts.json). No automatic repinning occurs.
|
||||
|
||||
The verifier reads members directly from the hash-verified archives without extracting or executing vendor files. Actual checks passed:
|
||||
|
||||
- Platform manifest version, framework range and exact Xtensa requirement.
|
||||
- SDK package version and SDK `tools/tools.json` recommendation `esp-14.2.0_20251107`.
|
||||
- Fixed `wpa_ap_get_wpa_ie(size_t *len)` callback declaration.
|
||||
- All seven packaged ESP32-S3 Wi-Fi libraries match the **5.5.3 release-point Git blob identities**, not the fix-point-only bundle, recorded in [the existing Wi-Fi plan](wifi_security_update_plan.md#bundle-identity-baseline-fix-point-release-point-are-different). The plan already establishes release commit `2c211b236707889e8400c4dc5644dd5c4ee071e0` and Wi-Fi submodule `e0befaa593277b4e80726079fbd521b4681754c2`; this task does not repeat fix research.
|
||||
|
||||
Whole-archive SHA-256 pins include the delivered PHY/coexistence/source/header contents, preventing changes to those bytes going unnoticed by this verifier. The subsequent [semantic rebase review](idf_553_rebase_review.md#5-whole-radio-bundle-identity-and-actual-link-inputs) compared every regular packaged file in `esp_wifi` (163), `esp_phy` (114), `esp_coex` (41), and `wpa_supplicant` (301) against the installed candidate: all matched. This is complete comparison of those delivered component trees, not merely seven Wi-Fi archives. This is **not** an independent recursive source-to-package audit, vendor signature verification, proof of opaque implementation correctness, or execution of the compiler binaries.
|
||||
|
||||
## Reproduce preparation and verification
|
||||
|
||||
From the repository root, with Python 3.9+ on Linux x86_64:
|
||||
|
||||
```sh
|
||||
python3 -B tools/idf_candidate/test_prepare.py
|
||||
python3 -B tools/idf_candidate/prepare.py
|
||||
```
|
||||
|
||||
The second command is offline, verifies all four already-downloaded archives, and makes no installation. Missing or altered inputs fail. Four offline helper tests cover corrupted hash/size, URL restrictions, contract failure, and absent/ambiguous archive members.
|
||||
|
||||
On a fresh checkout, download and create the isolated project:
|
||||
|
||||
```sh
|
||||
python3 -B tools/idf_candidate/prepare.py --fetch --prepare
|
||||
```
|
||||
|
||||
Network is restricted by the tool to HTTPS `dl.registry.platformio.org` and `dl.registry.nm1.platformio.org`, including redirects. Approximately 993 MB download space is needed plus substantial unpacked/build space for the later test. `--sdk-only --fetch` obtains/verifies only the platform and SDK. Existing mismatched archives fail rather than being overwritten. A killed download may leave a `.partial` file; inspect/remove that candidate-only partial before retrying. `--prepare` deliberately refuses an existing smoke directory rather than overwriting it. **Preparation already succeeded here**; use offline verification, not a second `--prepare`.
|
||||
|
||||
Generated smoke project: `.pio/idf-candidate-5.5.3/smoke/`. Its configuration uses the verified local official platform archive and exact local framework/toolchain archives, with `core_dir` under `.pio/idf-candidate-5.5.3/core/`. Its sources are a separate empty `app_main`; it neither inherits production config nor imports application overrides. Generic official ESP32-S3 board is intentional: this tests package integration, **not** the production N16R8 board or feature configuration.
|
||||
|
||||
### Command to test the isolated candidate
|
||||
|
||||
After successful verification, from the repository root:
|
||||
|
||||
```sh
|
||||
env -u PLATFORMIO_PACKAGES_DIR -u PLATFORMIO_PLATFORMS_DIR -u PLATFORMIO_BUILD_DIR -u PLATFORMIO_CACHE_DIR -u IDF_PATH -u IDF_TOOLS_PATH -u IDF_PYTHON_ENV_PATH PLATFORMIO_CORE_DIR=/home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core CCACHE_DISABLE=1 pio run --project-dir .pio/idf-candidate-5.5.3/smoke
|
||||
```
|
||||
|
||||
For another checkout location, replace the absolute `PLATFORMIO_CORE_DIR` accordingly. Run in a normal clean PlatformIO shell, not an activated unrelated IDF environment. The initial preparation task did **not run this smoke invocation**; the later fresh application build PASS is recorded above and does not retroactively claim execution of this exact smoke command. It may download platform ancillary packages and IDF Python dependencies (registry/mirror, PyPI/files.pythonhosted.org and Espressif download endpoints as requested by the adapter); grant those hosts separately as needed. Those ancillary/Python dependencies are not yet a complete frozen build closure. The prepared four-input lock is not advertised as a fully reproducible toolchain environment/SBOM. No upload, monitor or erase command is part of this evaluation.
|
||||
|
||||
## Rebase disposition and remaining integration gates
|
||||
|
||||
The official package-availability/toolchain mismatch question and bounded application build/host compatibility checks are resolved for the validated candidate. The [per-entry semantic review](idf_553_rebase_review.md#3-per-entry-semantic-disposition) retains every old protected correction (rebasing changed IDF original hashes), retains wolfSSH C/ABI overlays, and adds the ninth C override for five signed WS receive-size comparisons. Nothing was removed as superseded or bypassed by a permissive version guard. Reviewed heap extent/private HTTPD guards now target 5.5.3; generated-input ownership/include order and behavioral suites pass on the final build.
|
||||
|
||||
Remaining gates:
|
||||
|
||||
1. A successful normal root build: the default attempt stopped during installation, before compilation. Exact root version pins are not enforcement of the downloaded archive hashes.
|
||||
2. Complete immutable ancillary/Python/tool/managed-component dependency closure; four verified primary archives and source equality are not a full reproducible environment or SBOM.
|
||||
3. Target/radio/resource/recovery validation in the [Wi-Fi plan](wifi_security_update_plan.md), including the exact trigger and unchanged PMF/WPA3. Candidate integration is not hardware vulnerability closure.
|
||||
4. Recipient notices, corresponding source, radio-blob legal basis and release-specific runtime/bootloader attribution. Nine notice entries have been semantically rebased and 66 retained unchanged, but assembly is not delivery or legal clearance.
|
||||
5. Explicit whole-Phase-9 target acceptance. No upload, erase, credential migration, PMF weakening or generated-asset regeneration is part of this documentation update.
|
||||
@@ -0,0 +1,267 @@
|
||||
# Focused installed ESP-IDF security applicability review
|
||||
|
||||
## Finite applicability completion — 2026-09-16
|
||||
|
||||
**Current disposition supersedes the historical recommendations below.** This slice covers exactly the six previously named mbedTLS findings and the disabled/unused-feature entries on the first page of the official IDF advisory index. It is **not an exhaustive CVE inventory, security certification, upgrade approval, or Phase 9 sign-off**. Only this document was edited; no source/configuration/generated-file edits, build, test compilation, or device commands were performed.
|
||||
|
||||
### Effective source and configuration evidence
|
||||
|
||||
Rechecked installed **ESP-IDF 5.5.0 / mbedTLS 3.6.3**, not just upstream release ranges. Evidence is the existing `.pio/build/esp32-s3-devkitc-1-n16r8/` configuration and compilation database, installed source under `/home/mscholz/.platformio/packages/framework-espidf/`, and the checked-in override renderer. This establishes configured inputs, **not executable freshness or the firmware running on a device**.
|
||||
|
||||
- The current registry has **eight C replacements plus one wolfSSH internal-header overlay**. The seven-source count in the September 15 addendum is historical. Read-only `python3 -B` inspection called `verify_version()` and `render_entry()` only for the **six IDF-owned entries**, checked original hashes, and compared rendered bytes against existing generated copies: all six matched. No generator or build was run; wolfSSH bytes were outside this slice.
|
||||
- Filtered compilation entries select generated `dhcpserver.c`, `ssl_tls.c` and `x509_create.c`, but original `ecdh.c`, `pk_ecc.c`, `pkparse.c`, `ecp_curves.c`, `asn1write.c`, `x509_crt.c`, `ssl_tls12_server.c` and `httpd_ws.c`. None of the six newly classified findings has a project source backport. The mbedTLS entries use `MBEDTLS_CONFIG_FILE="mbedtls/esp_config.h"`; inspected command flags add no alternative user config. `esp_config.h` includes the generated `sdkconfig.h` and maps its ECP/NIST options to the library macros.
|
||||
- Generated header: `CONFIG_MBEDTLS_ECP_C=1`, `CONFIG_MBEDTLS_ECP_NIST_OPTIM=1`, `CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=1`, TLS 1.2 and client/server support enabled, TLS 1.3 absent. P-521 is enabled (maximum ECP output 66 bytes). Hardware MPI support is enabled, **not** a replacement for all software ECC reduction. Both mbedTLS client/server ticket options are enabled, but **`CONFIG_ESP_TLS_SERVER_SESSION_TICKETS` is absent**. Do not describe tickets as globally compiled out.
|
||||
- Rechecked generated EMS error branch (`mbedtls_ssl_tls/ssl_tls.c:7083–7086`) returns before derivation, and generated X.509 OID allocation (`mbedtls_x509_create/x509_create.c:313–317`) checks NULL before copying. These fixes are separate from ECDH, PK parsing, basicConstraints and ASN.1 value-state issues.
|
||||
|
||||
Additional installed-source SHA-256 evidence (paths below relative to `components/mbedtls/mbedtls/library/`; earlier original hashes remain unchanged):
|
||||
|
||||
| Source | SHA-256 |
|
||||
| --- | --- |
|
||||
| `ecdh.c` | `85439414902f8421db8921c69a0be771ed69a7c69f0722127298f41a1d330ba8` |
|
||||
| `pk_ecc.c` | `19ba6ca1251a610a53e3654a7727ba881c358a29d46f4586ab3c7e07c846b5bf` |
|
||||
| `pkparse.c` | `36c0590c516a1b4ad99bfefc213da1a19a2e27638ee3f267ea3365cc738ecf17` |
|
||||
| `x509_crt.c` | `799f6993d323d9158bf832967f404e9c63b0d76c0a72023be7a80c42630020fd` |
|
||||
| `ecp_curves.c` | `29329ed2ecad6e68f615b1b5a3e7c592a0b625c4d1a1090280e305fa9afb51d3` |
|
||||
| `ssl_tls12_server.c` | `ddf8d37e862d4176d27b928592b1971d2f397f50c4b1fac2443d099c3e9cab99` |
|
||||
|
||||
### Disposition matrix
|
||||
|
||||
**Addressed** means the specified source correction exists, not target acceptance. **Not-current-path** means this finding's prerequisite is absent in the inspected configuration/callers, not that the library is patched. **Excluded** is a threat-model boundary, not technical immunity. **Unresolved** marks evidence/coverage still absent.
|
||||
|
||||
| Finding | Disposition | Current evidence and boundary |
|
||||
| --- | --- | --- |
|
||||
| CVE-2026-45160 DHCP; CVE-2026-50581 TLS 1.2 EMS; CVE-2026-34874 X.509 OID OOM | **Addressed, scoped source corrections** | Existing pinned generated inputs match the renderer. Earlier host/build evidence remains attributed below; not rerun. EMS correction is TLS 1.2 only. |
|
||||
| CVE-2026-35336 ECDH small output buffer | **Not-current-path; primitive unpatched** | `ecdh.c:649–659` compares `mpi_size(z)` but writes curve-width bytes. HTTPS TLS premaster storage includes `MBEDTLS_ECP_MAX_BYTES`; no application direct caller supplies a small buffer. See caller qualification below. |
|
||||
| CVE-2026-50583 zero-length ECC PK parse | **Not-current-path / configuration not affected** | Advisory explicitly excludes built-in ECC in 3.x. `MBEDTLS_ECP_C` is enabled; `pk.h:179–182` requires it to be **absent** for `MBEDTLS_PK_USE_PSA_EC_DATA`. Active ECP-backed parser rejects `ilen < 1` before inspecting the point. |
|
||||
| CVE-2026-49300 basicConstraints | **Not-current-path; parser unpatched** | Both lax parsing behaviors exist. No untrusted certificate/trust-root enrollment or HTTPS client-certificate authentication was found; local identity validation requires CA=false. No current cross-validator CA promotion path established. |
|
||||
| CVE-2026-54435 ECC optimized reduction side channel | **Excluded attacker model; affected code active** | NIST optimization and deterministic ECDSA are enabled; P-256 identities use mbedTLS. Privileged-local precise execution traces / physical side channels are outside this network-focused review. No constant-time, hardware immunity, or key-extraction-resistance claim. |
|
||||
| CVE-2026-50586 NewSessionTicket disclosure | **Not-current-path; server tickets disabled, primitive unpatched** | HTTPS defaults `session_tickets=false`, application does not override it; ESP-TLS callback registration is additionally compiled out. mbedTLS ticket machinery itself remains enabled. |
|
||||
| CVE-2025-48965 stale ASN.1 value length | **Not-current-path; primitive unpatched** | `asn1write.c:412–415` still clears pointer without length. Certificate creation uses fresh subject/issuer lists, each a single fixed-format nonempty CN. Exact upstream one-line fix is now resolved below. |
|
||||
| CVE-2026-45541 WS subprotocol | **Not-current-path; SDK unpatched** | Existing manual-admission/NULL-subprotocol disposition below remains; not reclassified as globally fixed by this slice. |
|
||||
| Target fault behavior, local side-channel feasibility, running firmware, advisories beyond this finite set/index page | **Unresolved / not evaluated** | No runtime measurements, fault injection, exhaustive inventory or target acceptance supplied by this review. Revisit applicability whenever callers, features or trust enrollment change. |
|
||||
|
||||
### Evidence for the six named findings
|
||||
|
||||
**CVE-2026-35336:** fetched the official [ECDH advisory](https://raw.githubusercontent.com/Mbed-TLS/mbedtls-docs/main/security-advisories/mbedtls-security-advisory-2026-07-possible-buffer-overflow-ecdh-calc-secret.md) and [source fix `1d71bcc31cb8080b14b0dcbc6bc859c11d622c0f`](https://github.com/Mbed-TLS/mbedtls/commit/1d71bcc31cb8080b14b0dcbc6bc859c11d622c0f.patch). First fixed 3.6.x release: **3.6.7**. Installed vulnerable comparison matches the removed hunk. `ssl_tls12_server.c:3785–3789` passes `handshake->premaster`; `ssl_misc.h:990` sizes it as `MBEDTLS_PREMASTER_SIZE`, whose union in `ssl.h:691–724` includes `_pms_ecdh[MBEDTLS_ECP_MAX_BYTES]`. Although the TLS call's `blen` argument is `MBEDTLS_MPI_MAX_SIZE`, the **actual object**, not merely that argument, is sufficient for every enabled ECDH curve. The advisory explicitly says TLS ECDH is unaffected. Targeted `src/` search found no direct `mbedtls_ecdh_calc_secret()` caller.
|
||||
|
||||
Also checked the installed Wi-Fi adapter rather than assuming HTTPS is the only SDK consumer: `wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls-ec.c:803` uses the DPP maximum 66; DPP is not enabled in the generated configuration. Its `crypto_ecdh_set_peerkey()` at line 1290 uses `pbits / 8`; the inspected OWE caller in `src/rsn_supp/wpa.c` restricts both offered/received group to **19 (P-256)**, so this does not supply the advisory's non-byte-aligned P-521/floor-size trigger. OWE support **is** compiled in; this is not a blanket audit of its parsing or every SDK ECDH wrapper. Future arbitrary-curve callers must allocate and pass at least `(pbits + 7) / 8`, or use `MBEDTLS_ECP_MAX_BYTES`. A pinned `ecdh.c` backport is defense in depth, not a demonstrated current HTTPS overflow correction.
|
||||
|
||||
**CVE-2026-50583:** fetched the official [zero-length ECC public-key advisory](https://raw.githubusercontent.com/Mbed-TLS/mbedtls-docs/main/security-advisories/mbedtls-security-advisory-2026-07-zero-length-ecc-public-key-oob-read.md). It affects driver-only PSA ECC in 3.5.0–3.6.6, not built-in ECC. Original `pk_ecc.c:204–220` has the unchecked `*pub` only under `MBEDTLS_PK_USE_PSA_EC_DATA`; its active `#else` calls `mbedtls_ecp_point_read_binary()`, whose `ecp.c:775–777` checks `ilen < 1`. Application PK parsing **does occur**, in `src/web_security.c:409–414` and `src/ssh_security.c:156–181`, as well as ESP-TLS identity loading. Neither trusted storage nor a post-parse key check is being used as the exclusion: the decisive evidence is the built-in ECP path. Advisory identifies `0e2d7037db4048dbf1c194508c07384a818261d5` as the 3.6.x fix; no backport is installed or needed to close this configuration-specific finding. Reassess before moving to driver-only ECC.
|
||||
|
||||
**CVE-2026-49300:** fetched the official [basicConstraints advisory](https://raw.githubusercontent.com/Mbed-TLS/mbedtls-docs/main/security-advisories/mbedtls-security-advisory-2026-07-x509-ca-bit-forgery-invalid-basicconstraints.md). Installed `x509_crt.c:494–555` lacks the inner-SEQUENCE length equality check and accepts INTEGER in place of BOOLEAN. The extension caller at line 966 supplies `end_ext_octet`, so comparison against the exact inner sequence is still necessary. First fixed 3.6.x release: **3.6.7**.
|
||||
|
||||
Current `src/web_security.c` generates its own P-256 self-signed identity, `basicConstraints(false, -1)`, and parses persisted identity at lines 409–414. Validation checks matching keys, expected subject/issuer, self-signature, and specifically rejects `ca_istrue != 0` at lines 442–444. `src/web_server.c:673–691` retains default `cacert_pem=NULL`; generated ESP-TLS selects `MBEDTLS_SSL_VERIFY_NONE` for server peer authentication (`CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL` absent). Application authentication is not certificate-chain enrollment. Targeted application searches found no outbound ESP-TLS/HTTP client or untrusted certificate/CSR import; Wi-Fi configuration uses personal PSK/SAE profiles, not EAP credential/CA provisioning. These are current-use observations, **not global removal of X.509/client/enterprise support** (those libraries/features are available). A corrupt local blob being rejected is not evidence that the library parser is fixed.
|
||||
|
||||
Backport provenance correction: fetched [sequence fix `07f45b87681c1a0680c260089d3e6349b25fdd08`](https://github.com/Mbed-TLS/mbedtls/commit/07f45b87681c1a0680c260089d3e6349b25fdd08.patch). The advisory's listed “Basic fix #2,” [`4fb9c9e439fd1e7e44697d23d50f00b4642fbe08`](https://github.com/Mbed-TLS/mbedtls/commit/4fb9c9e439fd1e7e44697d23d50f00b4642fbe08.patch), changes **test encodings only**. A bounded official commit-history query for `library/x509_crt.c` at tag `mbedtls-3.6.7` located the actual [INTEGER-rejection source change `f67b8e5bded0c531b29480b8c5b7285366b1b153`](https://github.com/Mbed-TLS/mbedtls/commit/f67b8e5bded0c531b29480b8c5b7285366b1b153.patch), also fetched and compared. Do not backport the advisory's two listed hashes and claim both source defects fixed. No parser backport or runtime vector validation was performed here.
|
||||
|
||||
**CVE-2026-54435:** fetched the official [optimized modp side-channel advisory](https://raw.githubusercontent.com/Mbed-TLS/mbedtls-docs/main/security-advisories/mbedtls-security-advisory-2026-07-ecc-optimized-modp-side-channel.md). First fixed 3.6.x release: **3.6.7**. Installed `ecp_curves.c` retains specialized P-256 reduction and data-dependent carry/sign handling (`NEXT`, `LAST`, `mbedtls_ecp_fix_negative`, around lines 4900–5036); no override replaces it. Application key generation/loading and certificate signing genuinely use P-256; this is **not an unused-crypto finding**. Upstream requires privileged-local sufficiently precise traces (typically an untrusted OS attacking an enclave); physical side channels are also possible, not measured here. Disabling NIST optimization is an upstream workaround for NIST curves only, carries substantial performance cost, and does not cover Koblitz/Montgomery curves. No configuration change is recommended blindly on this resource-constrained target. Reopen for a local/physical attacker threat model or deliberate maintained-library update; the upstream multi-commit fix range was identified in the advisory but not patch-audited in this excluded slice.
|
||||
|
||||
**CVE-2026-50586:** fetched the official [TLS 1.2 ticket disclosure advisory](https://raw.githubusercontent.com/Mbed-TLS/mbedtls-docs/main/security-advisories/mbedtls-security-advisory-2026-07-tls12-newsessionticket-information-disclosure.md). First fixed 3.6.x release: **3.6.7**. Original `ssl_tls12_server.c:4242–4246` still declares uninitialized `tlen`/`lifetime`; failed ticket generation may disclose four stack bytes. `esp_https_server.h:183–192` defaults client CA to NULL and `session_tickets=false`; the application preserves both. Generated HTTPS `create_secure_context()` initializes tickets only under `config->session_tickets`; generated ESP-TLS registers callbacks only under `CONFIG_ESP_TLS_SERVER_SESSION_TICKETS` and non-NULL `cfg->ticket_ctx`. Neither prerequisite holds. This is disabled **server-side TLS session tickets**, not application WebSocket/admin tickets, cookies, or compiled-out mbedTLS ticket support. Reassess and backport/init both outputs before enabling the feature; advisory lists `99ccd257e2d6c5fc53bc970e3e533a90c363f8e1` and `548ed19f707565db5fb4c2487edd7ae1bea50199` (not patch-verified here).
|
||||
|
||||
**CVE-2025-48965:** re-fetched the official [ASN.1 advisory](https://raw.githubusercontent.com/Mbed-TLS/mbedtls-docs/main/security-advisories/mbedtls-security-advisory-2025-06-6.md). First fixed release: **3.6.4**. Rechecked original stale-length branch and fixed-format CN generation (`src/web_security.c:92–111,128–240`); subject and issuer are separate fresh lists, not repeated OIDs within one list. No application direct named-data/string-name API consumer besides those fixed name setters was identified. This is not the separately addressed OID-allocation failure. A bounded official history query for `library/asn1write.c` at `mbedtls-3.6.4` resolved and fetched [fix `2df7ab7c0c3d5bb8a31481073c494521d10d4eba`](https://github.com/Mbed-TLS/mbedtls/commit/2df7ab7c0c3d5bb8a31481073c494521d10d4eba.patch): exactly `cur->val.len = 0;` after clearing `cur->val.p`, plus repeated-OID tests. Its context matches installed lines 412–415. **The older unresolved-fix-commit statement below is now resolved; the source bug remains unpatched.** If backported defensively, select actual nested target `mbedcrypto`, retain source pin/notices and allocation-failure preservation, and test nonempty→empty→same-length replacement and serialization.
|
||||
|
||||
### Bounded IDF advisory-index snapshot: disabled/unused features
|
||||
|
||||
Fetched [official IDF index, page 1](https://github.com/espressif/esp-idf/security/advisories) on **2026-09-16**: ten entries spanning February 3–September 3, 2026, newest listed A2DP/L2CAP. Fetched each of the eight remaining feature advisories below; the other two are DHCP and WS already covered above. **Pages 2–3, broader historical/vendor advisory coverage and future publications were not reviewed.** Later affected-release labels do not establish that installed 5.5.0 is fixed; dispositions here rely on feature/target/caller absence, not release-table inference.
|
||||
|
||||
| Official advisory / CVE | Required feature | Snapshot disposition and local evidence |
|
||||
| --- | --- | --- |
|
||||
| [GHSA-xcpr-5mqp-9qvv](https://github.com/espressif/esp-idf/security/advisories/GHSA-xcpr-5mqp-9qvv), CVE-2026-81508 | Bluedroid Classic A2DP sink; paired BR/EDR source | **Not-current-path**: BT disabled; no BT host compile inputs. |
|
||||
| [GHSA-v335-fxwc-rc44](https://github.com/espressif/esp-idf/security/advisories/GHSA-v335-fxwc-rc44), CVE-2026-81507 | Bluedroid Classic L2CAP configuration | **Not-current-path**: same disabled BT evidence; no claim that authentication would mitigate this unauthenticated path. |
|
||||
| [GHSA-3pp8-42fh-3j3c](https://github.com/espressif/esp-idf/security/advisories/GHSA-3pp8-42fh-3j3c), CVE-2026-46532 | Bluedroid AVRCP target, paired BR/EDR peer | **Not-current-path**: same disabled BT evidence. |
|
||||
| [GHSA-9r76-858f-v6jh](https://github.com/espressif/esp-idf/security/advisories/GHSA-9r76-858f-v6jh), CVE-2026-45542 | NimBLE protocomm Security2 provisioning | **Not-current-path**: BT/NimBLE absent; no application protocomm/Wi-Fi provisioning API use found. SoftAP itself is not this provisioning service. |
|
||||
| [GHSA-h7r3-gmg9-xjmg](https://github.com/espressif/esp-idf/security/advisories/GHSA-h7r3-gmg9-xjmg), CVE-2026-25507 | BLE provisioning teardown with `keep_ble_on=true` | **Not-current-path**: same disabled BLE/no provisioning service evidence, not a verified vendor UAF fix. |
|
||||
| [GHSA-v6r2-f6p2-88cj](https://github.com/espressif/esp-idf/security/advisories/GHSA-v6r2-f6p2-88cj), CVE-2026-55687 | Hardware JPEG decoder DQT parsing | **Not-current-path**: ESP32-S3 configuration, no `esp_driver_jpeg` compilation input or application JPEG calls. No malformed-image testing. |
|
||||
| [GHSA-w82j-7q63-7pqm](https://github.com/espressif/esp-idf/security/advisories/GHSA-w82j-7q63-7pqm), CVE-2026-45329; [GHSA-mmgp-73p4-92xp](https://github.com/espressif/esp-idf/security/advisories/GHSA-mmgp-73p4-92xp), CVE-2026-45328 | ESP-TEE REE/TEE secure-service wrappers | **Not-current-path**: target is Xtensa ESP32-S3, not the advisory's supported RISC-V TEE SoCs; no `esp_tee` compilation inputs or enabled TEE setting. This exclusion is distinct from choosing not to implement secure boot/encrypted NVS. |
|
||||
|
||||
Local snapshot evidence: generated `sdkconfig.h` selects `CONFIG_IDF_TARGET="esp32s3"` / `CONFIG_IDF_TARGET_ARCH_XTENSA=1`; no enabled `CONFIG_BT_ENABLED`, `CONFIG_BT_BLUEDROID_ENABLED`, `CONFIG_BT_NIMBLE_ENABLED` or ESP-TEE option. Filtered existing compilation database counts for `/bt/host/`, `/esp_tee/`, `/esp_driver_jpeg/`: **0 each**. `src/` searches for provisioning/protocomm/JPEG APIs found none. This is a feature screen, not inspection or certification of the disabled vendor implementations. Re-enabling any feature invalidates its disposition.
|
||||
|
||||
### Parent handoff and remaining limits
|
||||
|
||||
**No new current-path actionable source correction established among these six named findings.** Keep the existing DHCP/EMS/OID-allocation fixes. Optional defense-in-depth candidates are the exact ASN.1 one-line fix, ECDH curve-width bounds check and both basicConstraints source fixes; they must not be represented as already implemented. The basicConstraints test-only commit mismatch is actionable **backport guidance**, not evidence of a present authentication bypass. Preserve server ticket disablement and re-review before introducing certificate imports, driver-only ECC, arbitrary-curve ECDH callers or local isolation requirements.
|
||||
|
||||
Completed evidence work: official six-advisory fetches, focused upstream patch comparisons/history queries, original-source/config/caller inspection, read-only equality checks of six IDF generated overrides, and filtered configured-input inspection. Document-scoped `git diff --check` and the corrected document-presence/link-count check passed (the initial checker incorrectly expected twice the number of advisory URLs). Concurrent changes appeared in other agents' parser/override/release-notice files during the review; none were edited or reverted here, and source/configuration evidence above is the inspected snapshot, not certification of subsequent changes. No exploits, new host regression tests, build or device validation were run. The finite applicability questions above are dispositioned; full advisory coverage, runtime fault/timing behavior and whole-phase target acceptance remain unresolved. **Do not summarize this as “all CVEs safe.”**
|
||||
|
||||
## Implementation addendum — 2026-09-15
|
||||
|
||||
**Implemented, with host/build evidence; not target validation or a completed advisory review.** Source checked against `tools/security_overrides.py`, `cmake/security_overrides.cmake` and the existing generated copies:
|
||||
|
||||
- **CVE-2026-45160:** pinned DHCP backport handles PAD/END before length access, validates remaining payload length and minimum type/IP lengths, and advances by the validated length. Remaining-length comparisons deliberately avoid forming out-of-object pointers.
|
||||
- **CVE-2026-50581:** TLS 1.2 EMS `calc_verify` failure now returns `ret` before derivation. This is the TLS 1.2 hunk only, not the upstream TLS 1.3 correction or a global mbedTLS update.
|
||||
- **CVE-2026-34874:** X.509 OID allocation failure now returns `MBEDTLS_ERR_X509_ALLOC_FAILED` before `memcpy()`.
|
||||
- The registry now has **seven** entries: the four historical entries below plus `dhcpserver` on `lwip`, `mbedtls_ssl_tls` on nested target `mbedtls`, and `mbedtls_x509_create` on nested target `mbedx509`. Nested-target selection validates the allowlist and owning SDK library directory, rejects imported/alias targets, and retains the exactly-one-source/property-preservation checks. All seven generated copies were read-only verified against rendered pinned inputs; each carries the project modification notice dated **2026-09-15**, retaining upstream notices.
|
||||
- **Still unpatched:** CVE-2026-45541 WS negotiation and CVE-2025-48965 ASN.1 named-data state. Their baseline applicability qualifications below remain; the three backports do not fix them.
|
||||
|
||||
Supplied parent evidence: `pio run` **PASS**, **94,340 B linked RAM / 1,767,217 B flash** (unchanged RAM, **−64,092 B flash** from the 9C 1,831,309 B baseline; includes the wolf crypto policy). Parent reports all four focused commands in [hardening validation](security_hardening.md#mitigation-hostbuild-evidence--2026-09-15) passed; independent reviewer reports the SDK-override and wolf-crypto-policy suites passed with no blocking implementation defects. These commands were not rerun for this documentation update. No target evidence, dependency upgrade, asset regeneration or device operation is claimed. Broader advisory review and whole-phase acceptance remain open.
|
||||
|
||||
## Historical pre-mitigation research baseline — 2026-09-15
|
||||
|
||||
**The remainder preserves the original investigation, hashes, applicability and proposed tests. Statements such as “not implemented,” “current,” four entries and original compilation inputs describe that earlier snapshot, not the implementation above.**
|
||||
|
||||
Review date: **2026-09-15**. Scope: the installed `/home/mscholz/.platformio/packages/framework-espidf`, current application source, and checked-in/generated security overrides. **Review and recommendations only; no fixes implemented.** No build, test compilation, upload, monitor, erase, or device operations were performed.
|
||||
|
||||
This is not a complete IDF/mbedTLS advisory inventory, certification, or statement that an old version is safe. Advisory release tables are not sufficient to identify downstream backports: the installed implementation and actual source registration were inspected.
|
||||
|
||||
## Decision summary
|
||||
|
||||
| Issue | Confirmed local finding | Application applicability | Recommended priority |
|
||||
| --- | --- | --- | --- |
|
||||
| CVE-2026-45160, DHCP option parsing | Original vulnerable parser compiled; no override | Exposed whenever the project SoftAP/DHCP server is active; adjacent associated client, no application login required | **First correction:** backport the release/v5.5 bounds fix |
|
||||
| CVE-2026-45541, WS subprotocol parsing | Vulnerable SDK function compiled; no override | Faulty tokenization loop **not reached by current application upgrades**, which pass `NULL`; both routes are ordinary HTTP handlers | Defense-in-depth backport; preserve current admission-before-101 design |
|
||||
| CVE-2026-50581, ignored EMS error | Missing early return in installed `ssl_compute_master()` | TLS 1.2/EMS path used by HTTPS; hash/allocation failure prerequisite. Cipher restriction and disabled renegotiation do not remove this path | Prompt fail-closed correction alongside DHCP/X.509 work |
|
||||
| CVE-2025-48965, ASN.1 named-data state | Zero-length replacement clears pointer but leaves stale length | Current certificate names are trusted, single nonempty CNs; malicious repeated-OID/empty-value trigger not provided by current name generation | Lower-priority library correction/regression coverage |
|
||||
| CVE-2026-34874, X.509 name allocation failure (3.6.6 fix) | Unchecked allocation immediately followed by `memcpy()` | **Used by local certificate creation**, even with the safe fixed CN; OOM can trigger it | Prompt correction; fixed input is not a mitigation |
|
||||
|
||||
**Important correction to issue classification:** CVE-2025-48965 is not the X.509 allocation-failure bug. Its official advisory describes a stale `val.len` after a zero-length replacement. CVE-2026-34874 is the separate unchecked allocation fixed in 3.6.6. Do not merge their applicability or mark either fixed by the project's HTTPD scratch allocator changes.
|
||||
|
||||
## Exact reviewed baseline and registration
|
||||
|
||||
Installed `components/esp_common/include/esp_idf_version.h` defines **5.5.0**. `components/mbedtls/mbedtls/include/mbedtls/build_info.h` defines **3.6.3**, `0x03060300`. The following SHA-256 values were calculated from installed bytes, not inferred from version labels. Paths are relative to the installed IDF root:
|
||||
|
||||
| Installed source | SHA-256 |
|
||||
| --- | --- |
|
||||
| `components/esp_common/include/esp_idf_version.h` | `30fdfbed6cc0055a89f3454f61b56d5e5643bbc46c0774aae63e41869cf2947a` |
|
||||
| `components/lwip/apps/dhcpserver/dhcpserver.c` | `953f46189bc64680ea5fa761e75511fadb3aebf698a0d9dff251d77166d78b80` |
|
||||
| `components/esp_http_server/src/httpd_ws.c` | `e514f603a696e3e775573bc8882a350691e31fcae96e435f9062248e7c91677c` |
|
||||
| `components/mbedtls/mbedtls/library/ssl_tls.c` | `b726c0c55bc5f32255f129d55f9f2fface85ce83de90a2d16c9017b93b738bff` |
|
||||
| `components/mbedtls/mbedtls/library/asn1write.c` | `6667aebb1e5f0500ef4a281fe2752fc7bc59610e7bfc01d2916d224e5bc5c3e3` |
|
||||
| `components/mbedtls/mbedtls/library/x509_create.c` | `fd399239aee30384786a19b47bfe5dd22b979d5d89bb38f29f0c82a3d81daaf7` |
|
||||
|
||||
### Checked-in edits are part of the effective source
|
||||
|
||||
Root `CMakeLists.txt:15–16` includes `cmake/security_overrides.cmake` after `project()`. That file invokes `tools/security_overrides.py`, loads its manifest, and replaces exactly one source on the resolved component target, preserving source properties and original quoted-include directory. Version/source/hash/edit ambiguity fails configuration.
|
||||
|
||||
The registry currently has exactly four entries:
|
||||
|
||||
| Entry | Component | Protected original SHA-256 |
|
||||
| --- | --- | --- |
|
||||
| `wolfssh_internal` | `wolfssl__wolfssh` | `81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9` |
|
||||
| `https_server` | `esp_https_server` | `6481942b62e51125e2a43441fa0900cbda74bd2ea05c82f0c29eb4933c31946e` |
|
||||
| `httpd_parse` | `esp_http_server` | `6bba77064aaa68a06f8d4c01432064a1b050c91ed22741c547785b0d8a6c07d8` |
|
||||
| `esp_tls_mbedtls` | `esp-tls` | `09210c5a601647ca5775d127a2951bab2f3e509192b53487bbea8a93d8731b78` |
|
||||
|
||||
Read-only Python inspection called `verify_version()` and `render_entry()` in memory (not `generate()`), verified all four original pins, and compared rendered bytes with the existing generated files: **all four matched exactly**. This did not rewrite installed or generated files.
|
||||
|
||||
The existing `.pio/build/esp32-s3-devkitc-1-n16r8/security_overrides/manifest.cmake` and narrowly filtered `compile_commands.json` register those four generated sources, but register **original** `dhcpserver.c`, `httpd_ws.c`, `ssl_tls.c`, `asn1write.c`, and `x509_create.c`. Thus HTTPS cleanup, HTTPD scratch wiping/null handling, TLS policy, and wolfSSH fixes do **not** constitute fixes for these five findings.
|
||||
|
||||
This confirms existing configured compilation inputs, not freshness of the executable, successful rebuilding, or the firmware installed on a device.
|
||||
|
||||
### mbedTLS registration constraint
|
||||
|
||||
Installed `components/mbedtls/CMakeLists.txt` imports upstream nested targets. The existing compile database's object paths confirm:
|
||||
|
||||
- `ssl_tls.c` → target `mbedtls` (`.../CMakeFiles/mbedtls.dir/ssl_tls.c.obj`).
|
||||
- `x509_create.c` → target `mbedx509`.
|
||||
- `asn1write.c` → target `mbedcrypto`.
|
||||
|
||||
The current replacement helper resolves an **IDF component's** `COMPONENT_LIB`; it does not resolve these nested library targets. Simply adding `Entry(..., "mbedtls", ...)` would select the component wrapper, not the actual owning target, and should fail its exactly-one-source check. A future patch must minimally add an explicit, validated target-selection mechanism for these three nested targets while retaining current component resolution for existing entries. Do not weaken the match check or add duplicate source compilation to the wrapper.
|
||||
|
||||
## CVE-2026-45160 — DHCP server `parse_options()`
|
||||
|
||||
Official [GHSA-g764-gwc3-75m5](https://github.com/espressif/esp-idf/security/advisories/GHSA-g764-gwc3-75m5) and fetched [release/v5.5 patch](https://github.com/espressif/esp-idf/commit/d51b1076092487e533eadf8b48c9c8579d3a6712.patch).
|
||||
|
||||
**Exact upstream fix commit:** `d51b1076092487e533eadf8b48c9c8579d3a6712`.
|
||||
|
||||
Confirmed installed `dhcpserver.c:902–952`:
|
||||
|
||||
- Loop only checks `optptr < end`.
|
||||
- Message type reads `optptr[2]` without validating a length byte or payload.
|
||||
- Requested IP compares four bytes without a declared/remaining-length check.
|
||||
- Advancement reads `optptr[1]` without ensuring it exists.
|
||||
- PAD is not treated as a one-byte option.
|
||||
|
||||
The GHSA lists later affected point releases and a planned 5.5.5 fix; that is **not evidence excluding 5.5.0**. The old implementation is directly present here.
|
||||
|
||||
### Reachability
|
||||
|
||||
`src/wifi_manager.c:create_default_wifi_netifs()` explicitly builds `ESP_NETIF_DEFAULT_WIFI_AP()` and installs default AP handlers. Installed `esp_netif_defaults.h:59–61` sets `ESP_NETIF_DHCP_SERVER | ESP_NETIF_FLAG_AUTOUP`. Existing generated configuration has `CONFIG_LWIP_DHCPS=1`. `start_radio_and_policy()` selects AP/APSTA when policy requires it; `set_runtime_ap_enabled()` also permits STA→APSTA activation.
|
||||
|
||||
This is therefore an applicable exposure when SoftAP is active, not a hypothetical unused SDK feature. A station-only moment is not a permanent mitigation if fallback or administrative policy can enable AP later. The official impact is adjacent-network denial of service, not demonstrated heap disclosure: the advisory says overread bytes are not echoed. WPA association controls reduce access but do not fix the parser. No live attack or actual device AP state was tested.
|
||||
|
||||
### Minimal recommended edits (not implemented)
|
||||
|
||||
Add a pinned `idf` entry for `components/lwip/apps/dhcpserver/dhcpserver.c`, component `lwip`, using the exact original hash above. Backport the official patch's PAD definition and parser changes:
|
||||
|
||||
1. Skip PAD; terminate at END before reading a length.
|
||||
2. Require a length byte and the entire declared payload to fit.
|
||||
3. Require message-type payload length ≥1 and requested-IP length ≥4.
|
||||
4. Advance using the already-validated `opt_len`; remove the obsolete end flag.
|
||||
5. Retain notices and existing post-loop state behavior.
|
||||
|
||||
For a strict-C bounds implementation, consider remaining-length comparisons (`end - optptr >= 2`, then `opt_len <= remaining - 2`) rather than constructing a pointer beyond the object in the upstream `optptr + 2 + opt_len > end` check. If chosen, document this small deviation and test equivalence; this review did not apply or test either form.
|
||||
|
||||
Future tests: empty options, lone option code, missing length, oversized payload, PAD sequences, END at boundary, short type/IP fields, valid DISCOVER/REQUEST/RELEASE and option combinations. Verify actual generated `lwip` compilation input, no duplicate original, and no changes to AP policy, broker, UART0, or USB recovery.
|
||||
|
||||
## CVE-2026-45541 — WS subprotocol negotiation
|
||||
|
||||
Official [GHSA-3j8v-xgrq-5vg8](https://github.com/espressif/esp-idf/security/advisories/GHSA-3j8v-xgrq-5vg8) and fetched [release/v5.5 patch](https://github.com/espressif/esp-idf/commit/00a2f7fbbbd8fe6d04729022e1d5c9a49435bfe8.patch).
|
||||
|
||||
**Exact upstream fix commit:** `00a2f7fbbbd8fe6d04729022e1d5c9a49435bfe8`.
|
||||
|
||||
Installed `httpd_ws.c:70–95` has `strtok_r()` followed by a `do` loop that dereferences its possibly NULL result. It also compares using `sizeof(subprotocol)`, the pointer size rather than protocol length. A nonempty delimiter-only header can yield no tokens.
|
||||
|
||||
### Current route registration avoids the defective loop
|
||||
|
||||
- Existing configuration has `CONFIG_HTTPD_WS_SUPPORT=1`; disabling WS at compile time is **not** the reason for non-reachability.
|
||||
- `src/web_server.c:518–539` defines serial and admin upgrade routes with `.is_websocket = false`; omitted `supported_subprotocol` members of these static initializers are NULL. `start_server()` actually registers the serial handler array and optional admin route.
|
||||
- `src/web_serial_transport.c:745` and `src/web_admin_transport.c:570` use `web_httpd_upgrade()` after admission.
|
||||
- `src/web_httpd_adapter.c:174` calls `httpd_ws_respond_server_handshake(request, NULL)`.
|
||||
- Installed `httpd_ws_get_response_subprotocol()` returns at its `supported_subprotocol == NULL` check **before** tokenization. The optional registration adapter also rejects `is_websocket` or `supported_subprotocol`.
|
||||
|
||||
Conclusion: vulnerable library bytes are present, but **this CVE's NULL-token dereference is not reachable through the current application upgrade paths**. The official generic warning about pre-application-authentication crashes applies to automatic SDK WS negotiation with a non-NULL supported protocol; it should not be copied onto these manual-admission routes without this qualification. TLS alone is not a general mitigation.
|
||||
|
||||
### Minimal recommended edits (not implemented)
|
||||
|
||||
Add a separately pinned `httpd_ws.c` entry for component `esp_http_server`; do not conflate it with the existing `httpd_parse.c` entry. Backport the upstream `while (s != NULL)` loop, exact-length match and explicit next-token step, retaining notices. Do not enable automatic upgrades or add a subprotocol to justify the patch.
|
||||
|
||||
Future tests: empty/delimiter-only headers, NULL supported protocol, unsupported protocol, exact match and prefix mismatch, plus current cookie/Origin/ticket admission-before-101 behavior. Mark it as a defense-in-depth fix, not closure of a demonstrated unauthenticated application crash.
|
||||
|
||||
## Bounded mbedTLS 3.6.3 review
|
||||
|
||||
### CVE-2026-50581 — EMS calculation failure ignored
|
||||
|
||||
Official [advisory](https://raw.githubusercontent.com/Mbed-TLS/mbedtls-docs/main/security-advisories/mbedtls-security-advisory-2026-07-extended-master-secret-calculation-failure-ignored.md), published 2026-07-07. Affects through 3.6.6; first fixed 3.6.x release is **3.6.7**, not 3.6.6.
|
||||
|
||||
Fetched [basic fix](https://github.com/Mbed-TLS/mbedtls/commit/f595df4569c1a1650ad9d077e2f2e819e9f1dddb.patch): **`f595df4569c1a1650ad9d077e2f2e819e9f1dddb`**. Advisory gives test/documentation range `338572c1d805a31b875a448536bcb50d72f9bc40..27065ceb643a4266888ba1f200e4f20845e801cf`; that range was not reviewed here.
|
||||
|
||||
Confirmed installed `ssl_tls.c:7041–7085`: `session_hash[48]`, initial `seed_len=64`, and `handshake->calc_verify()` error only logged; execution continues. The override edits `esp-tls/esp_tls_mbedtls.c`, **not** this file. The server's two ECDHE-ECDSA AES-GCM suites use SHA-256/SHA-384 and TLS 1.2; EMS remains relevant even without renegotiation. Installed `esp_config.h:1094–1097` enables EMS when `CONFIG_MBEDTLS_TLS_ENABLED`, which is 1 in the existing generated header.
|
||||
|
||||
A calculation/allocation failure can leave an uninitialized hash and potentially retain length 64, causing a 16-byte buffer overread. Per upstream, this does not directly disclose stack contents or the master secret; a correctly functioning peer rejects the incorrect Finished message. Higher-level master-secret uniqueness attacks require additional conditions, including both peers affected and induced failures. **No direct HTTPS login bypass, practical remote fault trigger, or device exploit was established.** Low historical memory minima justify fault testing, not an assertion that this failure occurred.
|
||||
|
||||
**Minimal recommendation:** pinned `ssl_tls.c` replacement on actual target `mbedtls`, inserting `return ret;` immediately after the existing `MBEDTLS_SSL_DEBUG_RET(1, "calc_verify", ret)` inside the error branch. Preserve EMS; do not disable it as a shortcut. The fetched commit also changes `ssl_tls13_server.c` for a separate resumption-secret error: do not claim the one-line TLS 1.2 backport implements that second fix or globally updates mbedTLS. Current HTTPS policy is TLS 1.2 only.
|
||||
|
||||
Future tests should inject `calc_verify` failure for both SHA paths, ensure PRF/key derivation and successful handshake progression do not occur, preserve success behavior, and exercise cleanup. Exact driver/backend failure inducibility on the target remains unresolved; no preprocessing/cryptographic or fault-injection test was run.
|
||||
|
||||
### CVE-2025-48965 — ASN.1 named-data inconsistency, not OOM
|
||||
|
||||
Official [advisory](https://raw.githubusercontent.com/Mbed-TLS/mbedtls-docs/main/security-advisories/mbedtls-security-advisory-2025-06-6.md) and [3.6.4 release notes](https://github.com/Mbed-TLS/mbedtls/releases/tag/mbedtls-3.6.4). Affects through 3.6.3; fixed in 3.6.4.
|
||||
|
||||
Installed `asn1write.c:374–435` checks allocation failures when creating a node/OID/value and preserves old data until replacement allocation succeeds. However, the `val_len == 0` branch frees `cur->val.p` and sets it NULL without resetting `cur->val.len`. Reusing the same OID or writing the inconsistent list can dereference NULL. Official examples include `DC=foo,DC=#0000` and `DC=foo,DC=#0000,DC=bar`.
|
||||
|
||||
`src/web_security.c:92–111,128–257` builds `CN=ESP32 SAK <six hex digits>` from the MAC, initializes a fresh writer, and sets subject and issuer separately. There is no repeated OID or empty value in either name. Thus the faulty primitive exists and is indirectly called, but **the official hostile-name trigger is not supplied by this certificate-generation path**. Do not say “not applicable because the project never creates certificates”; it does. Do not treat separate subject/issuer lists as repeated attributes in one list.
|
||||
|
||||
**Minimal recommendation:** ensure `cur->val.len = 0` when clearing the value, on actual target `mbedcrypto`, with a pin on the installed file. Retain the existing preserve-old-on-allocation-failure logic. The official advisory/release was fetched, but **an exact upstream CVE-2025-48965 fix commit was not resolved or patch-verified in this bounded review**; obtain and compare the specific 3.6.4 change before implementing/pinning a claimed official backport. Test nonempty→empty→same-length replacement, list serialization after empty replacement, repeated OIDs, and allocation failures independently.
|
||||
|
||||
### CVE-2026-34874 — `x509_string_to_names()` allocation failure
|
||||
|
||||
Official [advisory](https://raw.githubusercontent.com/Mbed-TLS/mbedtls-docs/main/security-advisories/mbedtls-security-advisory-2026-03-null-pointer-dereference-x509.md) and [3.6.6 release notes](https://github.com/Mbed-TLS/mbedtls/releases/tag/mbedtls-3.6.6). Affects 3.5.0–3.6.5; fixed in 3.6.6.
|
||||
|
||||
Fetched [basic fix](https://github.com/Mbed-TLS/mbedtls/commit/bfaf4a47fd33da860796feaba6235847acb71127.patch): **`bfaf4a47fd33da860796feaba6235847acb71127`**. The advisory identifies tests/documentation through `4704b6b4bd963f1331582374e881184addf8f523`; those additional changes were not reviewed here.
|
||||
|
||||
Installed `x509_create.c:306–310` allocates the known attribute's OID and calls `memcpy(oid.p, ...)` without checking NULL. The project's CN takes exactly this recognized-attribute branch through both name setters. Trusted names avoid the previous CVE, **not this OOM defect**. Application return-code checks cannot catch a fault that happens inside the setter before it returns.
|
||||
|
||||
**Minimal recommendation:** pinned `x509_create.c` replacement on actual target `mbedx509`; directly after `oid.p = mbedtls_calloc(1, oid.len)`, add the official three-line NULL guard returning `MBEDTLS_ERR_X509_ALLOC_FAILED`. Preserve existing application cleanup and fail-closed identity publication. No new buffer or policy change is required.
|
||||
|
||||
The upstream advisory discusses arbitrary code execution on microcontrollers where address zero is writable. **That impact was not demonstrated for this ESP32-S3 memory map**; confirmed here is the unchecked write path and potential failure during certificate generation, not practical code execution. Test OID allocation failure at each setter, clean writer destruction, no persistence/publication of incomplete material, preservation of the old identity on failed rotation, and successful retry.
|
||||
|
||||
## Actionable next correction and remaining evidence
|
||||
|
||||
1. **Implement the pinned DHCP parser backport first.** It is the clearest currently reachable unauthenticated network-input defect in this scope. Do not merely change advisory wording or a version label.
|
||||
2. In the same security correction effort, support explicit nested-target overrides and backport the EMS early return and X.509 allocation guard. Verify each protected source appears exactly once on its real owner target; retain all existing four corrections and their source properties.
|
||||
3. Backport WS defensively and add registration/NULL-subprotocol regression coverage without changing application admission. Resolve the exact ASN.1 3.6.4 patch before adding that lower-priority correction.
|
||||
4. Before acceptance, run focused parser/allocation/error-propagation tests against the generated compilation inputs, then a firmware build and target checks under separately authorized implementation/validation work. **None were performed for this review.** Ensure failures cannot compromise UART0 recovery, native USB independence, broker ownership, or bounded transport isolation.
|
||||
5. A full IDF upgrade is an alternative maintenance strategy, but must rebase/re-audit all existing source pins, private HTTPD contracts, and TLS behavior. Updating only to mbedTLS 3.6.6 would leave CVE-2026-50581 unresolved. This document does not certify any upgrade candidate against the full advisory set.
|
||||
|
||||
Unresolved boundaries: exact ASN.1 fix commit; target-specific OOM/hash failure reproducibility and impact; actual running firmware/configuration; complete mbedTLS/IDF advisory coverage and downstream release integration. Existing source registration and generated-byte equality are confirmed; patch application, tests, linking, and device behavior are not.
|
||||
|
||||
Review mechanics: project code-map/current-state read first; targeted source, configuration and compilation-input inspection; official advisories and four exact fix patches fetched. No large release-history index was used. Some GitHub API requests timed out/returned no results and direct ReadTheDocs access via Python returned HTTP 403; official raw advisory sources and individual release pages supplied the evidence above. One read-only inspection command hit its 10-second timeout; a linear-time replacement completed the inspection. No source modifications were made, and this review owns only this document.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Legacy credential removal and storage compatibility
|
||||
|
||||
Current storage/recovery contract. The user accepted cleanup on 2026-09-08, confirming the certificate fingerprint was unchanged and preexisting test users remained usable. [Overall acceptance and evidence limits](web_administration_acceptance.md) supersede the old per-slice handoff; no unreported provisioning, recovery, power-loss or all-key tests are implied.
|
||||
|
||||
## User database
|
||||
|
||||
- `user_database_init(load_result)` takes no legacy credential. Missing `user_db/database` storage is committed as an empty v1 database; no account/password is imported.
|
||||
- Create the first administrator on physical UART0 with `user add <username> admin`, optionally `--generate`. Entered passwords use hidden confirmation; generated passwords are shown once. There is no reserved bootstrap account or public bootstrap state.
|
||||
- `user recover --force` is UART0-only, calls `user_database_recover_empty()`, and destructively rebuilds only an unavailable database empty. It refuses a healthy initialized database, including an empty one. Follow with normal `user add`; unrelated configuration and TLS/SSH identities are untouched.
|
||||
- Valid existing v1 database bytes load without rewriting or changing accounts, roles, IDs, authentication generations, verifiers or authorized keys. Previously migrated role-`user` accounts are not automatically promoted. The private `v1_admin_marker` preserves the old wire byte and is derived from administrator count during mutations; it is not a new role, public bootstrap field or schema change.
|
||||
- No public bootstrap, legacy user migration or synchronization API remains. Final-admin protection, conditional mutations, copied principal currentness and target-only revocation remain the canonical account contracts.
|
||||
|
||||
## HTTPS identity storage
|
||||
|
||||
`web_security` owns only TLS material. At the unchanged `web_sec/material` key, a private byte-oriented reader validates the shipped **1,392-byte v1** layout and builds **1,340-byte TLS-only v2**. It retains the **exact private-key DER, certificate DER, SHA-256 fingerprint and material generation**: this migration is not certificate rotation.
|
||||
|
||||
The v2 candidate is validated and committed before live publication. Temporary credential-bearing v1 input is wiped. No public legacy credential reader/type, display, rotation or authentication path remains. Unknown/malformed material, read failures, invalid cryptographic identity and migration write/commit failures fail closed; they do not trigger fallback regeneration or overwrite of rejected material. Truly missing material may be generated and saved normally.
|
||||
|
||||
`web credentials show`, `web credentials rotate --force` and `user bootstrap` (including its generated form) are removed. `web certificate info` and `web certificate rotate --force` remain subject to existing frontend policy. `web reset --force` explicitly replaces TLS certificate/private key only; it does not reset passwords, import accounts or synchronize verifiers. TLS replacement/restart can close browser sessions and requires renewed certificate trust/login, without changing user credentials or revoking unrelated SSH sessions.
|
||||
|
||||
## Compatibility and physical-security limits
|
||||
|
||||
**Older v1-only firmware cannot read v2 HTTPS storage.** Do not assume a downgrade preserves usable HTTPS or restores removed credentials. Existing user database v1 compatibility is separate from this HTTPS downgrade incompatibility.
|
||||
|
||||
Replacing an NVS blob logically removes credential fields from the current record; it is **not secure flash wiping**. Append-oriented historical copies may retain plaintext legacy credentials, and current Wi-Fi secrets/TLS/SSH private keys remain unencrypted. PBKDF2 verifiers remain subject to offline guessing. No factory/partition erase is required or authorized by this cleanup.
|
||||
|
||||
## Regression references
|
||||
|
||||
Use `tests/web_security/run.py`, `tests/admin_console_boundary/accounts.py`, `tests/admin_ssh_policy/run.py` and [current legacy-removal procedures](user_administration_tests.md#current-legacy-removal-regression-procedure). Prior host tests used NVS fault doubles and a generated identity wrapped in the historical TLS layout, not an independently captured old-device fixture; they do not prove flash durability or power-loss behavior. Procedures are not claims of execution.
|
||||
@@ -0,0 +1,185 @@
|
||||
# Phase 9 bounded host validation
|
||||
|
||||
`tools/validate_phase9.py` is a thin, sequential orchestrator for existing test
|
||||
runners, not a new security test implementation or a Phase 9 acceptance gate.
|
||||
See [security hardening](security_hardening.md) for scope, historical evidence,
|
||||
and the deferred whole-phase target checklist.
|
||||
|
||||
## Current candidate execution — 2026-09-18
|
||||
|
||||
Supplied final parent command, run from the repository root:
|
||||
|
||||
```sh
|
||||
CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py --build-dir .pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8 --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --platformio-core-dir .pio/idf-candidate-5.5.3/core --interop --web-performance
|
||||
```
|
||||
|
||||
**PASS 24/24.** This selects the actual fresh isolated build, SDK and toolchain core; it is not a default-root-build execution. The separate fresh `.pio/idf-candidate-5.5.3/app-validated` build **PASS** reports **95,552 B linked RAM / 1,749,493 B flash**, versus historical **94,340 / 1,768,901 B** (**+1,212 B RAM / −19,408 B flash**). The default parent `pio run` timed out after **200 seconds during installation, before compilation**; no normal root build PASS is claimed.
|
||||
|
||||
Pre/post source equality: **3,237 files**, SHA-256 **`3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031`**. Actual generated-input registration covers **nine C sources plus one forced header**. WS receive tests exercise generated code: **982 cases / 10 mutation checks**, including the five signed `sizeof` corrections. The stale web-cookie fixture asserting IDF 5.5.0 was corrected, not bypassed. Historical stale-WS compilation/coverage failures in the [rebase review](idf_553_rebase_review.md) are resolved by this final snapshot, not hidden or retroactively called passes.
|
||||
|
||||
The [fix-bearing Wi-Fi bundle](wifi_security_update_plan.md) is integrated in the candidate, with unchanged PMF/WPA3; radio-hardware vulnerability closure and full target/resource/recovery gates remain pending. Nine notice catalog entries were semantically rebased, the other 66 unchanged (75 total); supplied notice evidence is **36 fixtures PASS**, two actual deterministic bundles each **77 files / 4,433,930 bytes**. Archive pins and source equality do not prove complete immutable root/ancillary/Python dependency closure, legal clearance or Phase 9 acceptance. This documentation update records supplied parent evidence; it did not rerun these builds, suites, bundles or hardware tests.
|
||||
|
||||
## Usage
|
||||
|
||||
From the repository root (the script itself also resolves the root independently
|
||||
of the caller's working directory):
|
||||
|
||||
```sh
|
||||
python3 -B tools/validate_phase9.py --dry-run
|
||||
python3 -B tools/validate_phase9.py
|
||||
python3 -B tools/validate_phase9.py --fail-fast
|
||||
python3 -B tools/validate_phase9.py --timeout 300
|
||||
```
|
||||
|
||||
Build and OpenSSH interoperability are **independent explicit opt-ins**:
|
||||
|
||||
```sh
|
||||
python3 -B tools/validate_phase9.py --build --build-timeout 600
|
||||
python3 -B tools/validate_phase9.py --interop --timeout 300
|
||||
python3 -B tools/validate_phase9.py --build --interop --dry-run
|
||||
```
|
||||
|
||||
These examples are usage, not execution claims. `--build` runs exactly `pio run`
|
||||
first; it does not upload, erase, monitor, or select another PlatformIO environment.
|
||||
PlatformIO itself may download dependencies or write its normal external caches.
|
||||
The caller/parent must choose whether to build and arrange any required permission
|
||||
separately. No installer, permission grant, network retry, or sandbox escalation
|
||||
is implemented. A failed/timed-out build skips every dependent host command to
|
||||
avoid testing stale artifacts as if that build succeeded.
|
||||
|
||||
`--interop` adds only `--interop` to `tests/wolfssh_order_contract/run.py` (its
|
||||
default single matrix). That runner uses OpenSSH and local AF_UNIX sockets, not
|
||||
an IP host or device. Socket permissions and installed OpenSSH tooling must
|
||||
already be available; denial is a failure, not a reason to grant permissions.
|
||||
Neither option is enabled by default. `--web-performance` independently adds the generated-WS performance runner (24 commands total). `--build-dir`, `--idf-path` and `--platformio-core-dir` select existing candidate artifacts/SDK/core; `--build` rejects a non-default build directory, which must be built separately. No target address, credentials, arbitrary
|
||||
command, or device-operation argument is accepted.
|
||||
|
||||
## Coverage and prerequisites
|
||||
|
||||
The fixed default plan contains **23 commands**; `--dry-run` prints their exact
|
||||
argv vectors and timeouts without launching anything or checking prerequisites.
|
||||
There is no discovery-time execution of runners (some run at import/top level).
|
||||
Paths and options were inspected in their actual `run.py` implementations.
|
||||
|
||||
| Scope | Existing runners / modes |
|
||||
|---|---|
|
||||
| 9A effective crash policy | `security_build_policy --sdkconfig-header .pio/build/esp32-s3-devkitc-1-n16r8/config/sdkconfig.h` |
|
||||
| 9B admission / credentials | `ssh_auth_policy`, `ssh_auth_transport`, `hidden_input` |
|
||||
| 9C allocator | `ssh_memory` |
|
||||
| Current seven-suite baseline | `sdk_security_overrides --build-dir .pio/build/esp32-s3-devkitc-1-n16r8`, `wolfssh_auth_contract`, `ssh_protocol_policy`, `wolf_crypto_policy`, `wolfssh_parser_contract`, `wolfssh_order_contract`, `release_notices` |
|
||||
| Focused SSH/admin and HTTPS lifecycle regressions | `ssh_management`, `admin_console_boundary`, `admin_ssh_policy`, `web_admin_transport`, `web_admin_tickets`, `web_httpd_idle` |
|
||||
| Web early admission, credentials and administrative lifecycle | `web_cookie_auth` default plus separate `--admin`, `--accounts`, `--ssh`, `--lifecycle` commands |
|
||||
|
||||
Every runner path is `tests/<name>/run.py`. Auth contract, protocol policy, and
|
||||
crypto policy receive explicit `--compile-commands
|
||||
.pio/build/esp32-s3-devkitc-1-n16r8/compile_commands.json` arguments. No
|
||||
`--host-only`, candidate injection, or silent fallback is used. Default operation
|
||||
requires an existing configured build, installed pinned SDK/managed sources,
|
||||
the production toolchain and host compiler/libraries needed by these runners
|
||||
(including OpenSSL development support for relevant tests). It **does not run
|
||||
PlatformIO** to satisfy those requirements. Missing explicit files/executables
|
||||
are `PREREQ` failures; deeper source/toolchain prerequisites are checked by the
|
||||
existing runners and surface as nonzero `FAIL` results. Existing artifacts are
|
||||
not proof that the current checkout has been freshly built.
|
||||
|
||||
The current cookie-auth runner has no `--admission` branch: the default exercises
|
||||
early admission/wiping, and each selected domain mode repeats that baseline.
|
||||
Unrelated display/network/UI suites are intentionally excluded; web performance is an explicit opt-in.
|
||||
`ssh_memory` retains its documented optional installed-IDF extent-source check
|
||||
as skipped when no `--idf-path` is supplied; explicit candidate selection enables it. Its normal allocator contracts still run.
|
||||
The ordering runner's candidate `--target-contracts`, separate `--pio-adapter`
|
||||
regression, and repeated interop matrices are not added implicitly. Run those
|
||||
separately when their particular evidence is needed. This is a focused full
|
||||
Phase 9 host regression set, not every test in the repository.
|
||||
|
||||
## Bounds, output, and result semantics
|
||||
|
||||
- Each host command has a 180-second wall-clock timeout by default; the optional
|
||||
build gets 600 seconds. Both timeout flags accept finite values in `(0, 3600]`.
|
||||
The fixed sequential plan bounds total child execution by the sum of its
|
||||
timeouts (default 4,140 seconds), plus launch/reporting/cleanup overhead.
|
||||
- POSIX process groups are required. Timeout or Ctrl-C kills the active process
|
||||
group and reaps the runner; successful/failed exit also retires any remaining
|
||||
group descendants. This covers normal compiler/test children, not deliberately
|
||||
detached processes. Group reaping has a five-second wait bound.
|
||||
- Commands use argv lists, no shell evaluation or interpolation, with stdin
|
||||
closed. The child environment is copied with `CCACHE_DISABLE=1`; explicit `--idf-path` and `--platformio-core-dir` also set resolved `IDF_PATH` and `PLATFORMIO_CORE_DIR` for children;
|
||||
arbitrary compiler, SDK, proxy and other caller environment settings are not
|
||||
overwritten. The parent's environment is not mutated.
|
||||
- stdout/stderr are inherited live. The orchestrator creates no persistent log,
|
||||
JSON capture, environment dump, or secret artifact. Existing runners retain
|
||||
their own temporary fixture/build behavior; forced termination can bypass
|
||||
their temporary-directory cleanup. Console output is **not a redaction
|
||||
guarantee**: use synthetic fixtures, review before sharing, and do not redirect
|
||||
output into routine secret-bearing persistent captures. Build outputs remain
|
||||
normal PlatformIO artifacts only when explicitly requested.
|
||||
- `PASS` means the entire runner exited zero, not that an earlier printed PASS
|
||||
line appeared. The final summary records each command's exit, timeout or
|
||||
missing prerequisite. Defaults collect failures and continue independent
|
||||
commands; `--fail-fast` reports remaining commands as `SKIP`. Build failure
|
||||
always stops dependent commands. No selected suite is silently dropped.
|
||||
- Exit 0 means all selected commands passed, or a dry-run plan was printed.
|
||||
Dry-run uses `PLAN`, never `PASS`, and is not validation. Failures, timeouts,
|
||||
prerequisites and dependent skips return 1; invalid CLI arguments return 2;
|
||||
Ctrl-C returns 130. Optional build/interop omissions are announced separately
|
||||
and do not turn a successful host run into failure.
|
||||
|
||||
## Orchestrator fixture tests
|
||||
|
||||
```sh
|
||||
python3 -B tests/phase9_validation/run.py
|
||||
```
|
||||
|
||||
Offline unittest groups check the exact plan/paths/strict arguments, independent
|
||||
opt-ins, timeout configuration and invalid values, dry-run nonexecution, default
|
||||
build/interop omission, failure collection, fail-fast and failed-build skips,
|
||||
missing file/executable handling, literal argv/environment preservation,
|
||||
timeout/descendant termination, and inherited streams/closed stdin. They execute
|
||||
only small Python fixtures, never PlatformIO, interop, devices, or network.
|
||||
|
||||
## Execution record — 2026-09-16
|
||||
|
||||
Initial orchestrator-agent execution, under the existing sandbox without elevation (historical first run):
|
||||
|
||||
- `python3 -B tests/phase9_validation/run.py`: **10 tests PASS**.
|
||||
- `python3 -B tools/validate_phase9.py --dry-run`: exit 0; **plan only**, no
|
||||
prerequisite or suite validation.
|
||||
- `python3 -B tools/validate_phase9.py`: **exit 1, 22 PASS / 1 FAIL**, no timeouts;
|
||||
about 57 seconds of summed command time. Build and interop explicitly skipped.
|
||||
- Direct rerun `CCACHE_DISABLE=1 python3 -B tests/admin_console_boundary/run.py`:
|
||||
reproduced exit 1 at line 40, `ValueError: substring not found` for
|
||||
`ssh.index("static void *ssh_malloc(")`. Earlier subtest PASS lines do not make
|
||||
the runner pass. The existing runner is outside this change's ownership and
|
||||
was not edited.
|
||||
|
||||
Passed commands: `security_build_policy`, `ssh_auth_policy`, `ssh_auth_transport`,
|
||||
`hidden_input`, `ssh_memory`, **all seven current baseline suites listed above**,
|
||||
`ssh_management`, `admin_ssh_policy`, `web_admin_transport`, `web_admin_tickets`,
|
||||
`web_httpd_idle`, and all five selected `web_cookie_auth` modes.
|
||||
Only `admin_console_boundary` failed.
|
||||
|
||||
No firmware build, interop, device, runtime reserve, hardware panic/recovery,
|
||||
release delivery, or whole-Phase-9 acceptance result is claimed for this task.
|
||||
|
||||
### Follow-up supplied evidence
|
||||
|
||||
The owning agent corrected the stale `admin_console_boundary` source anchor;
|
||||
its subsequent default orchestrator run reports **23/23 PASS**. This supersedes
|
||||
the initial 22/23 host result above, without turning its early PASS lines into
|
||||
a successful initial run. Final supplied parent execution now confirms:
|
||||
|
||||
- `CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py`: **PASS 23/23**.
|
||||
- `tests/phase9_validation/run.py`: **PASS 10 tests**.
|
||||
|
||||
No build or interop ran this turn; both remain separate opt-ins, not default
|
||||
execution. Firmware source is unchanged. These are actual parent results supplied
|
||||
for this record, not another execution by this documentation integration.
|
||||
|
||||
The supplied notice agent also reports **36 fixture tests PASS** and two actual
|
||||
75-input bundles, each **77 files / 4,433,930 bytes**, verified deterministic; see
|
||||
[packaging evidence](release_packaging.md). These are agent measurements, not a
|
||||
new build or bundle measurement by this documentation integration.
|
||||
|
||||
The [finite advisory search](dependency_advisory_coverage.md) is complete for its
|
||||
declared snapshot, but the [PMF-capable SoftAP blocker](wifi_security_update_plan.md)
|
||||
was **not fixed at that historical snapshot**. The current fix-bearing bundle is now integrated in the validated candidate; host success does not validate radio behavior, clear distribution obligations or complete Phase 9.
|
||||
@@ -0,0 +1,249 @@
|
||||
# Offline release notice packaging
|
||||
|
||||
This procedure produces a **bounded notice/provenance bundle**, not a firmware
|
||||
release, corresponding-source archive, complete SBOM, or legal clearance.
|
||||
The **radio-blob corresponding-source/System Library question remains open**.
|
||||
See [the license inventory](dependency_licenses.md) for the original review and
|
||||
[the retained scope/provenance statement](../third_party/release-notices/README.md)
|
||||
for license choices, mixed notices, wolfSSH packaging discrepancy, TLSF text
|
||||
assembly, icon limits, and remaining release gates.
|
||||
|
||||
## Run against the installed reviewed snapshot
|
||||
|
||||
Requirements: Python 3.9+ on Linux/POSIX with `O_NOFOLLOW`, directory descriptors
|
||||
and `dir_fd` operations; already installed matching managed components,
|
||||
ESP-IDF and Xtensa toolchain. Standard library only. The tool never fetches,
|
||||
installs, builds, runs PlatformIO, accesses a device, or regenerates assets.
|
||||
|
||||
Run from the repository root. Supply **an explicit, not-yet-existing output
|
||||
directory outside all three input roots**, with an existing parent. Example
|
||||
for the reviewed installed 5.5.3 candidate (choose a fresh final directory name):
|
||||
|
||||
```sh
|
||||
python3 tools/release_notices.py \
|
||||
--sdk-root /home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/framework-espidf \
|
||||
--toolchain-root /home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/toolchain-xtensa-esp-elf \
|
||||
--output /tmp/esp32-release-notices-review-01
|
||||
```
|
||||
|
||||
`--project-root` optionally selects a different checkout containing the exact
|
||||
reviewed inputs; by default it is the script's repository. The trusted catalog
|
||||
always comes from the script's repository, not from that alternate checkout.
|
||||
Paths to SDK/toolchain packages are explicit; no installation is guessed.
|
||||
There is no user catalog option, force/overwrite mode, automatic repinning,
|
||||
recursive collection, archive command, or automatic release upload.
|
||||
|
||||
Every input in `third_party/release-notices/inputs.json` is mandatory and pinned
|
||||
by **full-file SHA-256 and size**. Missing sources, altered bytes, bad excerpts,
|
||||
empty files, directories, FIFOs, symlinks (even to identical files), parent
|
||||
traversal, binary notice payloads and exhausted bounds fail closed. Symlinks in
|
||||
input-root or output-parent ancestry are rejected too: pass real non-symlinked
|
||||
paths, not a convenience symlink. All input preflight completes before creating
|
||||
output. The limits are 128 inputs, 4 MiB per input/catalog and 32 MiB cumulative
|
||||
source bytes. The current catalog has **75 inputs**, including the retained SVGs and pinned
|
||||
[icon provenance evidence](icon_provenance.md). Overview documents edited in this
|
||||
integration are not catalog inputs; `docs/icon_provenance.md` **is pinned**.
|
||||
Coordinate any change to that record with the catalog owner; never silently repin.
|
||||
|
||||
An existing output file, directory (even empty) or dangling symlink is never
|
||||
overwritten. Parent directories are not automatically created. Output uses
|
||||
private directory/file modes (0700/0600, subject to umask). On a write failure,
|
||||
the tool deliberately leaves any partial directory for inspection and never
|
||||
recursively deletes it. A successful exit plus a valid manifest and matching
|
||||
payload hashes is required; presence of a partial manifest is not success.
|
||||
Use another fresh path for a retry. Do not put output into a tree that an
|
||||
untrusted process can concurrently rename or modify; descriptor/no-follow
|
||||
checks prevent symlink traversal but are not isolation from the same OS user.
|
||||
|
||||
## Bundle layout and reproducibility
|
||||
|
||||
- `README.txt`: limitations and entry point for recipients.
|
||||
- `inputs/project/…`: project GPL text, resolved lock, managed licenses and
|
||||
manifests, wolfSSL/wolfSSH README evidence, original wolfSSH header excerpts,
|
||||
xterm MIT texts and recorded provenance, icon summary and official 7.4.47
|
||||
SVG/license/metadata evidence, TLSF full rendering
|
||||
and scope/provenance statement.
|
||||
- `inputs/sdk/…`: complete scoped SDK license documents, mixed Newlib/argtable3
|
||||
notices, radio license files, package/version identity and original source
|
||||
notice excerpts. SDK `LICENSE` supplies full Apache-2.0 text; it does **not**
|
||||
authenticate individual icons or cover all SDK files.
|
||||
- `inputs/toolchain/…`: actual toolchain Newlib text, GPLv3, GCC Runtime Library
|
||||
Exception 3.1 and package identity. No toolchain executable is copied.
|
||||
- `manifest.json`: schema/snapshot, catalog SHA-256, logical root/path,
|
||||
full-source size/hash, optional zero-based half-open byte range, purpose,
|
||||
output path/size/hash, and the generated introduction's hash. Excerpts have
|
||||
`.notice.txt` appended; null ranges retain whole original files unchanged.
|
||||
|
||||
Whole mixed documents are preserved, not converted into guessed SPDX labels.
|
||||
The longer toolchain Newlib notice does not replace the distinct SDK text.
|
||||
The wolfSSH package GPLv2-or-later text and GPLv3-or-later source headers remain
|
||||
separate and unmodified; upstream packaging clarification is still needed.
|
||||
|
||||
Generated metadata contains no collection timestamps or absolute host paths;
|
||||
original notices retain their own dates and text. For identical catalog and
|
||||
inputs, file names and file contents are deterministic across
|
||||
output locations, input-root locations and source mtimes. Directory/file
|
||||
mtimes are **not** normalized, and no reproducible tar/zip archive is claimed.
|
||||
The manifest hashes all payloads but not itself; retain its hash in your
|
||||
separate release evidence if needed. The catalog is trusted reviewed policy,
|
||||
not a cryptographic signature or proof of package-wide integrity/authorship.
|
||||
|
||||
Only explicit catalog paths are read. No application config, sdkconfig,
|
||||
build tree, ELF/map/binary, key, credential backup, NVS/flash dump or whole
|
||||
repository is collected. Hash-pinned source implementations are read only to
|
||||
validate original header excerpts, not copied as implementations. The lock and
|
||||
installed package/component manifests are dependency identity metadata, not
|
||||
build configuration. A hash pin is not a general-purpose secret scanner:
|
||||
review additions to this trusted list before accepting them.
|
||||
|
||||
## Validate and inspect
|
||||
|
||||
Temporary-fixture regression suite (does not need installed packages):
|
||||
|
||||
```sh
|
||||
python3 tests/release_notices/run.py
|
||||
```
|
||||
|
||||
The suite tests missing/changed sources (including changes outside a notice
|
||||
excerpt), exact mixed-text retention, byte ranges, path traversal, source/root/
|
||||
output symlinks, existing user data, nonregular files, bounds, output failures,
|
||||
secret/config exclusion, determinism, and CLI behavior. No hardware tests are
|
||||
implied.
|
||||
|
||||
For a real bundle, check the successful process exit, parse `manifest.json`,
|
||||
recompute every payload hash/size, check no unlisted files are present, and
|
||||
read the scope statement. Compare two independently created fresh outputs by
|
||||
relative names and bytes (not mtimes). Inspect at least both Newlib files,
|
||||
argtable3, wpa_supplicant COPYING/README, GCC exception, wolfSSH LICENSE/README
|
||||
and header excerpts, TLSF text/provenance, both xterm notices and the icon
|
||||
summary. These checks establish scoped assembly, **not release compliance**.
|
||||
|
||||
If a pinned input differs, stop and review the changed package/license/source.
|
||||
There is deliberately no update-catalog command. Review versions, new/nested
|
||||
NOTICEs, original source notice ranges and hashes, then deliberately update
|
||||
catalog policy and test it. The runtime does not discover added notices or
|
||||
packages outside its finite list; this limitation makes its reads bounded and
|
||||
prevents opportunistic collection of user files.
|
||||
|
||||
## Supplied execution evidence — 2026-09-16
|
||||
|
||||
Agent results: **36 notice fixture tests PASS**, and **two actual 75-input
|
||||
bundles verified deterministic**, each **77 files / 4,433,930 bytes**. These
|
||||
replace the earlier 62-input / 64-file / 541,147-byte snapshot, not its historical
|
||||
record. No parent rebuild or bundle reassembly was run in this documentation
|
||||
turn; firmware source is unchanged. Assembly is not legal clearance or Phase 9
|
||||
acceptance. The [unfixed Wi-Fi blocker](wifi_security_update_plan.md) also remains
|
||||
a separate security gate; a future coherent vendor update requires catalog review.
|
||||
|
||||
## Installed candidate notice rebase — 2026-09-18
|
||||
|
||||
This addendum audits the existing 75-input catalog against **already installed**
|
||||
PlatformIO 6.13.0 migration candidates, not merely downloaded archives. No
|
||||
installation, build, production/config edit, or security-override rebase was
|
||||
performed by this notice audit. The preceding 2026-09-16 evidence is historical.
|
||||
|
||||
Actual candidate package root:
|
||||
`/home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/`.
|
||||
Its `framework-espidf/package.json` reports **3.50503.0**, and `version.txt`
|
||||
reports **5.5.3**. Both `toolchain-xtensa-esp-elf/package.json` and
|
||||
`toolchain-riscv32-esp/package.json` report **14.2.0+20251107**. RISC-V identity
|
||||
was checked for context; it is not a new input root or a claim of RISC-V notice
|
||||
coverage. The shared `/home/mscholz/.platformio/packages/` still contains
|
||||
framework **3.50500.0 / IDF 5.5.0** and both toolchains **14.2.0+20241119**;
|
||||
do not use those shared paths with this rebased catalog.
|
||||
|
||||
All 75 old catalog full-file hashes/sizes matched the old shared SDK/toolchain
|
||||
and current project inputs before editing. Comparison against the installed
|
||||
candidate found exactly **nine changed entries** (eight SDK, one Xtensa),
|
||||
listed below. The other **66 entries remain unchanged**, including all **39
|
||||
project/managed inputs**, the dependency-lock pin, icon evidence and managed
|
||||
version map. No catalog paths, purposes unrelated to version identity, or
|
||||
excerpt ranges were added/removed/relaxed. Snapshot identity/date and the three
|
||||
identity-purpose strings were updated deliberately, not by automatic repinning.
|
||||
|
||||
Paths below are relative to the indicated catalog root. Hashes are full-source
|
||||
SHA-256; old hashes remain in the preceding Git catalog revision.
|
||||
|
||||
| Root / path | Old → candidate bytes | Candidate SHA-256 |
|
||||
| --- | ---: | --- |
|
||||
| sdk / `components/esp-tls/esp_tls_mbedtls.c` | 50176 → 54323 | `edc39052244526cb91c93a16bc765194031060e5560fd432e75542f2f6c8db53` |
|
||||
| sdk / `components/esp_http_server/src/httpd_parse.c` | 42937 → 42937 | `db7fbbb322bccb4a21bc1607208a6bb7d1f4c0d7ecc3299de10c9e60edaa943b` |
|
||||
| sdk / `components/esp_https_server/src/https_server.c` | 15257 → 15459 | `a2a5ca0549fbe8d1ddd7f9647a48a31fdd6329b997e1550ab69d6a10efecff0d` |
|
||||
| sdk / `components/freertos/FreeRTOS-Kernel/include/freertos/task.h` | 133072 → 133108 | `8b908b964d2c87377838762478253eec04312b985e6bb680bacd3f03ab253897` |
|
||||
| sdk / `components/mbedtls/mbedtls/library/ssl_tls.c` | 329921 → 336963 | `0154e70f20b465213d3f97a9c2b75e686d79ee83592f90e1d7e7d995593ca7e1` |
|
||||
| sdk / `components/mbedtls/mbedtls/library/x509_create.c` | 19803 → 19934 | `55edce5b8fcb039a404b84ea830f06d018128b155dfbf0d74d26b17247b546c6` |
|
||||
| sdk / `package.json` | 536 → 536 | `230c52993499adf9f64d1d6477495ea46c3f14d63022bd52081858373098da68` |
|
||||
| sdk / `version.txt` | 5 → 5 | `b473c8e2ed91b38d7e96e54d4ce094474ca6015c0641b7cdfc0ab94b1f8b4668` |
|
||||
| toolchain / `package.json` | 472 → 472 | `bdd3b852d051d6513912e71d29851bd04b800e2d33d78aec48067af5d83da5c9` |
|
||||
|
||||
Semantic notice review: five changed implementation files retain byte-identical
|
||||
complete opening notices at their existing ranges (ESP-TLS `[0,122)`, HTTPD
|
||||
`[0,124)`, HTTPS `[0,123)`, Mbed TLS shared TLS `[0,223)` and X.509 `[0,169)`).
|
||||
Their output hashes stay unchanged; full-source hashes must change even when
|
||||
only implementation bytes change. This does **not** approve the implementation
|
||||
changes or establish compatibility of the project's security overlays.
|
||||
FreeRTOS `task.h` retains its complete MIT notice at `[0,1455)`; only
|
||||
`SPDX-FileContributor: 2023-2024` becomes `2023-2025` within that excerpt.
|
||||
Its output hash changes from
|
||||
`8e3d9f79bbbdf6dfd6fbf132982a7ec3a45ff6dac92277034316efae4cccad84` to
|
||||
`e53c9bcefd4327c713696e870a3568a27cedf8833467b9cd9536cb2e09ab7ad8`.
|
||||
The remaining `task.h` changes correct stack-watermark documentation to bytes.
|
||||
The two package JSON diffs change only versions; SDK `version.txt` changes only
|
||||
`5.5.0` to `5.5.3`.
|
||||
|
||||
All 24 remaining SDK inputs and three Xtensa license inputs match the old pins,
|
||||
including SDK/radio Apache texts, both Newlib documents, mixed argtable3,
|
||||
wpa_supplicant and GCC exception/GPL texts. An auxiliary installed-tree filename
|
||||
comparison found the same 45 SDK files whose basenames start with LICENSE,
|
||||
COPYING or NOTICE (case-insensitive), with no additions/removals. All 85 files
|
||||
under Xtensa `share/licenses/` have identical relative paths and hashes. This
|
||||
finite discovery check is not an exhaustive attribution or linked-object audit.
|
||||
|
||||
Validation after the catalog edit: **36/36 notice fixture tests PASS** via
|
||||
`python3 -B tests/release_notices/run.py`; two fresh temporary actual bundles
|
||||
using the candidate roots above and the current project passed assembly,
|
||||
per-payload manifest SHA-256/size checks, exact file-list checks, and identical
|
||||
relative names/bytes across outputs. Each has **77 files / 4,433,930 bytes**
|
||||
(the unchanged total size does not imply unchanged contents). Both manifest
|
||||
SHA-256 values are
|
||||
`1fd0a69b82dc2632bab4ab6dda33675e7229c8d341c7fafb7158c1e8f2456a68`.
|
||||
Temporary bundles were removed after comparison. Project entries and managed
|
||||
snapshot pins were also asserted equal to the pre-migration Git catalog.
|
||||
|
||||
These results are **notice assembly evidence only**, not legal clearance,
|
||||
corresponding-source delivery, radio-blob exception approval, vulnerability
|
||||
closure, firmware build validation or target acceptance. Existing pinned project
|
||||
provenance retains historical baseline wording; this addendum distinguishes the
|
||||
new SDK notice snapshot without silently rewriting/re-pinning that evidence.
|
||||
All separate distribution gates below remain open.
|
||||
|
||||
## Separate work before distribution
|
||||
|
||||
1. Resolve or obtain qualified review of radio-blob corresponding-source or
|
||||
exception basis for the actual linked firmware. Apache binary permissions
|
||||
alone do not settle this question. No legal clearance is claimed.
|
||||
2. Resolve the wolfSSH package/header discrepancy and remaining icon derivative
|
||||
questions. [Exact retained SVG provenance](icon_provenance.md) is verified at
|
||||
official 7.4.47, commit `9e04201d4557e729822fb57f62a316c3dea1d4a8`: Google
|
||||
USB and Simran Wi-Fi. Full Apache text is retained; no NOTICE was found in
|
||||
that pinned distribution. Manual firmware bitmap derivation and the distinct
|
||||
mockup Wi-Fi path remain unresolved. No artwork was regenerated.
|
||||
3. Audit the final application/bootloader/runtime object set and aggregation
|
||||
boundaries. Full SDK/toolchain redistribution needs broader nested/tool/test
|
||||
notice coverage; this is not an all-files license scanner.
|
||||
4. Prepare and validate a separate, deliberately reviewed corresponding-source
|
||||
delivery. Preserve exact required sources and safe build/install inputs,
|
||||
asset preferred sources and scripts, original hash-matching overlay inputs,
|
||||
`tools/security_overrides.py`, `cmake/security_overrides.cmake`, root CMake
|
||||
integration, `cmake/wolf_crypto_policy.{cmake,h}`, and the complete
|
||||
`tools/wolfssh_order/` inputs/provenance/archives. Preserve upstream notices
|
||||
and generated dated change notices on eight C sources and one PUBLIC forced
|
||||
header. A stock vendor tarball or this notice bundle is not sufficient.
|
||||
Do not automatically archive the working repository or real configurations.
|
||||
5. Assess GPLv3 source delivery/offer and User Product Installation Information.
|
||||
Protect device passwords, Wi-Fi credentials, private keys, verifiers,
|
||||
tickets, credential backups and flash/NVS images throughout that process.
|
||||
6. Arrange and verify actual notice delivery with firmware, devices and browser
|
||||
assets. This command does not add a license endpoint, change browser assets,
|
||||
or prove that a recipient of a JS response or standalone device receives
|
||||
notices. Keep these gates open until the real delivery path is validated.
|
||||
@@ -1,10 +1,10 @@
|
||||
# Implementation roadmap
|
||||
|
||||
This document tracks the implementation and hardware-validation plan for the ESP32 Serial Swiss Army Knife. It describes intended sequencing rather than a release schedule. Completion requires implementation, documentation and build validation, plus relevant hardware tests or an explicitly recorded user waiver. A waiver is not a hardware-test pass.
|
||||
This document tracks the implementation and hardware-validation plan for the ESP32 Serial Swiss Army Knife. It describes intended sequencing rather than a release schedule. A phase is complete only when its implementation, documentation, build validation, and relevant hardware tests have passed.
|
||||
|
||||
## Status legend
|
||||
|
||||
- **Complete** — implemented and accepted, with target-hardware validation or an explicit phase-specific user waiver recorded below.
|
||||
- **Complete** — implemented and validated on the target hardware.
|
||||
- **In progress** — implementation or validation is actively underway, but the overall phase is not complete.
|
||||
- **Implemented; validation pending** — code is present and builds, but the current implementation still needs the listed hardware checks.
|
||||
- **Planned** — accepted project direction, not yet implemented.
|
||||
@@ -38,11 +38,12 @@ These constraints apply across all phases:
|
||||
| 5B | Offline xterm.js WebSocket serial terminal | **Complete** |
|
||||
| 6 | Authenticated SSH serial transport | **Complete** |
|
||||
| 7 | Local display and button interface | **Complete** |
|
||||
| 8 | Role-based users and administrative access | **Complete** |
|
||||
| 9 | Small intermittent-use security baseline | **Complete (user signoff 2026-09-18; new hardware check waived)** |
|
||||
| 10 | Simple admin web firmware upload | **Complete (explicit user acceptance 2026-09-18; upload and normal operation verified)** |
|
||||
| 12 | Dual-stack networking and local service discovery | **Complete (explicit user validation; fresh boot and full client mix at 230400 baud)** |
|
||||
|
||||
| 8 | Role-based users and administrative access | **Complete (8D.22 accepted 2026-09-13)** |
|
||||
| 9 | Security and production hardening | **In progress (9A–9C implemented; combined phase validation deferred)** |
|
||||
| 10 | Authenticated, rollback-capable OTA | **Planned** |
|
||||
| 11 | BLE serial transport and provisioning evaluation | **Planned** |
|
||||
| 12 | Advanced network integration | **Under evaluation** |
|
||||
| 13 | Optional filesystem-backed features | **Under evaluation** |
|
||||
|
||||
## Completed phases
|
||||
|
||||
@@ -199,173 +200,96 @@ Completed implementation (8A–8C target-hardware validated; 8D explicitly accep
|
||||
- Typed Serial, Accounts/password/authorized-key, Network/Wi-Fi/mDNS, Display, Broker and SSH settings; confirmed HTTPS stop/restart/reboot and HTTPS/SSH identity rotation. Canonical owners compare reserved service/identity/target generations, preserve commit/failure semantics and expose no stored secrets. Admitted work is not cancelled by later logout/timeout; uncertain mutations are never automatically replayed.
|
||||
- Accessible Serial/Wi-Fi/client/writer contextual controls reuse existing controllers. Writer transfer is explicit and generation-safe; one writer, isolated observers, binary transparency, UART0 administrative recovery and network-independent native USB UART1 remain invariants.
|
||||
- Scope removed by user: **8D.15** dedicated typed network diagnostics (shell diagnostics retained) and the unimplemented **8D.19** ordinary browser-session/native-USB controls (SSH controls retained). Browser identity reset/recovery/export is excluded; canonical recovery remains available.
|
||||
- Acceptance evidence and legacy storage compatibility are condensed below. [Web administration](web_administration.md) retains current API/owner/recovery contracts; [regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) retain practical checks, not claims of execution.
|
||||
|
||||
#### Phase8 acceptance evidence
|
||||
|
||||
**8D.22 signoff, 2026-09-13:** the user explicitly accepted the tested firmware, superseding earlier per-slice target, review and integration gates for the retained scope above. Earlier scoped acceptance covered browser login/logout, shared administration, Serial/account/Network presentation and legacy cleanup. Prior host regression suites and independent reviews were reported passing; this consolidation asserts no new build or test execution.
|
||||
|
||||
Prior final Phase 8 build: **PASS, 100,556 B linked RAM / 1,828,573 B flash, CPU 160 MHz**. The combined binary WebSocket-send fix was separately accepted at **160 MHz / 230400 baud with the full client mix including browser admin**; retain combined send and bounded failed-send isolation, not the earlier frequency-only experiment. Ordinary HTTPS idle cleanup was accepted, not every possible admission failure or owner delay.
|
||||
|
||||
Latest reported Phase 8 telemetry (bytes):
|
||||
|
||||
| Sample / capability | Free | Minimum free | Largest block |
|
||||
|---|---:|---:|---:|
|
||||
| Boot internal 8-bit | 59,808 | 58,840 | 31,744 |
|
||||
| Boot internal DMA | 52,052 | 51,084 | 31,744 |
|
||||
| Boot PSRAM | 8,196,968 | 8,183,972 | 8,126,464 |
|
||||
| Loaded after burst, internal 8-bit | 31,508 | 2,052 | 18,432 |
|
||||
| Loaded after burst, internal DMA | 23,752 | 460 | 18,432 |
|
||||
| Loaded after burst, PSRAM | 8,136,624 | 8,065,972 | 7,995,392 |
|
||||
|
||||
Loaded SSH minimum-free stack: **15,028 B**. This sample had two SSH sessions (serial/admin), two serial WebSockets and USB, with SSH as writer. Browser admin had been used then closed; it was **not active in this capture**. Web send/queue/protocol and SSH IO errors were zero; one SSH handshake failure and one session revocation were reported without an established cause.
|
||||
|
||||
**Limits, not reopened acceptance gates:** missing latest broker/serial counters prevent an exact zero-drop or byte-integrity inference. No latest browser-admin-active full-mix, individual fault-injection, exact duration, soak, cleanup-cycle or reserve-floor result is inferred. Internal/DMA lifetime minima remain a nonblocking transient-headroom follow-up, not approved reserves or proof of allocation failure: capability pools overlap and per-region minima may be non-simultaneous, so do not sum them. HTTPD/dispatcher stack margins, correlated peaks and allocation-reserve policy remain unmeasured/unapproved here. SDK TLS `-0x004C` means NET_RECV_FAILED, not an OOM diagnosis; stale cookies as the cause of two boot authentication failures remain unconfirmed.
|
||||
|
||||
#### Phase8 legacy credential compatibility
|
||||
|
||||
Cleanup was accepted **2026-09-08**, with unchanged certificate fingerprint and usable preexisting test users. This is not an unreported provisioning, recovery, power-loss or all-key pass.
|
||||
|
||||
- **Users:** missing `user_db/database` is committed as an empty v1 database, with no legacy account/password import. First-admin provisioning is physical UART0 `user add <username> admin` (optional `--generate`); entered passwords use hidden confirmation and generated passwords appear once. UART0-only `user recover --force` rebuilds only an unavailable database empty, refuses healthy databases (including empty ones), and leaves other configuration and TLS/SSH identities untouched; follow with normal `user add`.
|
||||
- Valid existing v1 user bytes load without rewrite or changes to accounts, roles, IDs, authentication generations, verifiers or authorized keys; migrated `user` accounts are not promoted. Private `v1_admin_marker` preserves the old wire byte and is derived from admin count on mutation, not a new role/schema/public bootstrap state. Public bootstrap and legacy migration/synchronization APIs are removed; final-admin protection, conditional mutations, principal currentness and targeted revocation remain.
|
||||
- **TLS:** at unchanged `web_sec/material`, a private reader validates the **1,392-byte v1** layout and migrates to **1,340-byte TLS-only v2**, preserving exact private-key DER, certificate DER, SHA-256 fingerprint and material generation—not rotating identity. The candidate is validated and committed before publication; temporary credential-bearing input is wiped. Malformed/unknown material, read/crypto failures and migration write/commit failures fail closed without regeneration or overwrite; truly missing material may be generated normally.
|
||||
- `web credentials show`, `web credentials rotate --force` and `user bootstrap` are removed. `web certificate info` / `web certificate rotate --force` retain frontend policy. `web reset --force` replaces TLS identity only, not accounts/passwords/verifiers; replacement/restart may close browser sessions and require renewed certificate trust/login, without revoking unrelated SSH sessions. No public legacy credential reader, display, rotation or authentication path remains.
|
||||
- **Downgrade/security:** older v1-only firmware cannot read v2 HTTPS storage; user-database v1 compatibility does not make HTTPS downgrade safe or restore removed credentials. Logical NVS replacement is **not secure flash wiping**: historical plaintext legacy credentials may remain, Wi-Fi secrets and TLS/SSH private keys remain unencrypted, and PBKDF2 verifiers permit offline guessing. No erase is required or authorized by cleanup.
|
||||
- Prior host tests used NVS fault doubles and a generated identity wrapped in the historical layout, **not an independently captured old-device fixture**; they do not prove flash durability or power-loss behavior. Focused runners: `tests/web_security/run.py`, `tests/admin_console_boundary/accounts.py`, `tests/admin_ssh_policy/run.py`. See [legacy-removal regressions](user_administration_tests.md#current-legacy-removal-regression-procedure).
|
||||
|
||||
### Phase 9 — Security and production hardening
|
||||
|
||||
**Complete — user signoff 2026-09-18; new hardware check explicitly waived.** Reduced scope: an intermittently powered wireless serial cable on a trusted network, not a permanently exposed Internet service. The earlier extensive Phase 9 implementation was abandoned in favor of the accepted pre-Phase-9 baseline. Short exposure reduces risk; it does not make upstream defects harmless or certify this firmware secure.
|
||||
|
||||
Small implementation:
|
||||
|
||||
- Keep the baseline PlatformIO/ESP-IDF and managed-library versions and upstream sources. No generated dependency patches, custom SSH state machine, crypto-policy replacement, allocator hooks, or SDK migration in this phase.
|
||||
- Explicitly disable core dumps and select silent panic reboot through standard `sdkconfig.defaults` options. Keep ordinary logging; reduced crash diagnostics are a deliberate trade-off. Existing generated configurations must be checked because defaults do not override saved selections.
|
||||
- Follow the [operational checklist](security_operations.md): unique credentials, trusted identity verification, no Internet forwarding, networking off/power off after use, and existing rotation/recovery procedures.
|
||||
- Review upstream maintenance releases occasionally and before planned upgrades. Evaluate reachable issues against actual use; any stock dependency upgrade is a separate build/device-tested task, not a reason to accumulate local backports. Preserve existing license notices.
|
||||
|
||||
**Excluded:** secure boot, NVS/flash/PSRAM encryption, eFuse changes, partition changes, exhaustive CVE/zeroization audits, new authentication throttling, and production certification. Existing authentication, session isolation and recovery behavior remain unchanged. There is no protection against physical extraction or replacement of firmware. Phase 10 uses administrator-selected firmware, without a separate image-signing system.
|
||||
|
||||
**Host/build evidence (2026-09-18):** normal `pio run` passed on PlatformIO 6.12.0 / ESP-IDF 5.5.0 with the original 20241119 toolchains: **94,196 B linked RAM / 1,828,565 B flash**. Generated configuration confirms no dumps and silent panic reboot; compilation inputs contain none of the abandoned Phase 9 source overlays or wolf crypto guards. No device operation or target validation was performed.
|
||||
|
||||
**Acceptance (2026-09-18):** the user signed off this small Phase 9 and explicitly waived a new whole-phase hardware check of boot, UART0 recovery, native USB serial, HTTPS/SSH login and normal serial sharing. Application code is unchanged, **but crash-diagnostic defaults changed**; this is a waiver, not evidence of target or panic-path validation. The prior build/configuration evidence above remains the validation record; no new hardware test was performed. A separately controlled panic check with synthetic secrets remains optional, not a completion gate; do not add a production panic endpoint or erase flash. Disabling dumps does not erase old contents.
|
||||
|
||||
### Phase 10 — Simple admin web firmware upload
|
||||
|
||||
**Complete — explicit user acceptance on 2026-09-18: firmware upload works and normal operation is verified.** The admin **Settings → HTTPS / Reboot** firmware card uploads the locally built `.pio/build/esp32-s3-devkitc-1-n16r8/firmware.bin`. Install this OTA-enabled firmware by wire first, then use web application uploads. There is no release server, automatic download or update service. [Firmware update and recovery](#phase10-update-and-recovery) below covers operation; [regression guidance](#phase10-regression-guidance) covers future checks.
|
||||
|
||||
Implemented scope:
|
||||
|
||||
- Raw `POST /api/firmware` requires exact `Content-Type: application/octet-stream`, known exact `Content-Length`, cookie, same-origin Origin and `X-CSRF-Token`; no multipart/JSON, Basic authentication or ordinary `user` access. Reject queries, ambiguous headers, chunked transfer and `Expect`; UI code leaves browser-managed headers alone. Admission precedes body/flash work; session/principal currentness is rechecked before boot selection. The card confirms upload/reboot and shows progress/results without automatic retries. HTTPD capacity is 40 method/path handlers.
|
||||
- Standard ESP-IDF OTA APIs (`esp_ota_begin`, `esp_ota_write`, `esp_ota_end`, `esp_ota_set_boot_partition`) stream one upload through a 4 KiB buffer into the inactive slot (two 4 MiB app slots). A reboot task with a 2 KiB stack is preallocated before erase. Lifecycle/identity reservations and ordinary software-reboot gating exclude competing work; no custom flash protocol or whole-image RAM buffer.
|
||||
- Accepts an ESP32-S3 application binary for this board/layout, not an ELF, ZIP, merged full-flash image, bootloader or partition-table image. Browser hints require a nonempty `.bin` of at most 4 MiB; the server bounds raw length against actual destination capacity before trusting HTTPD's narrowed length. Checks completeness, target/header and exact SDK image length/integrity, including mandatory appended SHA-256, before selection. The digest is not publisher authentication; neither it nor filename checks prove board compatibility or a working application.
|
||||
- Before selection, rejected/incomplete uploads and receive/write/validation failures abort any live OTA handle without selecting the candidate; the inactive slot may be erased/partially written. Boot-metadata failure (`firmware_commit_failed`) needs inspection and carries SDK transactional uncertainty. `200 {"ok":true,"rebooting":true}` means validation/selection succeeded; successful synchronous send schedules restart after 500 ms, retaining reservations, but proves neither browser receipt nor boot success. Response failure after selection schedules no restart, releases reservations for manual reboot and latches further admissible uploads to 409 `firmware_selected_reboot_required`; the latch survives HTTPS stop/start until device reset.
|
||||
- Synchronous HTTPD receive/flash blocks other HTTPD work: browser sessions can stall/drop. The 120-second total receive-loop and ten-second stall checks use the existing one-second socket timeout, not an absolute deadline: synchronous SDK erase/write/validation and scheduling are not preempted. The browser's 180-second timeout cannot cancel committed work. UART0/native USB remain independent paths, not guarantees of uninterrupted serial timing during flash; reboot interrupts all transports. Software exclusion cannot prevent physical reset, power loss or panic.
|
||||
- **Preserve NVS:** write only the inactive application slot and the OTA selection metadata. Do not erase the chip, rewrite the partition table/bootloader, or touch `nvs`, `nvs_key`, PHY, storage or other data partitions. Existing users, passwords, Wi-Fi/serial settings and HTTPS/SSH identities remain stored, as with an application-only wired update without erase. This preserves stored bytes; the uploaded firmware must still understand the existing schemas and must not itself erase/migrate them incompatibly.
|
||||
- Keep wired USB-to-UART flashing documented as recovery if the uploaded application does not boot or no longer serves the web UI. Basic image validation cannot prevent a valid but broken application from requiring wired recovery.
|
||||
|
||||
**Not in scope:** secure boot, image signatures/signing-key management, anti-rollback/version-downgrade enforcement, automatic rollback/post-boot health-confirmation machinery, remote release discovery, partition migration or NVS backup/restore. Use upstream OTA support without dependency patches. The administrator is responsible for selecting trusted, compatible firmware, including when deliberately installing an older build.
|
||||
|
||||
**Historical integration build and host evidence (parent-reported):** `pio run` **PASS**, **94,220 B linked RAM / 1,847,645 B flash**. Against Phase 9's 94,196 B / 1,828,565 B, this is **+24 B RAM / +19,080 B flash**. Final backend 88 cases plus the pinned SDK begin/abort failure-contract test, UI 169 groups, server lifecycle 44, admin transport 25, console lifecycle, SSH runtime and cookie lifecycle checks passed. Additional cookie base/admin/display/lifecycle, HTTPD idle cleanup 18, and SSH management/runtime/security checks passed; the extra cookie `--admin` run initially lacked a reboot symbol in its fixture, corrected in test-only code before passing. Host doubles are not target evidence. The later concise-UI fix was copy-only; its latest reported UI regression passed all **169 groups**, with no rebuild after the text change. The build figures above remain historical; this documentation update ran no build or tests.
|
||||
|
||||
**Acceptance (2026-09-18):** after the firmware upload implementation and concise-UI fix, the user confirmed: “That works perfectly. And the usual operation is also verified.” Phase 10 is complete by this explicit acceptance of working upload and verified normal operation. Do not infer specific fault-injection, NVS before/after comparisons, power-loss or wired-recovery passes. The [regression guidance](#phase10-regression-guidance) below is reusable future guidance, not an acceptance blocker.
|
||||
|
||||
#### Phase10 update and recovery
|
||||
|
||||
1. Keep **USB-to-UART** available for UART0 administration/flashing; native USB CDC is network-independent UART1 access, not administration. Install the updater by wire first: older firmware without the route cannot install its own first web updater. The custom OTA layout is required; [one-time old-layout migration](../README.md#one-time-migration-from-the-default-partition-table) is separate and destructive.
|
||||
2. Build trusted, schema-compatible `esp32-s3-devkitc-1-n16r8` firmware with `pio run`; choose **`.pio/build/esp32-s3-devkitc-1-n16r8/firmware.bin`**, never a merged image or a renamed non-application file. Observe the [downgrade warning](../README.md#legacy-credential-removal).
|
||||
3. Save desired RAM-only settings and record nonsecret configuration/public HTTPS/SSH fingerprints. Arrange a maintenance window and stable power/network; sign in as `admin` over trusted HTTPS, open **Settings → HTTPS / Reboot → Firmware update**, choose the file and confirm **Upload and reboot…**.
|
||||
4. Wait for validation/reboot, not just 100% transmitted bytes. Restore network reachability, reload and explicitly sign in; verify the running application, saved configuration and identities before another upload.
|
||||
5. A lost response, timeout, page close, sign-out or browser abort proves neither cancellation nor failure. The UI locks uncertain outcomes without replay; reload clears only browser locks, not the device latch. Inspect through UART0 and deliberately reboot if appropriate: any later reset can boot an already selected image.
|
||||
6. For initial wired installation or recovery when boot/HTTPS fails, connect USB-to-UART, select a known-good compatible checkout, run `pio run --target upload`, then `pio device monitor -b 115200`. **Do not erase for ordinary updates or recovery**; there is no automatic recovery flashing/rollback.
|
||||
|
||||
Replies: **400** malformed/incompatible/incomplete input; **401/403** authentication/Origin/CSRF or `admin_required`; **408** `firmware_timeout`; **409** selected-image latch; **413/415** destination capacity/content type; **500** `firmware_write_failed`/`firmware_commit_failed`; **503** busy/unavailable/resources. Unread rejected bodies close rather than drain; bounded secret-free JSON may never reach a disconnected browser.
|
||||
|
||||
#### Phase10 regression guidance
|
||||
|
||||
These are reusable checks, **not recorded passes or outstanding acceptance gates**. Record only performed outcomes; host flash/network/scheduling doubles are not hardware evidence. Focused host suites: `python3 tests/web_firmware_update/run.py` and `python3 tests/web_ui_session/run.py` (the SDK begin/abort case covers a live handle published on erase failure).
|
||||
|
||||
- Exercise wired-first install, known-good upload/reboot/new build and explicit reconnect; compare saved users/roles, password/key login, Wi-Fi/serial/display/hostname settings and public HTTPS/SSH fingerprints without recording secrets.
|
||||
- Interrupt/cancel receive; reject corrupt, truncated, wrong-target, non-application and oversized inputs without unintended selection/restart, then deliberately perform a valid update. Reject unauthenticated/ordinary-user, wrong-Origin and missing/wrong-CSRF requests before flash. Never attempt power interruption at commit without wired recovery ready.
|
||||
- Exercise competing upload, HTTPS lifecycle/identity and ordinary reboot exclusion; HTTPD requests may wait rather than promptly return busy. With controlled post-selection response-failure injection, verify no automatic restart, subsequent 409 (also after HTTPS restart), then manual reboot; an arbitrary disconnect does not establish this fault case.
|
||||
- Observe web stalls/drops and serial/network recovery, single-writer isolation, UART0 recovery and native USB UART1 access without networking; demonstrate known-good wired recovery without erase and recheck configuration/identities. Do not claim uninterrupted traffic.
|
||||
- Prior final build PASS: **100,556 B linked RAM / 1,828,573 B flash, CPU 160 MHz**. Earlier combined binary WebSocket-send fix was explicitly accepted at **160 MHz / 230400 baud with full client mix including browser admin**. Latest overall sign-off does not invent individual fault/soak/duration results or a latest exact zero-drop comparison.
|
||||
- Latest loaded internal/DMA minima **2,052 / 460 B** remain a conservative transient-headroom follow-up, not a blocker reopening acceptance or an approved reserve. See [acceptance and telemetry](web_administration_acceptance.md), [current contracts](web_administration.md), [regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) and [legacy compatibility](legacy_credential_removal.md).
|
||||
|
||||
## Current and planned phases
|
||||
|
||||
**Phases 8, 9, 10 and 12 are complete** for their accepted scopes. Phase 9 includes the explicit new-hardware-check waiver above; Phase 10 includes explicit user acceptance of upload and normal operation. Phase 12 includes explicit user validation and the fresh-boot/full-client evidence below. Optional features must not weaken completed serial and recovery paths. General release gates below guide future work, not claims that every fault, soak, recovery or reserve measurement was performed for completed phases.
|
||||
Phase 8 is complete for its accepted scope. Phase 9 has started at the user's request; later work remains planned or under evaluation. Optional features must not weaken completed serial and recovery paths. General release gates below remain guidance for future work, not claims that every listed fault, soak or reserve measurement was individually performed for 8D.
|
||||
|
||||
### Phase 9 — Security and production hardening
|
||||
|
||||
**In progress.** Harden network authentication, secret lifetimes, crash/debug exposure and operational maintenance. Secure boot and encrypted NVS are explicitly excluded by user preference. No eFuse, partition, at-rest encryption or dependency-version upgrades are part of 9A–9C; 9C adds source-pinned build-tree dependency corrections; no future flash/PSRAM encryption commitment is implied. Physical extraction and firmware replacement remain outside the threat model after Phase 9, and software debug restrictions do not imply physical JTAG fuse restrictions.
|
||||
|
||||
Staged work:
|
||||
|
||||
1. **9A — Crash/debug build policy and operational profiles — In progress; hardware pending.** `src/security_build_policy.c` requires `CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y` and `CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y`; rejects core-dump enable/flash/UART, panic print/halt/GDBstub, runtime GDBstub and ESP/FreeRTOS debugger-aware options. `sdkconfig.defaults` makes the baseline explicit. Development/test/production use the same build baseline, not separate PlatformIO environments. Host matrix (`python3 tests/security_build_policy/run.py`) compiles the actual guard: 17 cases plus the generated-header check passed on 2026-09-15. `pio run` passed (94,196 B linked RAM / 1,828,565 B flash); target panic/recovery tests have not run. Production readiness remains pending.
|
||||
2. **9B — SSH admission and credential handling — Implemented; combined target validation deferred.** Boot-lifetime, owner-only token buckets independently bound handshakes, password/signed-key requests and unsigned probes; reconnect/restart/counter clearing do not replenish them. Existing per-slot attempt limits/currentness remain. Explicit keyboard-interactive rejection, pending-signature result fencing, secret-free admission counters, consumed admin-buffer wipes and fail-closed hidden-prompt overflow/unsupported-byte handling are implemented. Four focused suites (including 35 pinned-vendor control-flow cases), 11 related regressions and `pio run` passed on 2026-09-15: 94,340 B linked RAM / 1,829,925 B flash. Global-budget starvation remains a documented tradeoff, not a solved availability problem.
|
||||
3. **9C — Library cleanup and protocol policy — Implemented; combined target validation deferred.** Exact-hash build-tree overrides correct HTTPS cleanup/leaks, HTTPD scratch failure/wiping/first-read handling, bounded SSH password parsing/wiping and server-local TLS policy without modifying installed dependencies. Secure wolfSSL allocation hooks and explicit SSH policy fail closed; early web quota probing avoids receiving already-throttled bodies. TLS1.2 ECDHE-ECDSA AES-GCM and SSH GCM/modern-KEX allowlists intentionally exclude legacy-only clients; no identity migration. Bounded password/certificate/header/destructor review is documented, not exhaustive zeroization. Final build PASS 94,340 B linked RAM / 1,831,309 B flash; focused and related host/source-contract tests passed. [Review and maintenance contract](security_library_review.md).
|
||||
4. **9D — Maintenance and lifecycle — In progress; unresolved security and distribution questions.** [Security operations](security_operations.md) documents provisioning, explicit saves, rotation, recovery, reconstruction (no implemented backup/export) and approved destructive reset/decommissioning. The [wolfSSL/wolfSSH implementation addendum](wolf_security_review.md) records the implemented Xtensa small X25519/Ed25519 mitigation (CVE-2025-12888), with consistent library/consumer flags and a resolved-settings guard; small X25519 is not combined with blinding. Bounded IGNORE/service/string parsing, channel-window overflow, ECC/Ed25519 labels and exact signature framing are now corrected in the existing generated override. [P-256 import and X25519 all-zero-result checks](ssh_key_validation_review.md) are enabled with effective PUBLIC flags confirmed; the [finite remaining SSH parser/caller review](ssh_parser_remaining_review.md) is complete, not exhaustive safety. Exact CHANNEL_FAILURE recipient, `ssh-userauth` service and all nine channel-name checks are implemented; unknown/trailing-payload behavior is retained. PR899 client skips remain unchanged behind server ordering, forwarding is disabled, and the inspected generic caller trace has no short-digest/OID trigger; generic APIs are not globally fixed. **Restricted existing-profile ordering correction (CVE-2025-14942) is implemented**, using audited PR793/819/840/855/921 subsets plus local gates, not a full upstream backport. X25519/P-256 only, independent SELF/PEER bits, expected-message/authentication gates and queued NEWKEYS continuation handle nonblocking writes. **EXT_INFO is intentionally disabled; no `server-sig-algs` is advertised.** Nine C overrides (including the new IDF 5.5.3 WS signed-size correction) plus one PUBLIC forced ABI header retain hash pins/notices; joined forced-header arguments fix PlatformIO sorting/deduplication. The review's misplaced EOF guard is corrected and verified with no scoped blocker. The [IDF implementation addendum](idf_security_review.md) records pinned DHCP (CVE-2026-45160), TLS 1.2 EMS (CVE-2026-50581) and X.509 allocation-failure (CVE-2026-34874) backports; historical research remains labeled and retained. The [finite IDF applicability completion](idf_security_review.md#finite-applicability-completion--2026-09-16) dispositions all six named findings: ECDH small-output, zero-length ECC PK parse, basicConstraints, server NewSessionTicket and stale ASN.1 length are not current paths/configurations for their documented reasons; optimized ECC reduction is active but its privileged-local/physical side-channel attacker model is excluded. No new current-path correction was established. That earlier review screened only the first advisory-index page. The subsequent [finite dependency coverage](dependency_advisory_coverage.md) accounts for all **22 IDF entries across three pages**, plus named managed-component and browser publication channels. The declared dated search is complete, not exhaustive advisory clearance; unpatched primitives, inaccessible full-release databases and fresh findings remain.
|
||||
|
||||
**Historical IDF 5.5.0 integration evidence:** supplied parent build PASS: **94,340 B linked RAM / 1,768,901 B flash**, unchanged RAM / **+200 B flash** versus 1,768,701 B. Final parent **all seven suites PASS**: ordering `--interop` (8,028 checks, seven rejected mutations, 12 exact-256-KiB sessions with clean channel close/transport EOF), SDK overrides with actual `--build-dir` registration, auth (135), protocol, strict crypto, notices (30), and parser (3,258 × two modes plus channel profiles; 11 + 18 + 2 rejected mutations). Interop required unsandboxed approval only for local AF_UNIX sockets; no remote network/device operation. Agent ordering evidence: **8,028 checks / seven rejected mutations**. Initial interop failed a harness close race despite early PASS output; the corrected harness waits for peer close/transport EOF and independently reaps the server through an fd-passing proxy. Final agent repeated OpenSSH matrix: **36/36 sessions**, each exact **256 KiB**, **two or ten** completed key exchanges and clean client/server exits; see [evidence and limits](security_hardening.md#ordering-hostbuild-evidence--2026-09-16).
|
||||
|
||||
**Release status:** the [bounded license inventory](dependency_licenses.md) marks modification/date notices resolved for all nine generated C files plus one header (baseline 2026-09-15 and wolfSSH ordering 2026-09-16; upstream licenses retained), with archived patch hashes/commits and patch license context recorded, but radio-blob corresponding-source/exception, source/notice delivery and provenance questions remain open. [Offline notice assembly](release_packaging.md) is implemented by [tools/release_notices.py](../tools/release_notices.py): 75 mandatory hash/size-pinned inputs (nine IDF/toolchain notice entries semantically rebased for the candidate; the other 66 unchanged), deterministic bounded outputs, fail-closed preflight and no overwrite/fetch/build/device access. Supplied agent evidence: **36 notice fixture tests PASS; two actual bundles verified deterministic, each 77 files / 4,433,930 bytes**. This includes retained SVGs and [exact icon provenance](icon_provenance.md), not corresponding-source delivery or legal clearance. These are agent measurements, not a parent rebuild or reassembly in this documentation turn.
|
||||
|
||||
**Remaining maintenance/target gates:** official registry wolfSSH 1.5.0 / wolfSSL 5.9.2 queries returned 404 on 2026-09-15; upstream tags exist but managed compatibility is not established. The [remaining ordering gates and immutable provenance](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates) retain target cleanup during rekey, intended-client compatibility without EXT_INFO and whole-phase resource/timing validation. Future upstream adoption still requires isolated packaging/compatibility evaluation. Upgrades/backports require coherent source/header review, override rebasing and contract tests, not blind repinning. The [declared finite dependency search](dependency_advisory_coverage.md) is complete; fresh findings and explicit coverage gaps remain. **The fix-bearing Wi-Fi vendor bundle is integrated in the validated IDF 5.5.3 candidate; radio-hardware closure remains pending.** Follow the [Wi-Fi security update plan](wifi_security_update_plan.md) for target acceptance; do not transplant one archive, edit only the callback signature or weaken PMF/WPA3. Root migration pins are edited; complete immutable dependency closure and a successful normal root build are not established. **Phase 9 is not complete or production-ready; scoped mitigations do not establish full advisory closure, device validation or license/distribution clearance.** OTA signing trust remains separate Phase 10 work.
|
||||
|
||||
**Current IDF 5.5.3 integration:** fresh isolated `app-validated` build PASS, **95,552 B linked RAM / 1,749,493 B flash** (**+1,212 / −19,408 B** versus historical 94,340 / 1,768,901 B). Root selects PlatformIO 6.13.0 / framework 3.50503.0 / both toolchains 14.2.0+20251107. Default root `pio run` timed out after 200 seconds during installation, before compilation. All nine C overrides plus the header have actual-build registration evidence; generated WS tests pass **982 cases / 10 mutation checks**. See [integration and remaining gates](idf_candidate_integration.md).
|
||||
|
||||
**Bounded host validation:** 23 commands by default; build, OpenSSH interop and web performance are explicit options. Historical default 23/23 and 10 orchestrator fixtures passed on the earlier snapshot. Current supplied parent validation selects the isolated IDF 5.5.3 build/core/SDK, with `--interop --web-performance`: **PASS 24/24**. See [exact command, source equality and evidence limits](phase9_validation.md#current-candidate-execution--2026-09-18). This does not claim a successful default root build or whole-phase acceptance.
|
||||
|
||||
At the user's request, hardware validation is deferred to **Phase 9 as a whole**, not required between implementation slices. [Security hardening](security_hardening.md) collects profiles, host evidence and the combined target checklist. Silent panic reboot removes useful crash diagnostics, not ordinary reset/boot/status information or every possible log disclosure. Raw flash/RAM/dumps remain secret-bearing, not routine diagnostic exports. Existing coredump bytes are not retroactively cleared; no secure erase is claimed. Isolated synthetic-secret debug builds require explicit reviewed source-policy changes, not a provided bypass flag.
|
||||
|
||||
### Phase 10 — Authenticated OTA and rollback
|
||||
|
||||
Use the existing dual 4 MiB application slots and `otadata` partition to add safe firmware updates.
|
||||
|
||||
Planned work:
|
||||
|
||||
- Accept only authenticated, integrity-checked firmware images under an independently defined OTA signing-trust policy without secure boot; define trusted-key provisioning, rotation, revocation and recovery. This cannot prevent physical firmware replacement.
|
||||
- Support an update path that does not expose a plaintext management endpoint.
|
||||
- Stream downloads with bounded RAM use and explicit progress/error reporting.
|
||||
- Preserve serial, Wi-Fi, HTTPS, and SSH configuration across successful updates.
|
||||
- Mark new firmware pending until a post-boot health check succeeds.
|
||||
- Roll back automatically after failed boot or health confirmation.
|
||||
- Define power-loss behavior for every update stage.
|
||||
- Keep UART0 recovery and wired re-flashing documented and functional.
|
||||
- Add version/compatibility checks and reject accidental downgrade unless an explicit policy allows it.
|
||||
|
||||
OTA is complete only after successful update, interrupted-update, invalid-image, rollback, and configuration-persistence tests on hardware.
|
||||
|
||||
### Phase 11 — BLE
|
||||
|
||||
Evaluate and, if resource limits permit, add BLE as another bounded transport or provisioning path.
|
||||
|
||||
Planned work:
|
||||
|
||||
- Decide whether BLE is primarily a serial transport, a provisioning interface, or both.
|
||||
- Require authenticated pairing/bonding appropriate to the device threat model.
|
||||
- Integrate serial access through the existing broker rather than bypassing writer ownership.
|
||||
- Preserve binary transparency through explicit framing and MTU-aware partial transfers.
|
||||
- Bound connections, queues, retries, and advertising behavior.
|
||||
- Measure coexistence with 2.4 GHz Wi-Fi, HTTPS, WebSocket, SSH, USB CDC, and UART service.
|
||||
- Define bond/provisioning secret retention under the unencrypted-storage threat model; do not assume Phase 9 provides at-rest encryption.
|
||||
- Provide UART0 controls and a physical recovery path for clearing BLE state.
|
||||
|
||||
BLE remains subordinate to stable Wi-Fi and serial operation; it should be omitted if coexistence or memory costs cannot be bounded acceptably.
|
||||
|
||||
### Phase 12 — Advanced network integration
|
||||
|
||||
**Complete by explicit user validation:** “I just validated Phase 12 successfully.” Accepted baseline:
|
||||
These features are candidates, not current commitments:
|
||||
|
||||
- Dual-stack local access and DNS-SD discovery: advertise available HTTPS and SSH services using the shared `sak-<suffix>.local` hostname, with appropriate IPv4 `A` and IPv6 `AAAA` records. Preserve IPv4 access and verify IPv6 support throughout HTTPS, WebSocket and SSH. Clients choose address-family preference and fallback; DNS-SD cannot mandate IPv6 preference. Certificate-name integration and trust remain separate concerns.
|
||||
- Send the configured device hostname (`sak-<suffix>`, without `.local`) through DHCPv4 Host Name option 12 so a suitably configured DHCP/DNS server can publish the lease address in its own DNS zone. Set the STA netif hostname before DHCP starts, define how hostname changes reach subsequent DHCP exchanges, and verify the transmitted option and server-side DNS registration. DNS publication and the DNS domain remain server policy; mDNS does not provide this integration.
|
||||
- IPv6 baseline: link-local addressing and SLAAC-provided ULA/global addresses, with mDNS/DNS-SD for local discovery; no DHCPv6 requirement. Preserve link-local interface scope and keep advertisements synchronized with usable addresses and service availability. Registration of SLAAC addresses in infrastructure DNS is a separate optional feature, outside this baseline; SLAAC does not provide automatic hostname registration.
|
||||
- Validate IPv4-only, IPv6-only and dual-stack operation, including reconnects, address changes and service lifecycle changes. Discovery remains local-link by default; preserve UART0 and native USB recovery regardless of network state.
|
||||
- Additional DNS-SD service advertisement and any certificate-name integration beyond the implemented configurable STA hostname.
|
||||
- Enterprise Wi-Fi support, subject to credential-storage and certificate-validation design.
|
||||
- IPv6 behavior and diagnostics beyond the current basic support.
|
||||
- WireGuard feasibility evaluation, including RAM, CPU, licensing, key storage, routing, and recovery impact.
|
||||
|
||||
The device is not intended to become a general-purpose router. Captive-portal interception, unauthenticated DNS redirection, NAPT, and a plaintext serial listener remain out of scope unless the project requirements are explicitly revised.
|
||||
|
||||
#### Implementation and operational boundaries
|
||||
### Phase 13 — Optional filesystem-backed features
|
||||
|
||||
- The Wi-Fi owner installs the full configured hostname before STA connection and updates it after hostname edits. DHCPv4 option 12 uses `sak-<suffix>`, not `.local`. Rename does not force DHCP restart: the next DHCP exchange carries the new name, and DNS registration/cache cleanup remain server policy. Existing hostname limits and NVS formats are unchanged.
|
||||
- STA enables SLAAC and IPv6 link-local creation; either IPv4 or a preferred IPv6 address establishes `ONLINE`. **Link-local-only counts as online**, prevents IPv4-only timeout/failover, and can retire the fallback AP after the existing stability interval. This indicates local address availability, not Internet reachability or a default route. Web, CLI and OLED distinguish absent IPv4 from IPv6 availability. A diagnostic follow-up adds up to three preferred numeric IPv6 addresses to the snapshot, copied and cleared with the same observation; `wifi status` and browser Network settings print them in link-local/ULA/GUA groups; overview/OLED status retains availability flags. The settings JSON is bounded to 2304 bytes, with no extra netif calls on HTTPD.
|
||||
- HTTPS/WebSocket use IDF's existing dual-stack listener. SSH explicitly uses one dual-stack listener, retaining the existing two-session limit. Bracketed IPv6 HTTPS authorities are canonicalized and bound to the existing Host/Origin/session checks; scoped IPv6 literal URLs are rejected. Prefer the `.local` hostname for link-local browser access, subject to client resolver support. No certificate regeneration, new trust mechanism, or automatic IPv6 preference is introduced.
|
||||
- STA-only mDNS advertises `_https._tcp:443` and `_ssh._tcp:22` according to listener availability, without TXT metadata. Record and address-family changes converge through the existing Wi-Fi owner, including offline reconciliation; service setters do no component work. The responder is not restarted for ordinary changes. Initialization failure remains latched/nonfatal.
|
||||
- mDNS 1.12.0 exposes no readiness acknowledgement and can silently drop queued family actions. Absent families are disabled on each one-second owner pass; available families are re-enabled/reprobed on a 30-second repair cadence. Upstream synchronous calls can delay this cadence. There can be a transient stale/zero-A response window after IPv4 loss before disable is processed. AAAA records follow upstream **valid-address** semantics, including deprecated-but-still-valid addresses, not preferred-only filtering. Client caches expire independently.
|
||||
- `cmake/mdns_membership.*` applies one version/hash-guarded, build-local source overlay to mDNS 1.12.0: balance per-family multicast leaves and unwind joins after PCB creation failure. Managed sources and dependency versions remain unchanged. An upstream mismatch fails configuration for deliberate review; see `tests/mdns_membership/README.md` for maintenance/removal and regression evidence. This is not the abandoned Phase 9 patch set.
|
||||
- `sdkconfig.defaults` explicitly enables IPv4/IPv6 and increases mDNS service capacity from one to two. Existing saved configurations override defaults: verify `CONFIG_LWIP_IPV4=y`, `CONFIG_LWIP_IPV6=y`, `CONFIG_MDNS_MAX_SERVICES=2` and STA-only predefined mDNS interfaces before building. The local N16R8 saved configuration was updated accordingly. No new task, transport socket, broker slot, serial buffer, partition, filesystem or generated web asset was added.
|
||||
- Existing diagnostic limits remain: hostname ping selects the resolver's first usable result (not Happy Eyeballs), scoped link-local ping is not newly supported, and traceroute remains IPv4-only. DHCPv6, infrastructure DNS registration of SLAAC addresses, and new IPv6 resolver provisioning are outside this baseline.
|
||||
The `storage` partition is reserved but not currently mounted. Possible uses must be justified individually:
|
||||
|
||||
#### Validation evidence and acceptance
|
||||
- LittleFS-backed web assets to decouple large static files from the application image.
|
||||
- Bounded diagnostic logs with explicit retention and secret-redaction rules.
|
||||
- Import/export of non-secret configuration.
|
||||
- Optional user files needed by later administration features.
|
||||
|
||||
`pio run` passed on PlatformIO 6.12.0 / ESP-IDF 5.5.0 with the guarded mDNS source compiled: **94,396 B linked RAM / 1,853,025 B flash**, +176 B RAM / +5,380 B flash versus the recorded Phase 10 build. These are static link sizes, not runtime heap or stack headroom. Initial target compilation rejected lock-free bool atomics; the implementation now uses short portMUX sections. Existing SDK Kconfig notes remain.
|
||||
|
||||
Host checks passed: Wi-Fi/address/hostname reconciliation, mDNS lifecycle/family repair, actual patched multicast-membership functions and CMake guards, SSH dual-stack/management/runtime/security, HTTPS authority parser (689 cases on host and actual installed lwIP parser), cookie authorization variants, Network settings, browser UI/CSP (171 groups), HTTPS lifecycle (45 groups and eight status projections), firmware upload (88 cases plus SDK contract), broker diagnostics, and session-store/serial integration. Network-settings host fakes were updated after the new netif dependency exposed a compilation failure; the rerun passed. These are not packet-level or hardware evidence.
|
||||
|
||||
Follow-up after the user's live status report: both preferred-address flags were `yes`, while plain client `ping` selected IPv4. Source verification confirms the flags require actual preferred addresses; this is not evidence of failed SLAAC or verified IPv6 reachability. `wifi status` now prints the addresses for diagnosis. Latest follow-up `pio run` passed: **94,444 B linked RAM / 1,853,685 B flash** (+48 / +660 versus the initial Phase 12 build). Browser Admin console now permits canonical Wi-Fi commands, including disruptive edits and hidden prompts, as explicitly requested. Focused policy/browser Wi-Fi prompt, address snapshot/rendering, Network/cookie and UI regressions passed. The broader console-boundary suite passes its console/certificate stages but has a pre-existing SSH-adapter fixture compilation failure for missing `web_firmware_update_reserve_reboot`; no production change was made to hide it.
|
||||
|
||||
Latest address-display build: `pio run` **94,444 B linked RAM / 1,854,485 B flash**; Network API/owner checks and **174 browser groups plus CSP** passed. These remain build/host results, separate from the user's device evidence below.
|
||||
|
||||
##### User-reported device acceptance
|
||||
|
||||
User explicitly validated Phase 12 after confirming numeric IPv6 ping/HTTPS, Avahi AAAA resolution and normal IPv6 hostname access following the Fedora `authselect` mDNS correction. The earlier hostname failure was a client resolver configuration issue, not missing device IPv6 addresses. The acceptance capture adds:
|
||||
|
||||
- **Fresh boot:** HTTPS, SSH and mDNS running without reported startup failures; zero SSH sessions, serial stopped, no broker clients, USB attached but not host-open. The configured UART profile is **230400 baud, 8N1, RTS/CTS, DTR active, RTS threshold 96**.
|
||||
- **Full client mix:** two authenticated public-key SSH sessions with IPv6 ULA peers (one serial observer, one admin console); two serial WebSockets (one writer, one observer); one active browser admin WebSocket; native USB host-open as an observer. Four broker clients, exactly one writer, all reported pending/event counts zero. Serial running with RX-available/TX-pending zero, CTS asserted and valid RS-232 voltage at observation time.
|
||||
- SSH: two successful handshakes, zero handshake/authentication failures or timeouts, zero stream I/O failures and zero session revocations. Stream counters **RX 68 / accepted 65 / rejected 3 / TX 161,209 bytes**; two broker writer revocations were reported. The rejected bytes are retained as evidence, not silently described as zero loss or assigned an unverified cause.
|
||||
- Web serial: two connections, **27 accepted RX frames/bytes**, zero rejected RX frames/bytes; **782 binary TX frames / 318,230 bytes**, 11 control frames / 948 bytes. Send/queue/protocol/close failure counters zero. Two writer requests were denied; the final broker snapshot still shows one writer. Browser admin: one connection, **106 RX / 3,721 TX bytes**, zero send/queue/protocol/authorization/backpressure failures. No response errors reported.
|
||||
- HTTPS and SSH both report running, not transitioning, `ESP_OK`; mDNS reports expected announcement and `ESP_OK`. Expected announcement alone is not packet-level service-record proof; the earlier successful Avahi lookup separately establishes the reported AAAA lookup.
|
||||
|
||||
All heap figures are bytes, copied from the user's observations:
|
||||
|
||||
| Observation | Heap capability | Free | Lifetime minimum-free | Largest block |
|
||||
|---|---|---:|---:|---:|
|
||||
| Fresh boot | Internal 8-bit | 65,880 | 64,912 | 31,744 |
|
||||
| Fresh boot | Internal DMA | 58,124 | 57,156 | 31,744 |
|
||||
| Fresh boot | External PSRAM | 8,196,732 | 8,187,980 | 8,126,464 |
|
||||
| Full client mix | Internal 8-bit | 35,408 | 8,528 | 22,528 |
|
||||
| Full client mix | Internal DMA | 27,652 | 772 | 22,528 |
|
||||
| Full client mix | External PSRAM | 8,111,952 | 8,072,612 | 7,995,392 |
|
||||
|
||||
SSH owner stack: **20,480 B** configured, **18,476 B minimum-free** at fresh-boot observation and **16,284 B minimum-free** under the full mix.
|
||||
|
||||
**Evidence limits:** minimum-free is the conservative sum of matching heap regions' lifetime minima, not a simultaneous free-space measurement or guaranteed allocation reserve. Internal 8-bit and DMA capabilities overlap and must not be added as independent pools. The **772 B DMA lifetime minimum remains a headroom watch item**, not an observed allocation failure or acceptance blocker. Boot output contains two TLS `-0x004C` receive errors and unauthenticated-request failures; the capture does not establish their cause or connect them to memory exhaustion. Command snapshots are non-atomic; differing cumulative request/authentication counts must not be treated as one aligned measurement interval. No UART overflow/per-observer drop counters, soak duration, byte-for-byte capture integrity, exhaustive fault injection, DHCP packet capture/server-zone update, or every address-family/service transition is established by this excerpt. User acceptance is recorded without inventing those passes.
|
||||
|
||||
##### Reusable regression guidance
|
||||
|
||||
The following procedures are retained for future regression testing, **not outstanding acceptance blockers or a claim that every item was executed**. Keep UART0 and USB recovery available:
|
||||
|
||||
1. On IPv4, IPv6-only and dual-stack STA networks, exercise HTTPS login, serial/admin WebSockets, SSH user/admin access and one-writer/observer isolation. Test hostname access plus IPv4 and unscoped ULA/GUA IPv6 literals; verify SSH host keys and HTTPS identity rather than trusting discovery.
|
||||
2. Capture DHCP DISCOVER/REQUEST option 12 at boot, rename/renew and reconnect, including a maximum-length suffix. With a configured DHCP/DNS server, verify the resulting zone entry and its server-controlled update/removal behavior.
|
||||
3. Browse `_https._tcp` and `_ssh._tcp` and inspect A/AAAA/SRV answers over both families. Stop/restart each service; rename; drop/reacquire DHCP while IPv6 survives; change/deprecate/expire RA prefixes; disconnect/reconnect. Confirm eventual withdrawal/restoration, valid-address AAAA semantics and no persistent zero-A response. Exercise repeated family transitions and, where practical, missed-event/action repair.
|
||||
4. Check link-local-only readiness and the fallback-AP transition explicitly. Confirm the client preserves interface scope; do not infer browser link-local success from `ONLINE` alone. Test multicast filtering/AP isolation separately from firmware address readiness.
|
||||
5. Repeat the previously accepted 230400-baud mixed USB/WebSocket/SSH workload with discovery and address changes. Capture current/minimum internal, DMA and PSRAM availability, task stack margins, UART overflow and per-client drop counters. No new runtime memory reserve or maximum-baud performance guarantee is claimed.
|
||||
|
||||
Phase 12 is complete by the explicit user validation above. This acceptance update changes documentation only; the agent did not run a new build, test, upload, erase or device operation.
|
||||
Before enabling storage, define corruption recovery, wear limits, quotas, atomic update behavior, compatibility across OTA slots, and whether encryption is required. Serial operation and recovery must not depend on a mountable filesystem.
|
||||
|
||||
## Cross-phase release gates
|
||||
|
||||
Every phase should satisfy the following before being marked complete, with any user-waived check explicitly recorded in that phase rather than reported as passed:
|
||||
Every phase should satisfy the following before being marked complete:
|
||||
|
||||
1. A clean release build for the ESP32-S3 N16R8 target.
|
||||
2. No new compiler or project diagnostics attributable to the change.
|
||||
@@ -382,10 +306,10 @@ Every phase should satisfy the following before being marked complete, with any
|
||||
|
||||
The following are not implemented merely because flash partitions or library support exist:
|
||||
|
||||
- NVS, flash, or PSRAM encryption.
|
||||
- Secure boot or production eFuse provisioning.
|
||||
- Automatic OTA downloads, image signing, post-boot health confirmation and automatic rollback. The simple admin upload is complete in Phase 10; these advanced update features remain deferred.
|
||||
- Core-dump collection or secret-safe core-dump processing.
|
||||
- Secure boot and encrypted NVS are excluded by explicit user preference, not scheduled future enablement.
|
||||
- Flash/PSRAM encryption and production eFuse provisioning are outside the current scope, with no enablement commitment.
|
||||
- OTA download, image confirmation, or rollback policy.
|
||||
- Core-dump collection or export: the supported Phase 9A baseline disables new dumps; existing partition bytes are not cleared.
|
||||
- Filesystem mounting.
|
||||
|
||||
- SFTP, SCP, SSH `exec`, forwarding, or subsystems.
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
# Security hardening — Phase 9
|
||||
|
||||
**Status: in progress.** Phase 8 is complete at the accepted 8D.22 scope. **9A crash/debug policy, 9B SSH admission/credential handling and 9C library cleanup/protocol policy** are implemented with host/build validation. At the user's request, hardware validation is deferred to **Phase 9 as a whole**, not an approval gate between implementation slices. **9D maintenance/lifecycle is in progress, with unresolved advisory and distribution/source/notice questions. Phase 9 is not complete or production-ready.** This document records policy and procedures, not unrun passes or production certification.
|
||||
|
||||
## Current migration evidence — 2026-09-18
|
||||
|
||||
Root `platformio.ini` now pins **PlatformIO espressif32 6.13.0 / framework-espidf 3.50503.0 (IDF 5.5.3)** and both Xtensa and RISC-V toolchains at **14.2.0+20251107**. These are exact version pins, not enforcement of archive hashes or a complete immutable build closure. The [candidate integration record](idf_candidate_integration.md) distinguishes the verified archive identities from the root configuration.
|
||||
|
||||
Supplied parent evidence: a **fresh isolated application build PASS** in `.pio/idf-candidate-5.5.3/app-validated`, with **95,552 B linked RAM / 1,749,493 B flash**, versus historical **94,340 / 1,768,901 B**: **+1,212 B RAM / −19,408 B flash**. The default root `pio run` timed out after **200 seconds during installation, before compilation**; it is **not a normal root build PASS**. Final explicit-candidate Phase 9 validation with interop and web performance **PASS 24/24**; see [command and snapshot evidence](phase9_validation.md#current-candidate-execution--2026-09-18). Source equality covered **3,237 files**, pre/post SHA-256 **`3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031`**.
|
||||
|
||||
All **nine generated C sources plus one forced header** are validated as actual compilation inputs. The new WS correction makes five fixed-header `sizeof` comparisons signed; **982 behavioral cases / 10 mutation checks** cover generated WS receive behavior. The stale web-cookie IDF 5.5.0 fixture was corrected. [Rebase review](idf_553_rebase_review.md) retains old findings as history, not current blockers, and records all packaged Wi-Fi/PHY/coexistence/supplicant files matching the locked framework archive.
|
||||
|
||||
The fix-bearing vendor Wi-Fi bundle is integrated in the validated candidate, **not radio-hardware vulnerability closure**. PMF/WPA3 is unchanged. Whole-phase target/radio/resource/recovery acceptance, complete dependency closure and release/source/legal gates remain pending; **Phase 9 is not complete or production-ready**. These are supplied parent results, not builds or hardware tests rerun by this documentation update.
|
||||
|
||||
## Scope and threat model
|
||||
|
||||
Reduce network abuse, accidental diagnostic disclosure and unnecessary secret retention while preserving one UART1 broker writer, isolated observers and binary transparency. UART0 remains trusted physical administration/recovery; native USB remains network-independent UART1 access, not an admin console. Whole-device reboot interrupts every transport.
|
||||
|
||||
Secure boot and encrypted NVS are explicitly excluded by user preference. Physical flash/RAM extraction and firmware replacement remain outside the threat model even after Phase 9. There is no commitment to flash/PSRAM encryption, eFuse provisioning or physical JTAG restrictions. Software debugger-aware configuration checks do not disable physical debug access by fuse.
|
||||
|
||||
9A does not change partitions, at-rest encryption, dependencies or generated assets, and requires no upload or erase as part of host/build validation. The unused `nvs_key` and `coredump` partitions remain for layout compatibility. Disabling new dumps does not clear old coredump contents. Logical NVS replacement, reset and credential rotation are not secure erasure; historical plaintext copies can remain.
|
||||
|
||||
## 9A supported build baseline
|
||||
|
||||
`src/security_build_policy.c` enforces the following at compile time, with explicit settings in `sdkconfig.defaults`:
|
||||
|
||||
- Require `CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y` and `CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y`.
|
||||
- Reject enabled core-dump support, flash or UART dump destinations.
|
||||
- Reject panic print/reboot, print/halt and GDBstub modes, plus runtime GDBstub.
|
||||
- Reject `CONFIG_ESP_DEBUG_OCDAWARE` and `CONFIG_FREERTOS_DEBUG_OCDAWARE` when enabled.
|
||||
|
||||
The host matrix in `tests/security_build_policy/run.py` compiles the actual guard against synthetic configurations; it is not merely a text check of defaults. Existing generated SDK configuration must also satisfy the guard: defaults alone are not evidence of the effective build configuration.
|
||||
|
||||
Silent panic reboot deliberately sacrifices panic text, register dumps and backtraces for reduced crash disclosure. Reset-reason/boot information and ordinary status/logging can remain; neither silence across the full boot sequence nor general log redaction is guaranteed. A monitor exception decoder cannot reconstruct a backtrace that was never emitted.
|
||||
|
||||
## 9B SSH admission and credential handling
|
||||
|
||||
### Boot-lifetime admission budgets
|
||||
|
||||
`src/ssh_auth_policy.{c,h}` owns three independent, fixed-size token buckets. Only the SSH owner task accesses the shared 72-byte policy; no allocation, per-peer/account map, timer task or sleep is added.
|
||||
|
||||
| Admission class | Initial/maximum burst | Refill |
|
||||
|---|---:|---|
|
||||
| New SSH handshake | 6 | One token per 10 seconds |
|
||||
| Password or signed-key authentication request | 6 | One token per 10 seconds |
|
||||
| Unsigned public-key probe | 12 | One token per 5 seconds |
|
||||
|
||||
These are **burst-plus-refill limits**, not six/twelve requests in every rolling minute. All peers/accounts and both slots share each class. Idle refill stops at capacity; denials do not extend the refill deadline. Reconnects, service stop/start, identity rotation and `ssh clear-counters` do not replenish the pools. Reboot starts a new policy lifetime. Timestamp regression fails closed. No persistent account lockout or NVS write is introduced.
|
||||
|
||||
- A handshake token is taken after finding capacity but before `wolfSSH_new()`/handshake work. Full-capacity rejection takes no token; later allocation/IO failure does not refund it.
|
||||
- Password/signed-key admission precedes database verification/authorization and ordinary key signature work. Success, invalid credentials, backend errors and rejected password-change requests do not refund admission. Unsigned probes use their own pool and cannot authenticate.
|
||||
- Exhaustion shuts down/rejects the new or authenticating connection without waiting inside the owner task. Already-authenticated streams do not pass through this admission gate. The existing three-counted-attempt failure closure, two-slot bound and 15-second handshake deadline remain.
|
||||
- **Availability tradeoff:** a client can consume the handshake burst by opening/abandoning connections and race legitimate clients for each refill. Global verification/probe pools can also starve other users. This bounds admitted work, not fair access or immunity to denial of service. TCP accept/rejection work and library parsing still occur; target latency under abuse is not yet measured. Restrict network access, stop the offending traffic and allow natural refill rather than repeatedly reconnecting/restarting. UART0/USB remain independent of these pools; HTTPS keeps its separate policy.
|
||||
|
||||
### Callback and library contract
|
||||
|
||||
`src/ssh_transport.c` requires wolfSSH 1.4.20, certificates disabled and `none` authentication disabled at compile time. The reviewed parser calls ordinary-key authorization before signature verification; rejected authorizations and unsigned probes have no result callback. Password results are completed within the password callback. An explicit pending-result marker fences signed-key completion; duplicate/unexpected/closing-session results cannot promote a principal or count another completed attempt. Principal currentness is still checked at successful signature completion and route admission.
|
||||
|
||||
Advertising only password/publickey is not a dispatch filter in this wolfSSH version. An explicit rejecting keyboard-interactive prompt callback and per-slot context prevent its unregistered-callback path; it creates/sends no prompts and closes the connection. The advertised methods remain password/publickey. This does not certify every malformed-packet path in the library.
|
||||
|
||||
`tests/wolfssh_auth_contract/run.py` checks the reviewed `internal.c` SHA-256 and version, preprocesses the actual build's feature profile, and executes extracted vendor parser/send functions with narrow crypto/IO doubles. A same-version source change requires re-audit, not blindly replacing the hash. It does not replace real-client/cryptographic integration testing. The positive `SendChannelData()` return contract means the caller's accepted prefix has been copied, including its consumed-data WANT_WRITE case; it is not peer acknowledgement.
|
||||
|
||||
### Counters and secret lifetime
|
||||
|
||||
`ssh counters` adds aggregate-only diagnostics:
|
||||
|
||||
- `handshakes` / `handshake-throttled`: admitted handshake work / rate-denied connections, separate from capacity failures.
|
||||
- `verifications` / `verification-throttled`: admitted password/signed-key requests / rate-denied requests. Admission does not imply the verifier ran or completed.
|
||||
- `probes` / `probe-throttled`: admitted/denied unsigned-key lookups, not completed credential attempts.
|
||||
- `attempt-limit-closes`, `backend-errors`, `method-rejects`: three-attempt closures, database auth/authorization/currentness errors, and rejected callback-level methods (including keyboard). These are not counts of every malformed SSH packet.
|
||||
|
||||
Existing `auth-attempts`/`auth-failures` remain completed counted outcomes; rejected password changes count, unsigned probes and rate-denied requests do not. Signed-key results finalize once after authorized work. These admission/auth counters saturate at `UINT64_MAX`, contain no submitted credentials/identities, and may be cleared independently of enforcement state.
|
||||
|
||||
The transport now wipes consumed admin RX bytes, positively accepted admin TX bytes, and the full retired slot while retaining its generation. Partial/retry paths preserve pending bytes. Serial-route hot-path behavior is unchanged. This shortens application plaintext lifetime; it is not a claim that wolfSSH/wolfSSL/mbedTLS, stack or PSRAM copies are all erased.
|
||||
|
||||
Hidden UART0 and shared remote-console prompts now reject overflow or unsupported bytes on submission with a wiped output buffer and `ESP_ERR_INVALID_SIZE`, rather than accepting a truncated/normalized prefix. The failure remains sticky after Backspace/Delete. Printable ASCII, CR/LF submission, Backspace/Delete and Ctrl-C retain their defined roles; visible command-line editing is unchanged. Existing callers prevent a rejected password or confirmation from reaching persistence. For pasted passwords, exceeding 64 characters or including unsupported bytes requires a fresh attempt; the password policy itself is unchanged.
|
||||
|
||||
## 9C library cleanup and protocol policy
|
||||
|
||||
[Library review and maintenance contract](security_library_review.md) records the scoped audit, corrected paths, existing cleanup and limits. This is not exhaustive library certification or a dependency security-release review.
|
||||
|
||||
### Reproducible source corrections
|
||||
|
||||
`tools/security_overrides.py` verifies full original-file SHA-256 values and ESP-IDF 5.5.3, applies exact-once edits, and generates nine corrected C sources plus one header under the build directory (the original four 9C sources, three IDF advisory sources, the additional wolfSSH `ssh.c`/`internal.h` ordering inputs, and the IDF 5.5.3 WS signed-receive correction). `cmake/security_overrides.cmake`, included after `project()`, replaces exactly the corresponding sources in existing IDF/component targets, retaining compilation properties. Installed SDK/managed sources and their notices remain unchanged. Missing, changed or ambiguous sources fail configuration; there is no unpatched fallback. Do not edit derived files or repin a hash merely to make an upgrade build.
|
||||
|
||||
- **HTTPS:** delete TLS on post-handshake transport-allocation failure; fully destroy retained TLS configuration on failed HTTPD start; wipe the copied raw private key before free. Failed stop still retains live ownership.
|
||||
- **HTTPD parser:** allocate/copy/wipe/free scratch on resize, preserve the old pointer on allocation failure, wipe final scratch, and handle the null initial parser pointer without undefined subtraction. Pending/unread bytes retain their existing behavior.
|
||||
- **wolfSSH password parser:** bound both password lengths against the actual packet before application callbacks, reject malformed change-password fields without calling authentication, and wipe the bounded method-specific payload suffix before failure responses. Username/service/method prefixes remain intact. The current project callbacks are synchronous; library `WS_AUTH_PENDING` retains the payload for retry and is not claimed wiped.
|
||||
- **ESP-TLS server configuration:** enforce the static-lifetime TLS list below before handshake setup; client defaults and global cryptographic primitives remain unchanged. IDF dynamic TLS buffers are rejected because their cleanup bypasses the reviewed upstream record-buffer wipe.
|
||||
|
||||
The new `src/ssh_memory.{c,h}` wolfSSL/wolfCrypt allocation hooks wipe the full owned usable allocation before release, including library import-failure and dynamic packet-buffer copies. They require the reviewed unpoisoned IDF 5.5.3 heap configuration; poisoning modes fail compilation rather than risking canary writes. No allocation header is added. Shrink retains capacity and wipes the tail; growth allocates/copies before wiping/freeing the old block, preserving it on allocation failure. PSRAM preference/internal fallback is unchanged. **Growth and HTTPD scratch resizing temporarily need both blocks; lower linked size is not evidence of safe runtime headroom.** Live inline buffers, in-place compaction tails, stack spills and every crypto intermediate are not comprehensively covered.
|
||||
|
||||
### Explicit network protocol policy
|
||||
|
||||
| Setting | Allowed values, in preference order |
|
||||
|---|---|
|
||||
| HTTPS version | TLS 1.2 only; server renegotiation disabled |
|
||||
| HTTPS suites | `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`, `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384` |
|
||||
| SSH KEX | `curve25519-sha256`, `ecdh-sha2-nistp256` |
|
||||
| SSH host key | `ecdsa-sha2-nistp256` |
|
||||
| SSH ciphers, both directions | `aes128-gcm@openssh.com`, `aes256-gcm@openssh.com` |
|
||||
| SSH MAC advertisement | `hmac-sha2-256` (GCM supplies packet authentication) |
|
||||
| SSH user-key policy | `ssh-ed25519`, `ecdsa-sha2-nistp256`; database authorization remains authoritative; no EXT_INFO/`server-sig-algs` advertisement |
|
||||
|
||||
`src/ssh_protocol_policy.c` uses permanent strings and checks every setter; any failure destroys the unpublished context without fallback. Tests verify the actual compiler's available algorithms and generated KEXINIT lists, not merely setter success. The server-only TLS correction avoids breaking future outbound HTTPS clients by globally removing RSA-certificate suites.
|
||||
|
||||
**Compatibility:** TLS CBC/CCM/ARIA-only and SSH CBC/CTR-only clients no longer connect; removed KEX-only clients also fail. Bounded OpenSSH host interoperability is recorded below; intended-client compatibility without EXT_INFO and rekey/cleanup on hardware remain target gates. RSA-SHA2 discovery is not claimed. Existing TLS/SSH identity and user-key storage need no rotation or migration. Password/KDF, certificate validity/trust and browser-header policy were reviewed and retained with documented limits; no blind KDF-cost increase or HSTS policy was introduced.
|
||||
|
||||
### Web admission and shorter plaintext lifetimes
|
||||
|
||||
A non-consuming quota/epoch check now runs after valid challenge consumption but before body receive. An already-exhausted verification budget returns 429/`Retry-After` without receiving/parsing credentials or calling the verifier; unread bodies still cause connection closure, not draining. The authoritative reservation remains after parsing. Raw JSON is wiped before verification, parsed credentials afterward, and both before error-response sending. Header strings remain live through synchronous serialization.
|
||||
|
||||
The existing global five-verifications/60-second fixed window is unchanged: malformed requests do not charge it, counter clear does not replenish it, and web service restart does. This differs deliberately from SSH's boot-lifetime buckets. The first boot-minute window remains anchored at uptime zero. Challenge monopolization, global-budget starvation and malformed-body work while budget is available are not solved by this early check.
|
||||
|
||||
## 9D maintenance and lifecycle — in progress
|
||||
|
||||
- [Security operations](security_operations.md) supplies source-checked provisioning, explicit-save, account/key/identity rotation, recovery, reconstruction and approved erase/decommissioning procedures. There is no implemented configuration backup/export or private-identity restore workflow. Commands and checklists are not device execution evidence; the user will validate Phase 9 as a whole.
|
||||
- [wolfSSL/wolfSSH review and implementation addendum](wolf_security_review.md): the **CVE-2025-12888** Xtensa mitigation now selects `CURVE25519_SMALL` and `ED25519_SMALL` consistently for wolfSSL and consumers. The PUBLIC forced-include resolved-settings guard rejects missing small implementations, X25519 blinding (incompatible with small in this pinned version), and unreviewed Curve448/Ed448 enablement. The existing generated wolfSSH override now bounds IGNORE/service strings, rejects zero-capacity string output and channel-window overflow, corrects ECC/Ed25519 key/signature labels, and enforces exact signature framing (ECC nested r/s bounds plus inner/outer consumption; Ed25519 outer consumption). These are PR892/881/880 subsets plus local framing corrections, not full backports; password wiping/async retention remain unchanged. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` now enable P-256 import validation and X25519 all-zero-result rejection, with effective production flags confirmed and fail-closed backend guards. See [key-validation evidence](ssh_key_validation_review.md) and [parser scope/limits](../tests/wolfssh_parser_contract/README.md). The restricted existing-profile correction for **CVE-2025-14942** is implemented as described below. The [finite remaining SSH review](ssh_parser_remaining_review.md) is complete: bounded exact CHANNEL_FAILURE recipient parsing (fatal policy retained), exact `ssh-userauth` service validation, and exact length/byte dispatch for all nine channel-request names are implemented. Unknown-request/trailing-payload behavior is preserved. PR899 client key skips are unchanged and blocked by current server role/ordering; PR918/919 forwarding is disabled. The inspected generic signature caller trace found no attacker-selected short-digest/OID path; generic APIs remain unpatched. These are profile-specific dispositions, not exhaustive parser/library safety; revisit on caller, feature, KEX or source changes. No exploit or whole-library clearance is demonstrated; added validation CPU/allocation cost and target interoperability remain unmeasured.
|
||||
- [Focused IDF review and implementation addendum](idf_security_review.md): pinned backports now implement DHCP option bounds (**CVE-2026-45160**), TLS 1.2 EMS failure return (**CVE-2026-50581**) and X.509 OID allocation-failure handling (**CVE-2026-34874**). Explicit nested-target validation places the mbedTLS edits on `mbedtls`/`mbedx509`, retaining source properties and exactly-one-source checks. WS negotiation **CVE-2026-45541** and ASN.1 named-data **CVE-2025-48965** remain unpatched with the review's qualified applicability, not blanket closure. The [finite IDF applicability completion](idf_security_review.md#finite-applicability-completion--2026-09-16) dispositions all six named findings: ECDH small-output, zero-length ECC PK parse, basicConstraints, server NewSessionTicket and stale ASN.1 length are not current paths/configurations for their documented reasons; optimized ECC reduction is active but its privileged-local/physical side-channel attacker model is excluded. No new current-path correction was established. That earlier review screened only the first advisory-index page. The subsequent [finite dependency coverage](dependency_advisory_coverage.md) accounts for all **22 IDF entries across three pages**, plus named managed-component and browser publication channels. The declared dated search is complete, not exhaustive advisory clearance; unpatched primitives, inaccessible full-release databases and fresh findings remain.
|
||||
- [Dependency license inventory](dependency_licenses.md) is a bounded engineering inventory, not legal or distribution clearance. All **nine generated C sources plus one header** carry prominent modification notices: the baseline **2026-09-15** notice plus **2026-09-16** ordering/provenance notices on wolfSSH outputs, with upstream notices retained, including both mbedTLS dual-license headers. That narrow finding is resolved. Radio-blob corresponding-source/exception questions, actual firmware/device/browser notice delivery, preferred-source packaging, wolfSSH package-license discrepancy and manual bitmap derivation, the distinct mockup Wi-Fi path and recipient license delivery remain open; [exact retained SVG provenance](icon_provenance.md) is resolved. Notice assembly does not prove recipient delivery. [Offline notice assembly](release_packaging.md) is implemented by [tools/release_notices.py](../tools/release_notices.py): 75 mandatory hash/size-pinned inputs (nine IDF/toolchain notice entries semantically rebased for the candidate; the other 66 unchanged), deterministic bounded outputs, fail-closed preflight and no overwrite/fetch/build/device access. Supplied agent evidence: **36 notice fixture tests PASS; two actual bundles verified deterministic, each 77 files / 4,433,930 bytes**. This includes retained SVGs and [exact icon provenance](icon_provenance.md), not corresponding-source delivery or legal clearance. These are agent measurements, not a parent rebuild or reassembly in this documentation turn.
|
||||
- **Restricted ordering correction implemented, not a full upstream backport or sign-off.** The [ordering review](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates) and [provenance/prerequisite disposition](../tools/wolfssh_order/README.md) document the audited PR793/819/840/855/921 subsets plus local gates. Existing X25519/P-256 KEX only; independent SELF/PEER state, exact expected replies and authentication-phase checks cover both roles. Queued NEWKEYS survives WANT_WRITE without duplication. **EXT_INFO is deliberately disabled; no `server-sig-algs` is sent**, and `extInfoSent` stays zero. CMake applies the generated ABI header BEFORE PUBLIC and via a PUBLIC forced include; joined `-include/path` flags fix PlatformIO sorting/deduplication for ordering and crypto guards. Review's misplaced EOF guard is corrected before channel mutation; verification found no scoped blocker. Target cleanup during rekey and no-EXT_INFO client compatibility remain pending.
|
||||
- No dependency versions were upgraded. Beyond the implemented backports above, proposed upgrades/backports remain candidates, not approved compatible versions. Re-audit coherent source/header changes, effective compile policy, exact-hash overrides and callback/parser contracts, then obtain host/build and whole-phase target evidence. 9A–9C passes below are historical scoped evidence, not closure of these newly recorded findings.
|
||||
|
||||
Secure boot and encrypted NVS remain excluded. No runbook, advisory report or license inventory establishes production readiness or authorizes a destructive device operation.
|
||||
|
||||
## Operational profiles
|
||||
|
||||
These are handling and validation profiles of the **same supported build baseline**, not separate PlatformIO environments or selectable security overrides.
|
||||
|
||||
| Profile | Operational rules |
|
||||
|---|---|
|
||||
| Development | Keep the guard enabled; use synthetic credentials and controlled serial payloads for fault investigation. Keep UART0 recovery available. Review captures before sharing. |
|
||||
| Test | Use an isolated, expendable target and synthetic secrets; record exact source/configuration, host/build results and device observations. Exercise crashes and recovery without exporting raw memory. |
|
||||
| Production | Use the same guard, restrict physical/network access, verify device identity through trusted UART0, and apply reviewed provisioning/rotation/recovery procedures. Readiness remains pending Phase 9 review and target evidence. |
|
||||
|
||||
If richer crash debugging is essential, use an isolated synthetic-secret build outside this supported baseline. It requires explicit reviewed changes to the source policy and applicable configuration; no bypass flag is provided. Do not use real credentials or deploy that build as production firmware. Restore and revalidate the supported policy before release.
|
||||
|
||||
Raw flash, RAM and dumps can contain Wi-Fi passwords, private keys, password verifiers, session material and serial payloads. Treat them as secret-bearing and **do not export them as routine diagnostics**. Prefer bounded status/counter observations and reviewed synthetic-secret reproductions. Restrict any exceptional artifacts and define retention/deletion before collecting them; deletion is not a secure-erase guarantee.
|
||||
|
||||
## Validation gates
|
||||
|
||||
### Ordering host/build evidence — 2026-09-16
|
||||
|
||||
Supplied parent `pio run` **PASS**: **94,340 B linked RAM / 1,768,901 B flash**, unchanged RAM / **+200 B flash** versus 1,768,701 B. **Supplied final parent results: all seven suites PASS** — ordering `--interop` (8,028 checks, seven rejected mutations, 12 sessions with exact 256 KiB echo each and clean channel close plus transport EOF), SDK overrides `--build-dir .pio/build/esp32-s3-devkitc-1-n16r8`, auth (135 cases), protocol, strict crypto, notices (30), and parser (3,258 cases in each of two stack modes plus channel profiles; 11 + 18 + 2 rejected mutations). Interop required unsandboxed approval solely for local AF_UNIX sockets; no remote network or device operation occurred. This documentation update did not rerun firmware or host suites.
|
||||
|
||||
Supplied agent ordering tests passed **8,028 checks and seven rejected mutations**, including the corrected EOF guard and real shutdown/exit-status rekey fences. The installed PlatformIO/SCons adapter regression validates joined forced-header flags with a real Xtensa consumer and rejects a split-option mutation. The [test README and code](../tests/wolfssh_order_contract/README.md) describe full generated translation units, real wolfCrypt, message-ID matrices, fragmented writes and both roles/rekey directions.
|
||||
|
||||
Initial host interoperability failed a harness close race: early `INTEROP PASS` preceded OpenSSH `Broken pipe` and was not a pass. The harness now waits for peer channel close and transport EOF, passes a local socket descriptor to OpenSSH, independently owns/reaps the server, and checks both process exits. Final agent `python3 tests/wolfssh_order_contract/run.py --interop --interop-repeat 3` evidence: **36/36 sessions**, each exact **256 KiB** binary echo, **ten** key exchanges in client-rekey cases or **two** in fragmented server-rekey cases, clean exits and no EXT_INFO. Coverage uses OpenSSH 10.2p1, both KEX algorithms, Ed25519/P-256/password authentication and AES128-GCM. It is not general library shutdown, arbitrary-client or target evidence. Whole-phase gates remain pending.
|
||||
|
||||
The [source-authoritative parser report](ssh_parser_remaining_review.md#validation-and-remaining-handoff) and [test contract](../tests/wolfssh_parser_contract/README.md) split the channel matrix from the 3,258 base cases: **2,737 per stack mode** for TERM-only, TERM+SHELL and TERM+SHELL+AGENT; **2,735 per stack mode** for no-terminal and SHELL-only. All five profiles run both modes; alternate features are host fixtures, not firmware enablement. Mutations are **11 base + 18 name/length + 2 real application admission gates**. Parent ordering adds **12 OpenSSH sessions**, each exact **256 KiB**, rekey and clean client/server exit; SDK tests checked actual build registration. These integrated parent results supersede the parser report's earlier stale-build handoff, without changing its historical execution record. Independent review reported no actionable defects within the scoped parser/bundle review, not a Phase 9 approval.
|
||||
|
||||
### Mitigation host/build evidence — 2026-09-15
|
||||
|
||||
Supplied parent results (not rerun for this documentation update): `pio run` **PASS**, **94,340 B linked RAM / 1,768,949 B flash**. RAM is unchanged and flash is **1,732 B larger** than the preceding 1,767,217 B mitigation build. Linked size is not runtime headroom or timing evidence.
|
||||
|
||||
All five parent commands passed (crypto policy in strict mode, without candidate injection):
|
||||
|
||||
```sh
|
||||
CCACHE_DISABLE=1 python3 tests/sdk_security_overrides/run.py --build-dir .pio/build/esp32-s3-devkitc-1-n16r8
|
||||
CCACHE_DISABLE=1 python3 tests/wolf_crypto_policy/run.py
|
||||
CCACHE_DISABLE=1 python3 tests/wolfssh_parser_contract/run.py
|
||||
CCACHE_DISABLE=1 python3 tests/wolfssh_auth_contract/run.py
|
||||
CCACHE_DISABLE=1 python3 tests/ssh_protocol_policy/run.py
|
||||
```
|
||||
|
||||
Independent review found no blocker in the scoped changes. The parser suite passed **3,124 cases per stack mode (two modes)** with guard pages/UBSan trap instrumentation and **six rejected guard-removal mutations**; its crypto doubles establish parser gating, not signature arithmetic. The auth suite passed **135 cases**. Strict crypto tests run real vendor arithmetic/ASN vectors, independently compare seven audited source bodies with exact parser deltas, and check twelve production translation units plus negative policy cases. Effective ECC/X25519 flags were confirmed. SDK override validation includes actual seven-source build registration. This is scoped implementation/host/build evidence, not ordering closure, exhaustive parser/crypto review, license clearance or whole-phase acceptance. No target evidence, upgrade, asset regeneration or device operation is claimed.
|
||||
|
||||
### Host and build — historical passes 2026-09-15 (9A–9C)
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
python3 tests/security_build_policy/run.py
|
||||
python3 tests/ssh_auth_policy/run.py
|
||||
python3 tests/ssh_auth_transport/run.py
|
||||
python3 tests/hidden_input/run.py
|
||||
pio run
|
||||
python3 tests/security_build_policy/run.py --sdkconfig-header .pio/build/esp32-s3-devkitc-1-n16r8/config/sdkconfig.h
|
||||
python3 tests/wolfssh_auth_contract/run.py
|
||||
python3 tests/sdk_security_overrides/run.py --build-dir .pio/build/esp32-s3-devkitc-1-n16r8
|
||||
python3 tests/ssh_memory/run.py
|
||||
python3 tests/ssh_protocol_policy/run.py
|
||||
python3 tests/web_cookie_auth/run.py --admission
|
||||
```
|
||||
|
||||
Historical final 9C `pio run` passed with **94,340 B linked RAM / 1,831,309 B flash**, unchanged linked RAM / +1,384 B flash against 9B. This is linked size, not measured runtime headroom. Five focused suites passed after final HTTPD first-read correction: pinned SDK cleanup/TLS/source registration, 135 generated wolfSSH parser/control-flow cases, secure allocator, SSH policy (including 15 actual context-integration cases), and web early admission/wiping. The allocator's optional installed-SDK extent contract was also run with the installed IDF path and passed; plain invocation reports that optional check skipped. Seventeen integrated regression commands passed before the final first-read addition, including SSH auth/management, HTTPD idle, HTTPS lifecycle, five cookie-auth modes and the 18-case crash-policy matrix. All ten existing cookie-auth domain modes also passed during implementation. Independent reviews found no blocking issues; the identified inherited first-read pointer issue was corrected and tested. Use `CCACHE_DISABLE=1` on host commands if the compiler wrapper's cache is read-only in a sandbox. No upload, erase, eFuse operation or target test was performed.
|
||||
|
||||
Record the revision, compiler/build outcome and effective configuration. Confirm that the matrix accepts the supported configuration, rejects each prohibited option independently, and rejects absent/disabled required settings. Confirm the normal firmware build compiles the guard. A rejected unsafe configuration is an expected negative-test result, not a firmware build pass. Neither these commands nor a successful build proves target panic behavior.
|
||||
|
||||
### Combined Phase 9 target validation — deferred, not run
|
||||
|
||||
Retain these checks for the user's final whole-phase test session; do not stop implementation for separate slice sign-off. Include the [9D operational rehearsal](security_operations.md#deferred-whole-phase-9-hardware-rehearsal) and targeted message-order/parser/key-validation/interoperability checks for the implemented restricted mitigations and any subsequent reviewed changes. None is recorded as passed here.
|
||||
|
||||
#### Crash and recovery
|
||||
|
||||
1. On an isolated synthetic-secret target, record the tested image/configuration and capture UART0 at 115200 baud. Verify normal boot, UART0 administration, native USB UART1 access, HTTPS and SSH before fault testing.
|
||||
2. Through separately reviewed test-only fault injection, trigger a controlled panic with the supported build policy intact. Verify reboot rather than halt/debugger wait, no panic register/backtrace output and no UART core dump. Record any remaining boot/reset information; do not promise complete UART silence.
|
||||
3. Verify no new flash core dump is written using a reviewed target-side pass/fail check that does not export partition contents. Distinguish old partition contents from a new write; do not erase the partition merely to claim this test passed.
|
||||
4. After reboot, verify UART0 recovery and USB serial access, then authenticated HTTPS/SSH and broker writer/observer behavior. With network services unavailable, verify UART0 and native USB still work. Review routine status/log output using synthetic secrets; this is bounded evidence, not universal redaction proof.
|
||||
5. Record outcomes and limitations in the combined Phase 9 acceptance. Device flashing/fault injection requires a separately authorized hardware session; no eFuse changes, partition migration or erase is required by this policy.
|
||||
|
||||
#### Authentication, input and loaded isolation
|
||||
|
||||
1. On a restricted test network using synthetic credentials, exercise password and Ed25519/P-256 key login for both roles, including a client offering multiple keys. Verify unsigned probes, wrong passwords/signatures, stale-principal rejection and normal shell admission. Explicit keyboard-interactive requests must close/reject without a crash or prompt.
|
||||
2. Exhaust each admission class separately, respecting the independent budgets. For verification testing reuse admitted connections (up to the existing three-failure limit) so handshake exhaustion does not mask the verification gate. Verify counter deltas, reconnect resistance, natural refill and that successful logins also consume capacity. Unsigned probes must not increase completed `auth-attempts`.
|
||||
3. From UART0, clear counters and stop/start SSH while exhausted; observe that neither grants fresh tokens. Account for time elapsed during these operations. Do not assume that a reconnect failure indicates bad credentials. A quiet 60-second period replenishes all pools; ongoing hostile traffic can keep them depleted.
|
||||
4. Keep an established SSH serial stream and USB/browser clients active while generating bounded invalid-login/reconnect traffic. Record serial/broker drops, UART0 command latency, SSH stream responsiveness, internal/DMA minima and recovery. Do not use this admission policy to claim zero CPU impact; TCP/kernel work, KDF/signature work within budget and two-slot occupancy still matter.
|
||||
5. Test hidden credentials at maximum length and one byte over, different suffixes past the limit, unsupported input bytes, overflow followed by editing, Ctrl-C, disconnect and confirmation failure on UART0 and remote administration. No rejected prefix may be persisted or echoed. Check both CR/LF behavior, including delayed UART0 LF delivery: the current UART0 reader relies on next-prompt input flushing, unlike the remote reader's explicit paired-LF handling; host fakes do not prove device timing.
|
||||
6. Exercise generated-password delivery with slow/partial remote output and short subsequent commands, then disconnect/reconnect. Application-buffer wipe assertions are host evidence; do not export live RAM to establish a device pass.
|
||||
|
||||
#### Protocol compatibility and allocation-failure recovery
|
||||
|
||||
1. Verify both allowed TLS suites and both SSH GCM ciphers using compatible clients; force excluded CBC/CTR/other-only offers and confirm rejection. Exercise both SSH KEX choices and both user-key types, initial handshake and rekey, plus TLS renegotiation rejection. Verify intended clients work without EXT_INFO/`server-sig-algs`; do not assume RSA-SHA2 discovery. Exercise disconnect/cleanup during rekey and subsequent session recovery under load. Retain UART0 access; do not rotate identities to work around an algorithm mismatch.
|
||||
2. With synthetic credentials, test truncated/oversized SSH password and change-password packets: no authentication callback for malformed fields, no crash, bounded disconnect/recovery. Include malformed IGNORE/service strings, window overflow, ECC/Ed25519 labels and nested/trailing signature bytes, invalid P-256 points and low-order X25519 inputs. Measure added import-validation latency/allocation/stack cost, host-key loading and handshake deadlines under repeated KEX/rekey and combined load. Host canary/vector assertions are not real encrypted-packet coverage.
|
||||
3. Exercise HTTPS failed-start, post-handshake allocation failure, normal/failed-stop retry and split-header scratch allocation failure on a separately reviewed fault-injection image. Observe recovery/no accumulating allocation loss without exporting keys or RAM. Failed stop must not prematurely free live TLS state.
|
||||
4. Repeatedly start/stop HTTPS and SSH and stress header parsing/authentication under the full transport mix. Capture internal/DMA/PSRAM free/minimum/largest-block and stack margins alongside serial/broker loss counters. Specifically measure old-plus-new allocation peaks and secure-free CPU cost; previous very low internal minima remain important.
|
||||
5. Verify exhausted web login returns early without stalled-body work, clears the used pre-login challenge, and recovers after the documented window. Check malformed requests below quota and correct credentials for normal behavior; do not infer fairness from a rate-limit pass.
|
||||
|
||||
**Bounded host validation:** 23 commands by default; build, OpenSSH interop and web performance are explicit options. Historical default 23/23 and 10 orchestrator fixtures passed on the earlier snapshot. Current supplied parent validation selects the isolated IDF 5.5.3 build/core/SDK, with `--interop --web-performance`: **PASS 24/24**. See [exact command, source equality and evidence limits](phase9_validation.md#current-candidate-execution--2026-09-18). This does not claim a successful default root build or whole-phase acceptance.
|
||||
|
||||
## Staged next work
|
||||
|
||||
- **Implementation/maintenance gate:** the finite SSH and six-finding IDF reviews are complete for their stated profiles; do not re-list them as unimplemented. The [declared finite advisory search](dependency_advisory_coverage.md) is complete. The fix-bearing vendor bundle is integrated in the validated IDF 5.5.3 candidate; complete the [remaining Wi-Fi target gates](wifi_security_update_plan.md), retaining PMF/WPA3 and exact override guards. Integration is not radio-hardware closure. Residual direct-SDK AES-DMA applicability and publication-channel gaps remain; recheck the bounded snapshot before release. Optional ASN.1/ECDH/basicConstraints defense-in-depth backports are not implemented or required by a demonstrated current-path finding.
|
||||
- **Release gate:** notice assembly is implemented; validate actual firmware/device/browser delivery, corresponding source and preferred asset sources, radio-blob legal basis, wolfSSH packaging clarification, remaining manual bitmap/mockup provenance, final runtime/bootloader attribution and any Installation Information. See [packaging gates](release_packaging.md#separate-work-before-distribution).
|
||||
- **Target/acceptance gate:** rehearse operations and the combined target checklist above, including panic/recovery, abuse/isolation, cleanup during rekey, intended-client compatibility without EXT_INFO, loaded KEX/rekey and heap/stack/CPU timing. Obtain explicit whole-phase acceptance; no scoped reviewer or host PASS can substitute.
|
||||
|
||||
- **Continue 9D maintenance and lifecycle.** Execute the [remaining ordering gates and advisory work](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates), target-validate the integrated coherent Wi-Fi correction identified by the completed finite dependency search, and revisit its explicit residual questions, and address the [release source/notice work](dependency_licenses.md#actionable-release-work-not-performed). Runbooks are documented, not rehearsed; remaining mitigations/reviews, distribution clearance and whole-phase acceptance remain outstanding.
|
||||
- **Retained evidence limits:** 9C completes a bounded cleanup/protocol review, not every-library-copy zeroization. Live inline residue, compaction tails, hardware/stack intermediates, global admission starvation and resource/interop measurements remain documented limitations or combined target gates. Any additional hardening must preserve owner lifetimes and bounded recovery.
|
||||
- **Phase 10: OTA trust.** Define independent image-signature verification, trust-anchor provisioning, rotation/revocation, rollback/downgrade and recovery policy without secure boot. Authenticated transport alone is not image-signing policy, and OTA signature checks cannot prevent physical firmware replacement.
|
||||
|
||||
See the [roadmap](roadmap.md#phase-9--security-and-production-hardening), [electrical procedures](electrical_tests.md) and [administration regressions](user_administration_tests.md) for wider gates. Production readiness remains pending; Phase 8 acceptance is not reopened by these follow-ups.
|
||||
@@ -0,0 +1,143 @@
|
||||
# Security library review — Phase 9C
|
||||
|
||||
## Scope and status
|
||||
|
||||
Bounded implementation/source audit, verified 2026-09-15; not library security certification.
|
||||
Baseline: **ESP-IDF 5.5.0, mbedTLS 3.6.3, wolfSSH 1.4.20, wolfSSL 5.8.2~1**
|
||||
(upstream wolfSSL version macro: 5.8.2). Original dependencies are not upgraded or hand-patched.
|
||||
Versions were checked against installed headers and `src/idf_component.yml`; override hashes
|
||||
were checked against installed originals. Source is authoritative over older integration notes.
|
||||
The reported Phase 9C reviews have no remaining blocking finding; the HTTPD null-initial
|
||||
read finding is fixed and covered by the passing host suite below.
|
||||
Whole-Phase-9 target validation is deferred at the user's request; see [main policy](security_hardening.md).
|
||||
The [main policy](security_hardening.md#validation-gates) records final firmware build/size evidence separately from this source review.
|
||||
|
||||
## Confirmed gaps fixed
|
||||
|
||||
| Boundary / source | Implemented correction |
|
||||
|---|---|
|
||||
| SDK `esp_https_server/src/https_server.c` | Delete TLS when post-handshake transport allocation fails; destroy the complete secure context on HTTPD start failure. Restore the original open callback and clear stale context/destructor pointers. Wipe `serverkey_bytes` before freeing the raw key copy. Failed stop retains live ownership. |
|
||||
| SDK `esp_http_server/src/httpd_parse.c` | Replace scratch realloc with allocate/copy/wipe/free; preserve old pointer/content on allocation failure and wipe current scratch at final cleanup. Preserve pending/unread bytes. Initial reads avoid NULL subtraction and retain a NULL parser position until set; existing positions relocate correctly. |
|
||||
| SDK `esp-tls/esp_tls_mbedtls.c` | Apply the server-local TLS profile below after defaults and before setup; static suite storage, TLS 1.2 minimum/maximum, no renegotiation. Client defaults/caller suites and global crypto features are unchanged. |
|
||||
| wolfSSH `src/internal.c` | Use `GetSize()` bounds for password/new-password fields, reject invalid context/index, and guard authentication dispatch after new-password parse failure. Wipe the checked packet suffix before failure output, preserving the username/service/method prefix needed by the caller. Skip wiping on `WS_AUTH_PENDING` for retry; the project does not return pending. |
|
||||
| `src/ssh_memory.c`, `src/ssh_transport.c` | Register secure wolfSSL/wolfSSH allocation hooks before library allocation; wipe retired heap extents and explicit shrink tails, including allocator rounding. |
|
||||
| `src/ssh_protocol_policy.c`, `src/ssh_transport.c` | Apply all five explicit lists; any setter failure frees the unpublished candidate and returns failure, without default-policy fallback. |
|
||||
| `src/web_cookie_auth.c` | Check exhausted verification budget before body receive/parse, reserve authoritatively after parsing, and shorten JSON/credential lifetime before backend/error output. |
|
||||
|
||||
The three SDK overrides and wolfSSH override are registered in `tools/security_overrides.py`.
|
||||
Root `CMakeLists.txt` includes `cmake/security_overrides.cmake` **after `project()`**;
|
||||
`src/CMakeLists.txt` includes both new SSH modules. No embedded web assets were regenerated.
|
||||
|
||||
## Heap and packet lifetime contract
|
||||
|
||||
`ssh_memory` compile-guards **unpoisoned IDF 5.5.0**: `heap_caps_get_allocated_size()`
|
||||
must return the owned usable extent of a base allocation, not an interior-pointer extent.
|
||||
Allocation remains PSRAM-first with internal fallback; no allocation headers, metadata tables,
|
||||
extra locks or tasks are introduced. Free securely wipes the complete extent before release.
|
||||
Shrink retains the pointer/capacity and wipes the discarded tail; it does not reclaim heap.
|
||||
Growth allocates a replacement, copies the old usable extent, then wipes/frees the old allocation.
|
||||
Failed growth leaves the old allocation and contents unchanged. Growth temporarily needs **old + new**
|
||||
storage, including possible internal fallback. HTTPD resize similarly needs both bounded allocations,
|
||||
but retains its ordinary shrink/grow behavior rather than a permanent maximum-sized scratch buffer.
|
||||
|
||||
These fixes cover specific retired copies, not every secret throughout its lifetime:
|
||||
- Static and still-live library buffers can retain bytes; heap hooks do not intercept in-place compaction.
|
||||
- Packet-suffix wiping is deliberately prefix-preserving and is not an asynchronous-auth wipe guarantee.
|
||||
- Backend-specific spills, stack/register copies, crypto intermediates and accelerator state were not exhaustively audited.
|
||||
- Browser memory, flash history and all allocator regions are not proven clean; do not export raw memory dumps as evidence.
|
||||
|
||||
## Existing cleanup verified, not presumed broken
|
||||
|
||||
Inspection of the installed original sources found existing wipes on the checked normal paths:
|
||||
- wolfSSL `wolfcrypt/src/ecc.c:wc_ecc_free()` calls `mp_forcezero()` for the private scalar;
|
||||
`integer.c` wipes used digits before release, while `tfm.c` delegates to `fp_forcezero()`.
|
||||
- mbedTLS `library/pk_wrap.c:eckey_free_wrap()` delegates to `ecp.c:mbedtls_ecp_keypair_free()`;
|
||||
the private MPI reaches `bignum.c:mbedtls_mpi_free()` and its zeroize-and-free path.
|
||||
- mbedTLS `library/md.c:mbedtls_md_free()` zeroizes/frees HMAC pads and wipes the context.
|
||||
- mbedTLS `library/ssl_tls.c:mbedtls_ssl_free()` zeroizes/frees input/output record buffers;
|
||||
its inspected buffer-resize path also zeroizes retired storage.
|
||||
|
||||
Thus ordinary destructors are **not generally broken across both stacks**. The confirmed gaps above
|
||||
are separate raw-copy, ownership, resize and packet-lifetime issues. IDF dynamic TLS buffers are
|
||||
compile-rejected because their destruction bypasses the inspected upstream record-buffer path;
|
||||
other configurations/backends need their own review, not extrapolation from these observations.
|
||||
|
||||
## Current protocol allowlists and compatibility
|
||||
|
||||
| Layer / setting | Exact current policy |
|
||||
|---|---|
|
||||
| HTTPS versions | TLS 1.2 only; renegotiation disabled or compiled out |
|
||||
| HTTPS suites | `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`, `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384` |
|
||||
| SSH `Kex` | `curve25519-sha256,ecdh-sha2-nistp256` |
|
||||
| SSH `Key` (host identity) | `ecdsa-sha2-nistp256` |
|
||||
| SSH `Cipher` (both directions) | `aes128-gcm@openssh.com,aes256-gcm@openssh.com` |
|
||||
| SSH `Mac` (both advertised directions) | `hmac-sha2-256`; GCM provides the negotiated AEAD integrity |
|
||||
| SSH `KeyAccepted` | `ssh-ed25519,ecdsa-sha2-nistp256` (`server-sig-algs` advertisement only) |
|
||||
| SSH compression | `none` |
|
||||
| SSH authentication | Password or enrolled Ed25519/ECDSA-P256 public key; keyboard-interactive rejected |
|
||||
|
||||
SSH list strings have static lifetime because contexts/sessions borrow their pointers. List setters
|
||||
alone do not validate compiled support; the source/production-feature tests check names, IDs and
|
||||
serialized initial/rekey lists. Enrollment/authorization remains in the user database, not `KeyAccepted`.
|
||||
Legacy CBC/CTR-only SSH clients, excluded KEX/host-key clients, and CBC-only TLS clients cannot connect;
|
||||
TLS clients need TLS 1.2 plus one listed ECDHE-ECDSA GCM suite (TLS-1.3-only also fails).
|
||||
There is no automatic compatibility fallback. Modern-client compatibility is still a live-test gate,
|
||||
not a claim that signature verification, real KEX/rekey or TLS/SSH handshakes were exercised here.
|
||||
|
||||
## Web admission and retained credential/browser policy
|
||||
|
||||
The early quota probe neither consumes attempts nor advances the window. The final post-parse
|
||||
reservation preserves **five password verifications per 60 seconds globally**; malformed requests
|
||||
are not charged. Exhausted requests avoid body receive/parser/KDF and close without draining unread
|
||||
bodies. Challenges remain consumable before this probe: this does **not** establish challenge fairness
|
||||
or prevent global starvation. HTTPS service stop/start resets this window/challenges, unlike SSH's
|
||||
boot-lifetime admission buckets. Epoch/readiness checks fence stale work at both quota boundaries.
|
||||
Raw JSON is wiped after parsing and before KDF; parsed credentials immediately after authentication;
|
||||
denial paths wipe both before error responses. Ordinary final request/token cleanup remains in place.
|
||||
|
||||
`src/user_database.{c,h}` remains unchanged: **12–64 printable ASCII bytes** (`0x20`–`0x7e`),
|
||||
PBKDF2-HMAC-SHA256 with **50,000 iterations**, **16-byte random salt**, **32-byte verifier**.
|
||||
Generated passwords select **24 symbols from 64**, giving **144 bits** with uniform secure randomness.
|
||||
This is a reviewed retained baseline, not a claim that 50,000 iterations meets every current deployment
|
||||
recommendation. Benchmark target verification latency and mixed-load headroom before choosing a new
|
||||
cost; do not blindly increase it. No verifier storage format or key-rotation behavior changes here.
|
||||
|
||||
`src/web_security.c` generates a self-signed **P-256 / ECDSA-SHA256** certificate, non-CA,
|
||||
digital-signature usage, server-auth EKU, device DNS and fixed AP IPv4 SANs, with fixed validity
|
||||
**2025-01-01 through 2049-12-31**. Existing validation checks the key pair, expected fields/SANs and
|
||||
self-signature; this inspection is not a new real-crypto signature-verification test.
|
||||
Compare the certificate SHA-256 fingerprint through trusted UART0 (`web certificate info`) before
|
||||
accepting browser trust; a warning bypass is not verification, nor is arbitrary STA-IP trust solved.
|
||||
Existing persistence/rotation/recovery contracts remain unchanged; NVS is not newly encrypted.
|
||||
|
||||
`src/web_login_ui.c`, `src/web_ui.c` and `src/web_cookie_auth.c` retain CSP, document/auth
|
||||
`Cache-Control: no-store`, and `Secure; HttpOnly; SameSite=Strict` cookies. Static assets retain their
|
||||
separate caching policy. HSTS is deliberately not blindly forced for the self-signed hostname/IP
|
||||
workflow: it is not a substitute for verified certificate trust and may obstruct recovery.
|
||||
|
||||
## Maintenance and evidence
|
||||
|
||||
1. Keep the original SDK/managed sources untouched. Maintain reviewed `Entry` hashes and exact-once
|
||||
edits in `tools/security_overrides.py`; never repin a hash merely to make configuration succeed.
|
||||
2. Re-audit changed source ownership, cleanup, allocator extents, algorithms and resolved features.
|
||||
Full original SHA-256/version mismatch, missing/ambiguous edits or source registration fail closed.
|
||||
3. CMake retains component targets and source properties/quoted-include context, replacing exactly one
|
||||
original compilation per entry. Generator/version/original/derived changes trigger reconfiguration;
|
||||
changed originals fail the hash check. Do not hand-patch SDK files or derived build-tree output.
|
||||
4. Derived full files preserve original copyright/license notices; regenerate through configuration,
|
||||
verify exact generated bytes and single-source registration, then rerun the relevant host contracts.
|
||||
5. Future release/dependency review remains pending: external advisories and license obligations have
|
||||
**not** been reviewed here. No CVE absence, vulnerability completeness or license-compliance claim.
|
||||
|
||||
Verified host commands passed during this documentation audit (prefix `CCACHE_DISABLE=1`):
|
||||
- `python3 tests/sdk_security_overrides/run.py --build-dir .pio/build/esp32-s3-devkitc-1-n16r8`
|
||||
- `python3 tests/ssh_memory/run.py --idf-path /home/mscholz/.platformio/packages/framework-espidf`
|
||||
- `python3 tests/ssh_protocol_policy/run.py`
|
||||
- `python3 tests/wolfssh_auth_contract/run.py`
|
||||
- `python3 tests/web_cookie_auth/run.py`
|
||||
|
||||
These execute actual modules/extracted installed or patched functions with heap, crypto, IO and layout
|
||||
mocks, plus pinned source/production-feature contracts and existing Ninja registration checks.
|
||||
They cover cleanup failures, null-first-read behavior, policy serialization/publication and web quota/wipe
|
||||
ordering; they are not complete parser fuzzing, real signature verification, live handshakes or target tests.
|
||||
No firmware build, upload, erase, raw-dump export or hardware operation was performed for this document.
|
||||
@@ -1,100 +1,117 @@
|
||||
# Security operations — intermittent hobby use
|
||||
# Security operations — Phase 9D
|
||||
|
||||
This checklist is for a wireless serial cable used occasionally on a trusted network,
|
||||
not an Internet-facing appliance or a production-security certification.
|
||||
It covers existing firmware operations only: no dependency patches, crypto upgrades,
|
||||
secure-boot, encrypted-NVS, or eFuse changes are part of this scope.
|
||||
Operator runbook for the current firmware; procedures below are **not execution records**.
|
||||
Use the [command reference](command_reference.md) for complete syntax and the [browser contracts](web_administration.md) for frontend permissions and result handling.
|
||||
No device operation is authorized merely by this document. Schedule disruptive work, retain trusted UART0 access, and obtain explicit approval for destructive recovery/erase.
|
||||
The user will validate **whole Phase 9**, not provide per-segment sign-off. **9D is in progress; Phase 9 is not complete or production-ready.** The [partial wolfSSL/wolfSSH advisory review](wolf_security_review.md) leaves current-path security findings open; the [bounded license inventory](dependency_licenses.md) leaves distribution/corresponding-source/notice questions unresolved. This runbook neither resolves those findings nor claims device validation or license clearance.
|
||||
|
||||
## Provision once, locally
|
||||
## Safety and access
|
||||
|
||||
- Use the physical **UART0 administration console** on a trusted computer.
|
||||
Native USB CDC is a separate UART1 serial-data path, not the admin console.
|
||||
- Create the first administrator with `user add <username> admin`.
|
||||
Use a unique password; the prompt accepts 12–64 printable ASCII characters.
|
||||
Add a separate `user` account for ordinary serial use, especially over SSH:
|
||||
SSH `admin` opens administration, whereas SSH `user` opens UART1.
|
||||
- Use unique Wi-Fi credentials, not passwords reused from other services.
|
||||
Configure station profiles using the [Wi-Fi command reference](command_reference.md#wi-fi);
|
||||
enter their passwords with `wifi profile secret <slot>`.
|
||||
If using the device AP, set its password with `wifi ap secret`.
|
||||
If the AP is unnecessary, select `wifi ap policy off`.
|
||||
Run `wifi save` to persist working Wi-Fi settings.
|
||||
- Before entering network credentials into a client, check device identity locally:
|
||||
run `web certificate info` and `ssh host-key info` on trusted UART0.
|
||||
Compare the SHA-256 certificate fingerprint in the browser certificate viewer
|
||||
and the SSH host-key fingerprint in the SSH client with those local values.
|
||||
Record the verified fingerprints; do not blindly dismiss certificate or changed-key warnings.
|
||||
- Keep passwords in a password manager. Generated account passwords (`--generate`)
|
||||
are displayed once; avoid terminal recording or sharing that output.
|
||||
- Restrict physical access: UART0 is the trusted administrative recovery authority. Use the USB-to-UART bridge at 115200 baud, not native USB CDC.
|
||||
- Native USB is network-independent, binary-transparent UART1 access through the one-writer/multiple-observer broker; it is not an admin console. Reboot interrupts every transport.
|
||||
- Use a trusted workstation and restricted provisioning network. Disable terminal recording, screen sharing and clipboard history before secret entry/reveal; do not put secrets in command arguments, tickets, screenshots or routine logs.
|
||||
- UART0 and authenticated role-`admin` SSH provide the operational commands below, except `user recover --force` is UART0-only. Role-`user` SSH is serial, not administration.
|
||||
- Browser shell is narrower: other-account interactive add/password and forced role/delete only; no self/generated/key/recovery account commands. Wi-Fi/mDNS shell access is status-only. Typed Settings has separate account/network/identity workflows; do not infer shell parity.
|
||||
- NVS is **unencrypted**: Wi-Fi PSKs and HTTPS/SSH private keys are plaintext at rest; password verifiers are also sensitive. Secure boot and NVS encryption are not provided by this phase. Physical flash extraction and firmware replacement remain outside the protection boundary.
|
||||
|
||||
## Each use
|
||||
## First provisioning — UART0 first
|
||||
|
||||
- Use only a trusted LAN or the device's password-protected AP.
|
||||
Do not configure router port forwarding or otherwise expose HTTPS/SSH to the Internet.
|
||||
A trusted network reduces exposure; it does not make every attached client trustworthy.
|
||||
- Check unexpected identity warnings against UART0 before logging in.
|
||||
Keep UART0, native USB, and the connected RS-232 equipment physically controlled:
|
||||
local serial access is not protected by network account authentication.
|
||||
- When finished, sign out of the browser and close SSH clients.
|
||||
Browser serial **Disconnect** alone is not sign-out.
|
||||
- Prefer powering the device off when unused. If it must remain powered for USB
|
||||
serial use, run `wifi stop` from UART0 and check `wifi status` after the queued
|
||||
operation completes. This stops network access, not native USB UART1 access.
|
||||
To keep Wi-Fi disabled at the next boot, follow with `wifi save`.
|
||||
`wifi start` enables it again in RAM; save again only if that boot policy is wanted.
|
||||
Do not rely on a temporary stop surviving reboot without saving.
|
||||
1. Start in a controlled RF environment with UART0 attached. Defaults enable Wi-Fi with a random-password fallback AP and no station profiles; networking can start before the console is ready. A new AP name is not an authentication secret.
|
||||
2. Once the console is ready, use `wifi stop`, inspect `wifi status`, then `wifi save` if Wi-Fi must remain disabled after reboot. Stop is asynchronous: check actual state, not only the queue acknowledgement. Do not rely on this to prevent the initial boot's radio exposure.
|
||||
3. Inspect `user status`. Missing user storage initializes empty; create the first administrator with `user add <username> admin` on UART0 and the hidden confirmation prompts. There is no bootstrap/shared default web credential. A healthy empty database needs **add**, not recovery.
|
||||
4. Store the chosen password in the operator's protected vault. Usernames and password bounds are in the command reference (passwords: 12–64 printable ASCII characters). Optionally use `--generate` only with private, reliable one-time capture. Create/test a separately controlled recovery administrator if policy requires one; the final administrator cannot be deleted or demoted.
|
||||
5. Choose AP policy explicitly with `wifi ap policy off` or `wifi ap policy fallback` (use `always` only intentionally). For retained AP access, set `wifi ap secret` through the hidden 8–63-character prompt, or privately retrieve the generated password with `wifi ap show-secret`. Never include its output in routine captures.
|
||||
6. Configure an unused/disabled station slot using `wifi profile set <slot> <priority> <mixed|wpa3> <ssid>`, then `wifi profile secret <slot>`, then `wifi profile enable <slot>`. Check peer compatibility before choosing WPA3. Profile set preserves the existing secret/enabled state; disable an existing slot before staging its replacement. Enabling or editing an enabled profile/AP can reconnect the radio.
|
||||
7. Use `wifi start` when ready, inspect `wifi status` / `wifi profiles`, and `wifi save` to persist the intended working policy, profiles, secrets and boot-enabled state. Restrict upstream network access to intended operators; do not expose ports 443/22 to untrusted networks.
|
||||
8. Read `web certificate info` and `ssh host-key info` through trusted UART0. Compare the certificate SHA-256 fingerprint in the HTTPS client and the OpenSSH `SHA256:` host-key fingerprint **before supplying account credentials or accepting changed trust**. A self-signed certificate or an unsolicited network fingerprint alone is not proof of device identity.
|
||||
9. Establish narrowly scoped browser certificate trust and SSH known-host trust only after comparison. Investigate an unexpected mismatch rather than disabling verification or deleting all known-host entries. Test fresh HTTPS login and both intended SSH roles; an admin SSH login does not acquire a serial writer lease.
|
||||
10. Set required serial framing, mDNS suffix and display policy with their documented commands and explicit saves. Record intended state privately; later rehearse a reboot and compare actual loaded settings/trust before production use.
|
||||
|
||||
## Change credentials or identity when needed
|
||||
**Fallback-default caution:** true first boot attempts to persist generated Wi-Fi defaults, but that save can fail. Incompatible stored configuration selects generated RAM defaults without overwriting the rejected blob. `wifi defaults` generates fresh RAM defaults; `wifi reset` generates and saves them. Both restore enabled/fallback policy and replace the AP password, not merely repair connectivity. Re-protect the AP and verify/save intended state rather than assuming an old password or disabled radio survived.
|
||||
|
||||
Use trusted UART0 for these operations to avoid losing a remote session mid-change.
|
||||
Rotate after suspected disclosure, a lost client key, or a change of owner/access;
|
||||
there is no need for a calendar-driven rotation ritual for this use case.
|
||||
## Working configuration versus service lifecycle
|
||||
|
||||
| What changed | Built-in operation |
|
||||
| Operation/domain | Persistence and operational effect |
|
||||
|---|---|
|
||||
| Account password | `user password <username>` (no-echo prompt). |
|
||||
| Authorized SSH client key | `user show <username>` to identify its index; `user key delete <username> <0..2> --force`, then `user key add <username>` to enter the replacement public key. |
|
||||
| Account no longer needed | `user delete <username> --force`; the final administrator is protected. |
|
||||
| Wi-Fi password | Change the router credential as appropriate, then `wifi profile secret <slot>`; for the device AP use `wifi ap secret`. Finish with `wifi save`. |
|
||||
| HTTPS device identity | `web certificate rotate --force`. |
|
||||
| SSH device identity | `ssh host-key rotate --force`. |
|
||||
| Serial, Wi-Fi, mDNS, display edits | Working RAM until `serial save`, `wifi save`, `mdns save` or `display save`, respectively; `load` reads that domain's storage, not an external backup. `defaults` is RAM; `reset` applies/persists domain defaults. Check errors and actual state; these domains do not share one atomic transaction. |
|
||||
| `wifi start` / `wifi stop` | Queue runtime action **and** set RAM `enabled-at-boot` true/false; only `wifi save` persists it. Local start/stop has the same semantics. |
|
||||
| `web start` / `web stop`, `ssh start` / `ssh stop` | Runtime service controls, not persistent enable/disable configuration. No corresponding service `save` or durable boot-disable command. Stops disconnect that service's clients. |
|
||||
| Boot network services | `main.c` attempts HTTPS/SSH startup independently when Wi-Fi initialization/start has no error and each service's security/runtime initialization succeeds. Wi-Fi configured off can still initialize successfully: listeners may be started without radio reachability. Wi-Fi-off is not a persistent HTTPS/SSH-disable flag. |
|
||||
| Accounts and identities | Mutations commit their own NVS blob as part of the operation; no follow-up `save`. Commit acknowledgement is not a general power-loss/durability guarantee. |
|
||||
| `reboot` | Loses unsaved RAM and all live sessions. A runtime service stop does not survive normal boot startup. |
|
||||
|
||||
Account changes invalidate that account's network sessions; identity replacement
|
||||
interrupts the affected service. Check command results, then verify new fingerprints
|
||||
through UART0 before updating browser trust or SSH known-host entries and reconnecting.
|
||||
Changing a password does not remove authorized SSH keys; review both after compromise.
|
||||
Identity rotation is not an account-password change. If an acknowledgement is lost,
|
||||
inspect current state on UART0 rather than blindly repeating the operation.
|
||||
## Routine account, password and authorized-key rotation
|
||||
|
||||
## Recovery and backup limits
|
||||
1. Keep UART0 or a separate current administrator available. Inspect `user show <username>` privately to confirm role and current key slots; do not operate from stale indices. The database allows eight accounts and three authorized keys per account.
|
||||
2. Set a known replacement with `user password <username>` using hidden prompts. `user password <username> --generate` commits then displays a value once: protect it immediately. Lost generated output has no retrieval command; inspect account state, then deliberately set another known password via UART0/another admin rather than blindly replaying generation.
|
||||
3. Admin SSH **rejects its own generated-password replacement**; its ordinary hidden self-password change is allowed but revokes the invoking principal. Browser shell denies all self account mutations. Typed Accounts supports self changes; its Generate action only returns a value, does not change the account, and has no retained retrieval. Securely save/acknowledge it before separately submitting the mutation.
|
||||
4. For an authorized-key rotation, generate/protect the private key on the operator's host; import only the supported Ed25519 or ECDSA-P256 **public** key with `user key add <username>`. With capacity available, add first, test a fresh login using the new key, then remove the old key using its freshly inspected slot: `user key delete <username> <0..2> --force`. If full, use independent recovery access before removing a slot. `user key clear <username> --force` removes all that account's keys, not its password.
|
||||
5. Password, role and key changes stale the affected principal; deletion removes the account. Committed changes trigger targeted web-ticket/session and SSH revocation, supplemented by authoritative currentness checks. Self changes can lose their result before it arrives; unrelated accounts should remain usable. Disconnect/timeout is not proof of rollback or cancellation of admitted work.
|
||||
6. Reauthenticate to verify the new credential and rejection of the retired one; do not confuse admission throttling with a bad password. For account retirement use `user delete <username> --force`; for reduced privilege use `user role <username> user --force`. Preserve a usable administrator. A password change does **not** remove authorized keys, and key removal does **not** change the password: revoke both paths for a compromised account.
|
||||
7. Update the protected operator record and revoke any reused credentials/keys on other systems separately. A key can authorize multiple device accounts; remove every affected authorization. Retire superseded host-side private-key copies according to storage policy.
|
||||
|
||||
- Keep UART0 available for forgotten passwords, Wi-Fi mistakes, and network-service
|
||||
recovery. Native USB can still carry UART1 data when networking fails, but cannot
|
||||
repair accounts or identities; neither path promises uninterrupted service through reboot.
|
||||
- For a healthy database, use normal UART0 account commands to repair access.
|
||||
`user recover --force` is **only** for an unavailable user database: it destroys
|
||||
that database's contents and rebuilds it empty, refuses a healthy database, and
|
||||
requires creating a new administrator afterward. It is not a forgotten-password reset.
|
||||
- `web reset --force` replaces HTTPS certificate/private-key material only;
|
||||
`ssh reset --force` replaces SSH host-key material. Neither resets accounts or Wi-Fi.
|
||||
These are scoped recovery operations, not a whole-device factory reset.
|
||||
- No supported configuration/credential/identity backup-export command was found
|
||||
in the application console. Keep a private reprovisioning record of settings,
|
||||
account names/roles, client public keys, and verified fingerprints; store passwords
|
||||
separately in a password manager. Do not expect status output to recover secrets
|
||||
or private keys. Reprovisioning may require accepting a newly verified device identity.
|
||||
- Physical extraction or firmware replacement is **not protected by this checklist**.
|
||||
Treat a lost device or a raw flash copy as potentially exposing stored Wi-Fi secrets,
|
||||
private identities, and password verifiers. Rotation is not a secure flash-erasure claim.
|
||||
## HTTPS and SSH server identity rotation
|
||||
|
||||
## Lightweight maintenance
|
||||
Plan a maintenance window and distribute newly verified public trust through a trusted channel. Server identity changes are separate from account passwords/authorized keys and do not replace them.
|
||||
|
||||
Occasionally, and before a planned firmware update, review relevant ESP-IDF,
|
||||
wolfSSL/wolfSSH, and bundled web-library upstream release/security notes.
|
||||
If a relevant issue appears, stop network use until a separate upgrade decision is made;
|
||||
USB serial remains an option. Dependency upgrades and their validation are separate work,
|
||||
not an endless CVE audit or an implied promise that this firmware is vulnerability-free.
|
||||
- **HTTPS:** `web certificate rotate --force` replaces certificate **and** private key. Owner ordering is reserve → generate/commit/publish → stop/restart if running. Precommit failure leaves the old identity/logins in place; after commit, lifecycle failure never rolls the identity back. Failed stop can leave the old certificate served while UART0 reports new stored material. Rotation while stopped stays stopped.
|
||||
- **SSH:** `ssh host-key rotate --force` reserves ownership → stops sessions → generates/commits/publishes → restarts if previously running. Failed stop skips mutation/start. Persistence failure may follow client disconnection and attempts to restart with unchanged material; that restart can also fail. A committed new key is never rolled back after restart failure. Rotation while stopped stays stopped.
|
||||
- UART0/admin SSH expose both rotations. Browser shell allows only exact `web certificate rotate --force`, not SSH host-key mutation; typed HTTPS/SSH Settings provides its own bounded rotation flow. Neither browser route provides identity reset/recovery/export.
|
||||
- Remote scheduling/drain/HTTP acknowledgement is not peer receipt or operation completion. On timeout/lost result, use Check Result/Refresh where available, then inspect UART0 service status and fingerprints. **Do not automatically repeat rotation.**
|
||||
- Resolve lifecycle failures with deliberate UART0 `web stop` / `web start` or `ssh stop` / `ssh start`, checking each outcome; do not start over failed cleanup or rotate again to repair it. If recovery needs reboot, account for unsaved state and all-transport interruption.
|
||||
- After recovery, compare the actually served identity against trusted UART0, replace only the relevant client trust entry, and test a fresh connection. HTTPS restart requires fresh login; SSH rotation closes SSH sessions but does not inherently require HTTPS relogin. USB/UART0 remain independent of these network-service rotations, not of reboot.
|
||||
|
||||
Commands and limits were checked against application sources and the [command reference](command_reference.md) at `f40c09c`.
|
||||
This is source-reviewed operating guidance, not a build, hardware test, security audit,
|
||||
or verification of any individual device's flash/eFuse configuration.
|
||||
## Configuration backup and reconstruction
|
||||
|
||||
There is **no implemented configuration export/import or full backup/restore workflow**, nor a supported private-identity export/restore command. `save` writes working state to the same device; it is not an off-device backup. Do not use raw NVS/flash dumps as routine backups or duplicate them onto replacement devices.
|
||||
|
||||
Maintain an access-controlled operator inventory outside the repository: reviewed firmware/build provenance, intended serial settings, station/AP policy and profile ordering, boot-enabled intent, mDNS/display settings, account roles, authorized-public-key provenance, verified public server fingerprints, and last successful save/rehearsal outcomes. Keep device/account/network identifiers in that protected record, not public issue logs. This runbook intentionally contains no populated inventory or identifier template.
|
||||
|
||||
Keep passwords, Wi-Fi PSKs and client private keys in a separately protected, backed-up vault with controlled recovery access and retention. Inventory entries should reference vault records rather than duplicate secrets. Ordinary status cannot reconstruct PSKs/passwords/private keys; public fingerprints cannot recreate an identity. Do not collect verifier material, session cookies or tickets for backup.
|
||||
|
||||
Reconstruction means first-admin provisioning, manually re-entering reviewed settings and secrets, re-importing authorized **public** keys, explicitly saving each configuration domain, and verifying new server trust. Replacement hardware/newly generated identities require client trust changes, not promises to restore the old identity. Test the reconstruction on an isolated spare with synthetic credentials. Exceptional forensic flash capture requires separate approval, restricted handling and retention; it remains secret-bearing, not a supported restore format.
|
||||
|
||||
## Recovery — smallest affected domain first
|
||||
|
||||
Use UART0 status/error observations to distinguish connectivity, authentication, storage and lifecycle faults. Avoid reset for ordinary login throttling or protocol mismatch; restrict hostile traffic and allow natural budget refill. Native USB preserves serial access during network failure, not administrative recovery.
|
||||
|
||||
| Fault | Deliberate recovery and scope |
|
||||
|---|---|
|
||||
| Wrong/lost password, healthy database | `user password <username>` through UART0 or another current admin; inspect/revoke authorized keys separately if compromised. Do not recover/erase the database. |
|
||||
| User database unavailable after failed initialization | Only with explicit approval, UART0 `user recover --force` replaces **only the user blob** with an empty database, discarding all accounts/verifiers/authorized keys. Then `user add <username> admin`. Healthy databases, including healthy empty ones, are refused. Underlying NVS/RNG/allocation failure can still prevent recovery. Empty storage commits before dummy-verifier initialization; a later initialization error does not prove the old accounts survived. |
|
||||
| HTTPS material unavailable/damaged | With approval, UART0 `web reset --force` replaces **only HTTPS certificate/private key**, not users or SSH/Wi-Fi configuration; it can also replace healthy material. Unlike stopped rotation, reset attempts to start a stopped service. Verify new trust and lifecycle state. |
|
||||
| SSH material unavailable/damaged | With approval, UART0 `ssh reset --force` replaces **only SSH host identity**, not account keys/passwords or HTTPS material; it can also replace healthy material and attempts startup. Verify new trust and lifecycle state. |
|
||||
| Ordinary configuration wrong/incompatible | Review the domain's `status`, then choose `load`, manual correction plus `save`, or explicitly approved domain `reset`. `serial reset`, `wifi reset`, `mdns reset`, `display reset` are not factory resets; Wi-Fi reset can reopen fallback access with a new password. |
|
||||
| Service/network fault with healthy identity | Recover radio/profile or stop/start the affected service; preserve identities. If initialization remains unavailable, investigate the reported dependency/storage fault before an approved reboot or broader recovery. |
|
||||
|
||||
Identity resets are also available to authenticated admin SSH, but UART0 is preferred for recovery; browser reset is unavailable. None of these domain commands promises secure deletion of superseded flash bytes. A failed write or lost result calls for inspection, not assumptions about persisted state.
|
||||
|
||||
## Destructive factory-style reset — explicit approval only
|
||||
|
||||
There is **no on-device factory-reset command**. Full reset is the host-side erase/reflash procedure, not a collection of identity resets. It is not a routine upgrade, backup, password repair or prerequisite for this runbook.
|
||||
|
||||
1. Obtain explicit approval for this particular target and **all flash/NVS data loss**; confirm the physical device/port, maintenance window, protected reconstruction inventory and reviewed firmware. Disconnect sensitive serial equipment and isolate RF before erase/reflash.
|
||||
2. Only in that separately authorized hardware session, from the repository root, run `pio run --target erase`; check success, then reflash with `pio run --target upload`. Use `pio device monitor -b 115200` on the USB-to-UART bridge for provisioning. Do not automate or run these operations as documentation validation.
|
||||
3. Erase removes firmware and all NVS domains, including users, Wi-Fi secrets/configuration, TLS/SSH identities, serial, mDNS and display settings, plus other flash contents. Ordinary upload alone is **not** a reset. Reflash and follow UART0-first provisioning; expect newly generated identities/AP secret and no old accounts. Re-establish client trust only after verification.
|
||||
4. Erase success is **not a forensic secure-erase guarantee**, proof that external copies disappeared, or a general persistence guarantee. Preserve custody/disposal controls below. See the [documented partition migration](../README.md#one-time-migration-from-the-default-partition-table) before changing layouts.
|
||||
|
||||
## Decommissioning
|
||||
|
||||
- Isolate/disconnect the device and attached serial equipment; revoke access **outside the device** before custody changes. Rotate upstream/shared Wi-Fi PSKs or revoke network admission as applicable; remove saved device-AP credentials on peers.
|
||||
- Remove retired device certificate exceptions/pins and SSH known-host trust on operator systems. Revoke account/key authorizations and reused credentials on other devices/services separately; a local reset cannot revoke remote trust or a copied private key.
|
||||
- If approved, perform the destructive erase above; do not reflash merely to retire hardware. Logical rotation/reset can leave historical plaintext NVS pages, older credentials and private keys in flash. Retired flash/RAM captures, backups, terminal records, vault versions and workstation copies require separate retention/disposal handling.
|
||||
- Flash erase, file deletion and software RAM wiping do not prove all copies are unrecoverable. Maintain physical custody; use approved media destruction/device disposal when confidentiality requires it. Do not claim secure boot, encrypted NVS, extraction resistance or guaranteed erasure.
|
||||
|
||||
## Deferred whole-Phase-9 hardware rehearsal
|
||||
|
||||
Use a separately authorized, isolated expendable target and synthetic secrets. Record sanitized outcomes, exact tested build and limitations, not passwords, raw dumps or live identifiers. These checks are **planned, not passed**, and join the [combined Phase 9 gates](security_hardening.md#combined-phase-9-target-validation--deferred-not-run) and [administration regressions](user_administration_tests.md):
|
||||
|
||||
- [ ] First boot/healthy-empty provisioning; no remote first-admin path; fallback AP protection, saved radio-off/on behavior, and fresh trusted HTTPS/SSH login for both roles.
|
||||
- [ ] Save/load/default/reset/reboot per configuration domain; failed first-default save and incompatible Wi-Fi storage; compare runtime radio/listener state with saved boot intent.
|
||||
- [ ] Password/key/role/delete rotation: old credential rejection, self-revocation, unrelated-account isolation, final-admin protection, remote self-generated denial, typed generation-before-submit, lost/partial one-time output and stale results.
|
||||
- [ ] Both identity rotations running/stopped; failed stop/commit/restart and lost ACK/result using reviewed fault injection; no blind replay, stored-versus-served trust comparison, reconnect/relogin and independent UART0/USB access.
|
||||
- [ ] Each domain recovery preserves unrelated domains; unavailable-user recovery is UART0-only, healthy recovery refused. Reconstruct a spare from protected records without raw NVS restore.
|
||||
- [ ] Separately approved destructive erase/reflash rehearsal: all domains reset, fresh identities/AP credential/accounts provisioning; rehearse external trust revocation and disposal checklist without claiming secure erasure.
|
||||
- [ ] Complete existing whole-phase crash/no-dump, authentication/admission/refill, hidden-input, protocol/rekey, allocation-failure and full transport-mix/resource checks; verify one writer, isolated observers and recovery. Do not substitute this runbook review for those measurements.
|
||||
|
||||
Source basis: [startup](../src/main.c), [console policy](../src/admin_ssh_console.c), [users](../src/user_console.c) / [storage](../src/user_database.c), [Wi-Fi commands](../src/wifi_console.c) / [manager](../src/wifi_manager.c) / [storage](../src/wifi_config.c), [HTTPS owner](../src/web_server.c) / [material](../src/web_security.c), and [SSH owner](../src/ssh_transport.c) / [material](../src/ssh_security.c). Commit ordering is an implementation contract, not power-loss or hardware-validation evidence.
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# SSH key-validation review — 2026-09-15
|
||||
|
||||
## Decision and scope
|
||||
|
||||
**The baseline P-256 ECDH and X25519 validation gaps are mitigated by project-owned compile policy. Parent firmware build and local strict crypto suite PASS; the broader SSH review and hardware gates remain open.** This supplements, rather than silently rewrites, the historical [wolf review](wolf_security_review.md).
|
||||
|
||||
- Enable upstream `WOLFSSL_VALIDATE_ECC_IMPORT` for wolfSSL 5.8.2. The SSH server imports an unauthenticated P-256 peer point and otherwise reaches scalar multiplication without an on-curve check.
|
||||
- Enable existing upstream `WOLFSSL_ECDHX_SHARED_NOT_ZERO`. The SSH X25519 input precheck does not reject every low-order input; the result check was disabled.
|
||||
- These are PUBLIC definitions in `cmake/wolf_crypto_policy.cmake`, with resolved-settings checks in `cmake/wolf_crypto_policy.h`. No root `CMakeLists.txt` edit is necessary: it already includes this module after `project()`. The existing small X25519/Ed25519 policy and RNG/acceleration controls are preserved.
|
||||
- No dependency version, installed vendor source, generated override, application source, or device change was made by this task. Edits are restricted to the assigned policy files, `tests/wolf_crypto_policy/`, and this report.
|
||||
- This is **not** a demonstrated long-term-key recovery, authentication bypass, remotely measured exploit, full upstream backport, or release approval. ECDH uses a freshly generated ephemeral key, separate from the long-term signing identity.
|
||||
|
||||
## Exact local evidence
|
||||
|
||||
Inspected installed wolfSSH 1.4.20 / wolfSSL 5.8.2~1 and the generated wolfSSH input located through `.pio/build/esp32-s3-devkitc-1-n16r8/compile_commands.json`. SHA-256 snapshot:
|
||||
|
||||
| Input | SHA-256 |
|
||||
| --- | --- |
|
||||
| `managed_components/wolfssl__wolfssh/src/internal.c` | `81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9` |
|
||||
| `.pio/build/esp32-s3-devkitc-1-n16r8/security_overrides/wolfssh_internal/internal.c` | `9e5923d536cee049409a7df471d155a6dd2e804d55a1528f5c4abc8e34807dea` |
|
||||
| `managed_components/wolfssl__wolfssl/wolfcrypt/src/ecc.c` | `909c57e2756a8002df9f1d214483c659cb20db4a5f51047eda62d64ac458db06` |
|
||||
| `managed_components/wolfssl__wolfssl/wolfcrypt/src/curve25519.c` | `9a0f6f0205245a8d19500a936d9b02bb71c8656713648408d1cb408362694b76` |
|
||||
| `managed_components/wolfssl__wolfssl/wolfcrypt/src/signature.c` | `62ab3db3dfd251b2a2c73b69ef05aab6085d2e0d673fd9159514b3ee261cea4f` |
|
||||
|
||||
The original and generated bodies of `HashForId`, `KeyAgreeEcdh_server`, `KeyAgreeCurve25519_server`, `SignHEcdsa`, and `DoUserAuthRequestPublicKey` remain byte-identical. `DoUserAuthRequestEcc` and `DoUserAuthRequestEd25519` now contain the reviewed label/framing corrections from `tools/security_overrides.py`. The crypto suite independently reconstructs only those exact deltas from the hash-pinned original, requires exact anchor counts, and compares all seven complete functions with the actual generated compilation input. It does not derive its expected bodies from the generator being checked, skip changed functions, normalize away changes or relax the original hashes. Any additional change requires re-review. This is not a whole-generator equivalence test.
|
||||
|
||||
Generated-source line references below retain the initial pre-parser-correction snapshot; use the named functions in the current generated source, whose hash is recorded above.
|
||||
|
||||
Before changing policy, actual Xtensa preprocessing of `ecc.c` confirmed `USE_FAST_MATH`, `ECC_TIMING_RESISTANT`, `WOLFSSL_SMALL_STACK`, `HAVE_ECC_CHECK_KEY`, and internal `HAVE_ECC_CHECK_PUBKEY_ORDER`; neither validation definition was present. `USE_ECC_B_PARAM` and SP math were absent. Initial candidate replay checked the proposed definitions. Following the parent build, **strict mode now passes without injecting policy flags**, checking the saved production compiler/includes/definitions and syntax for twelve library/SSH/application translation units, including `ecc.c` and `signature.c`. Parent-reported `pio run` PASS: **94,340 B linked RAM / 1,768,949 B flash**. This agent did not rerun PlatformIO or inspect a flashed image; compile-profile checks and the supplied link/build result are distinct evidence.
|
||||
|
||||
## P-256 import and ECDH
|
||||
|
||||
Generated `internal.c:11251–11317`, `KeyAgreeEcdh_server`:
|
||||
|
||||
1. Derive `primeId` from negotiated KEX; project policy allows `ecdh-sha2-nistp256` and X25519.
|
||||
2. Initialize public/private keys; attach the session RNG to the ephemeral private key.
|
||||
3. `wc_ecc_import_x963_ex(handshake->e, eSz, pubKey, primeId)` imports the peer point.
|
||||
4. Only after successful import, generate a fresh private key, export its public point, and call `wc_ecc_shared_secret`.
|
||||
5. Errors propagate; both key objects are freed. The policy adds no task, queue, retry loop, protocol control sequence, or new entropy source.
|
||||
|
||||
Installed `ecc.c:10709–11020` parses X9.63 coordinates and selects the curve. At `10993–10996`, `wc_ecc_check_key` is called **only** with `WOLFSSL_VALIDATE_ECC_IMPORT`. Merely compiling `HAVE_ECC_CHECK_KEY` is not equivalent. The inferred-curve `wc_ecc_import_x963` wrapper uses the same implementation.
|
||||
|
||||
Without this flag, `wc_ecc_shared_secret` (`4669–4752`) checks pointers, private-key type, domain metadata and matching curve IDs, not whether the peer coordinates satisfy the curve equation. Its software path reaches `wc_ecc_shared_secret_ex` (`5102`), `wc_ecc_shared_secret_gen_sync` (`4759`), and `wc_ecc_mulmod_ex2` (`4942` vicinity), using curve A, prime and order. No equivalent point-validation call precedes the multiplication. **The absent import validation is applicable, not just an unproven macro concern.**
|
||||
|
||||
With the flag, `_ecc_validate_public_key` (`10495–10703`) checks infinity, coordinate ranges, the curve equation and public-point order; private imports additionally check the private range and private/public consistency where applicable. It loads B from `key->dp->Bf` even when `USE_ECC_B_PARAM` is absent. There is no need to force B storage or reproduce the larger upstream source refactor. The guard rejects configurations that disable the software validator or route it to the known successful hardware stubs; it does not claim arbitrary future backends are validated.
|
||||
|
||||
The work is bounded by the selected curve and existing key/input limits, but **not free**: valid imports incur public validation, including order checking, and private/public consistency checks can add multiplication and allocations. Target latency, memory peaks, stack margins, repeated-handshake/rekey load and the existing deadlines must be measured. Existing admission limiting is not evidence that this cost is harmless.
|
||||
|
||||
## X25519 all-zero result
|
||||
|
||||
Generated `internal.c:11335–11394`, `KeyAgreeCurve25519_server`, explicitly runs `wc_curve25519_check_public` before import, key generation and shared-secret calculation. Installed `curve25519.c:645–710` rejects wrong lengths, zero/one, the high bit, and the upper-end range in the little-endian path. This is **not** a complete low-order rejection rule.
|
||||
|
||||
The real vendor small-math tests exercise two nontrivial low-order u-coordinates that pass that precheck:
|
||||
|
||||
- `e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800`
|
||||
- `5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157`
|
||||
|
||||
`wc_curve25519_shared_secret_ex` (`452–536`) already contains a 32-byte OR reduction under `WOLFSSL_ECDHX_SHARED_NOT_ZERO`. It returns `ECC_OUT_OF_RANGE_E` for an all-zero result before copying it to the caller, then wipes its temporary. Neither the current SSH server helper nor subsequent successful-KEX processing adds an equivalent result test. Enabling this existing check is sufficient for the inspected software path. Tests verify rejection of both inputs, unchanged caller output on rejection, ordinary zero/one precheck rejection, and the valid RFC7748 shared secret. No custom blacklist or small-math/blinding combination is introduced.
|
||||
|
||||
## Raw signatures and CVE-2026-5194 applicability
|
||||
|
||||
**No attacker-selected short digest/OID-confusion trigger was found in the reviewed current raw SSH authentication/signing paths. The separate ECC/Ed25519 label and signature-framing defects have now been corrected by the parser owner, as reviewed below; that does not backport generic crypto API hardening.**
|
||||
|
||||
- `src/ssh_transport.c:489` calls `user_database_authorize_ssh_public_key` before wolfSSH signature verification. `src/user_database.c:185` vicinity checks exact embedded type, exact `nistp256`, 65-byte uncompressed point and end-of-blob; mbedTLS parses and checks the point. `user_database_authorize_ssh_public_key:810` repeats validation and requires exact stored key-type/blob matching for the named account. Thus the server user-key path already has an independent P-256 point-validation boundary, unlike unauthenticated KEX. The new wolfSSL import check is defense in depth here.
|
||||
- Generated `DoUserAuthRequestPublicKey:7324` vicinity derives `hashId = HashForId(pkTypeId)`, obtains `digestSz` from `wc_HashGetDigestSize`, and hashes the session ID and authentication message locally. `HashForId` maps P256 to SHA256: the digest is 32 bytes, not a peer-supplied digest length. The untrusted signature type does not choose an alternate prehash independently of that authorized key type.
|
||||
- Generated `DoUserAuthRequestEcc:6851` vicinity imports Q, converts raw r/s through `wc_ecc_rs_raw_to_sig`, and calls `wc_SignatureVerifyHash` with that locally computed digest and length. Installed `signature.c:131` only checks that the hash type exists, not equality of `hash_len` with its size; `ecc.c:9204` checks r/s ranges but predates the new minimum-digest check. These upstream API weaknesses remain in the dependency, but the reviewed caller supplies the correct size.
|
||||
- `SignHEcdsa:11676` hashes exchange hash H using `HashForId(handshake->pubKeyId)` and signs the resulting full SHA256 digest for the allowed P256 host key. This is distinct from ECDH.
|
||||
- Client host verification (`DoKexDhReply`, generated calls near `5651/5685`) uses `wc_SignatureVerify`, which computes the full digest before verification. Client `BuildUserAuthRequestEcc` derives its digest size from `HashForId(keySigId)`. These are not the intended application's server authentication role. Existing message-order concerns mean role alone must not substitute for validating dispatch reachability.
|
||||
- Certificate variants (`DoUserAuthRequestEccCert`, `BuildUserAuthRequestEccCert`) are under disabled `WOLFSSH_CERTS`. Current Ed25519 auth takes its separate message/streaming-verification path, not an attacker-sized Ed25519ph digest. Ed448 and ML-DSA are not current SSH algorithms.
|
||||
- Host private DER decoding goes through `wc_EccPrivateKeyDecode` in installed `asn.c:35833`; template parsing calls `wc_ecc_import_private_key_ex` near `36033`. Real host tests cover valid SEC1 P256 private/public import without a pre-attached RNG, plus rejection of a corrupted embedded public point. This is not an NVS lifecycle or identity-rotation test.
|
||||
|
||||
The compile-policy mitigation does **not** backport PR10131's global digest-length/OID enforcement. Reassess if new raw APIs, certificates, key types, callbacks or client roles are enabled.
|
||||
|
||||
## Parser-owner corrections reviewed and remaining work
|
||||
|
||||
The following formerly pending gaps are **fixed in the current generated input**, not by the crypto compile flags:
|
||||
|
||||
1. Both key/signature label checks in `DoUserAuthRequestEcc` and `DoUserAuthRequestEd25519` now use OR. Unequal lengths reject before `memcmp`; equal lengths compare the bounded expected span. This rejects equal-length wrong labels and avoids comparing an oversized label against the shorter expected label. Existing error normalization remains unchanged.
|
||||
2. ECC `GetSize` first proves `sz <= signatureSz - i`; converting `sz` to the absolute end with `sz += i` therefore cannot wrap. Both `GetStringRef` calls use that end, not the outer field size. The subsequent `i != sz || sz != pk->signatureSz` rejection requires exact inner and outer consumption before conversion or verification.
|
||||
3. Ed25519 requires `sz == pk->signatureSz - i` before starting streaming signature verification. Trailing bytes outside the declared signature string now reject.
|
||||
|
||||
Reviewed `tools/security_overrides.py` and `tests/wolfssh_parser_contract/{run.py,README.md,auth_framing.c}` against exact original/generated function diffs. These deltas leave ECC digest creation, raw-to-DER conversion and crypto calls, and Ed25519 streamed-message construction unchanged. Valid framing is retained; previously tolerated malformed labels/trailing bytes reject. No exploit or authentication-bypass demonstration is claimed.
|
||||
|
||||
The parser suite uses extracted generated functions, guard pages, instrumented nested reads, UBSan trap mode and **crypto doubles**. Its documented 3,124 cases per stack mode and six guard-removal mutations concern parser gating, not actual signature arithmetic. In contrast, this crypto suite executes real installed vendor arithmetic/ASN/wrappers and separately checks exact generated parser deltas and production compilation settings. Neither suite is an end-to-end SSH handshake test.
|
||||
|
||||
Parent build plus local strict crypto validation resolve the earlier build/profile handoff; no root edit is requested. Remaining work: broader ordering/state-machine and deferred parser/advisory review, standalone ECC curve-name/key-blob semantic validation if that dependency path is used without the application's existing checks, generic PR10131 API hardening as applicability requires, and whole-phase hardware/resource/latency tests. Later changes to audited functions still require explicit delta review, not silent repinning.
|
||||
|
||||
## Tests and limitations
|
||||
|
||||
Commands run for this task:
|
||||
|
||||
```sh
|
||||
python3 tests/wolf_crypto_policy/run.py --host-only
|
||||
python3 tests/wolf_crypto_policy/run.py --candidate
|
||||
# Follow-up after parent firmware build:
|
||||
python3 tests/wolf_crypto_policy/run.py
|
||||
```
|
||||
|
||||
**Follow-up strict suite PASS**, including all host vectors and private ASN-decode cases, seven exact source-body checks with independently specified parser deltas, and production flags without candidate injection. Earlier host-only/candidate runs also passed. Parent build evidence is supplied, not rerun here; no device validation was performed. Details are in the companion [test README](../tests/wolf_crypto_policy/README.md). Coverage includes 20 guard cases; a host CMake fixture executing the real module over a library → SSH → app graph; real installed small-X25519/Ed25519 and TFM P256 crypto; explicit/inferred import rejection for off-curve, infinity, out-of-range, truncated and wrong-tag points; valid ECDH; raw/DER valid and invalid ECDSA verification; private-key ASN import; exact source checks; twelve strict production target macro/syntax checks; four real-settings missing-policy rejection cases.
|
||||
|
||||
Host settings use software TFM, ECC timing resistance and small-stack allocation, but host word size, allocator, OS entropy, compiler and absent ESP acceleration differ from firmware. The CMake fixture is not the full ESP-IDF graph. No exhaustive Wycheproof/fuzz campaign, allocator-failure injection, crypto-suite sanitizer execution, network handshake, real rekey, timing/side-channel measurement, stack/heap reserve measurement, agent-performed firmware link, flashing or hardware validation is claimed. The parent-reported firmware build/link and size figures above do not establish runtime reserves. Test development exposed host fixture omissions (POSIX declarations, wolfmath linkage, filesystem RNG and ASN settings); those were corrected without modifying vendor sources.
|
||||
|
||||
## External sources rechecked
|
||||
|
||||
Read-only retrieval on 2026-09-15:
|
||||
|
||||
- [wolfSSL PR10133 diff](https://github.com/wolfSSL/wolfssl/pull/10133.diff): removes conditional B/on-curve gating and treats `wc_ecc_import_x963_ex` input as untrusted by default in the later tree. This is not a directly applied patch to 5.8.2.
|
||||
- [5.9.1 tagged ChangeLog](https://raw.githubusercontent.com/wolfSSL/wolfssl/v5.9.1-stable/ChangeLog.md), Bug Fixes: explicitly recommends `WOLFSSL_VALIDATE_ECC_IMPORT` for users of older versions. This is the basis for the bounded policy choice.
|
||||
- [wolfSSL PR10374 diff](https://github.com/wolfSSL/wolfssl/pull/10374.diff): makes X25519/X448 all-zero checking opt-out. The existing 5.8.2 opt-in macro enables the inspected equivalent synchronous result check; later nonblocking/TLS changes are not imported.
|
||||
- [wolfSSL PR10131 diff](https://github.com/wolfSSL/wolfssl/pull/10131.diff): certificate signature-OID/key-type consistency plus raw digest-size hardening; used to distinguish the current SSH caller contract from unpatched generic API behavior.
|
||||
|
||||
PR URLs are mutable and are not an archived commit-pinned upstream evidence bundle. Local original source hashes above and the source-contract tests bound the implementation inspected here.
|
||||
@@ -0,0 +1,276 @@
|
||||
# Remaining SSH parser review — 2026-09-16
|
||||
|
||||
## Completed scope and decision
|
||||
|
||||
**This finite review is complete for the current pinned server profile:** PR899
|
||||
key skips and CHANNEL_FAILURE, PR902 service validation, PR918/919 forwarding
|
||||
callback applicability, the remaining generic signature-API caller question, and
|
||||
**exact `DoChannelRequest` name dispatch including application callback gates**.
|
||||
Three bounded current-path corrections are implemented. This is not a complete
|
||||
wolfSSH/wolfSSL audit, a full backport of these PRs, firmware validation, or Phase9
|
||||
sign-off. It supplements the historical/deferred statements in
|
||||
[wolf review](wolf_security_review.md), [key-validation review](ssh_key_validation_review.md)
|
||||
and the [parser contract](../tests/wolfssh_parser_contract/README.md).
|
||||
|
||||
Only `tools/security_overrides.py`, `tests/wolfssh_parser_contract/*`, and this report
|
||||
are owned by this slice. No ordering delta, crypto configuration, version, managed
|
||||
component, application, production generated file, PlatformIO or device change.
|
||||
Concurrent packaging/IDF-review work is unrelated and left untouched.
|
||||
|
||||
| Reviewed item | Current-profile disposition |
|
||||
| --- | --- |
|
||||
| PR899 RSA/ECC unchecked key skips | Confirmed in the pinned client parsers, not reachable through current server dispatch. No speculative client patch. |
|
||||
| PR899 CHANNEL_FAILURE length predicate | Reachable after authentication. Corrected with a bounded exact recipient parser, not just the upstream predicate change. Existing fatal failure policy retained. |
|
||||
| PR902 | **Service names**, not channel callbacks. Exact `ssh-userauth` required by current server handler. Client accept half unused/unmodified. |
|
||||
| PR918/919 | Forwarding global/channel callback handling; compiled out with `WOLFSSH_FWD` absent. No forwarding patch. |
|
||||
| `DoChannelRequest` prefix/NUL name aliases | Closed: all nine name predicates require exact length then exact bytes; branch bodies and unknown-request handling preserved. |
|
||||
| Generic signature API / PR10131 remaining question | Weak generic API remains, but no attacker-selected short digest/OID path in the inspected current SSH caller set. No crypto/API patch justified for this profile. |
|
||||
|
||||
## Exact source and upstream provenance
|
||||
|
||||
The authoritative source is wolfSSH **1.4.20 original + existing ordering delta +
|
||||
existing parser/password edits + the three corrections below**, not installed source
|
||||
alone. `render_entry` verifies original SHA-256 and exact-once edit anchors. Tests
|
||||
render into temporary files; production generated inputs are not overwritten.
|
||||
|
||||
| Input | SHA-256 |
|
||||
| --- | --- |
|
||||
| Original `managed_components/wolfssl__wolfssh/src/internal.c` | `81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9` |
|
||||
| Reviewed prior original+ordering+parser generated `internal.c` | `4948f8c447670eb54153dd1f3db69e4fa3092f7d7f7ed58a18a8fa05fcd168ca` |
|
||||
| Fresh generated `internal.c` after this review | `1fdd608d45c4f33da82b0765dc66e1ec2060e78c744bc539906ef1b8a0f783ae` |
|
||||
| Unchanged `tools/wolfssh_order/delta.json` | `6a81376fe3ffc5f449cde105402963f52d2d78cc844153e869a7e1e0f734fb76` |
|
||||
| wolfSSL 5.8.2 `wolfcrypt/src/signature.c` | `62ab3db3dfd251b2a2c73b69ef05aab6085d2e0d673fd9159514b3ee261cea4f` |
|
||||
|
||||
Fetched official PR patches and independently fetched their commit patches on
|
||||
2026-09-16; each pair was **byte-identical**. Exact archives and URL/SHA-256 records
|
||||
are in `tests/wolfssh_parser_contract/pr*.patch` and `provenance.json`. The runner
|
||||
also pins those commits/hashes/URLs independently; no download occurs during tests
|
||||
or configuration.
|
||||
|
||||
- [PR899 commit d2eeec5e263a4821c90805963eeb0666e99868a6](https://github.com/wolfSSL/wolfssh/commit/d2eeec5e263a4821c90805963eeb0666e99868a6): RSA algorithm skip, ECC curve skip, CHANNEL_FAILURE predicate; Windows file/terminal hunks unused on ESP.
|
||||
- [PR902 commit ffa646a4b9d47d5d9d6127db140c433c58b1e276](https://github.com/wolfSSL/wolfssh/commit/ffa646a4b9d47d5d9d6127db140c433c58b1e276): service request/accept name checks.
|
||||
- [PR918 commit fd82a4bcf55935f0801b14bca6be9c71e32ae914](https://github.com/wolfSSL/wolfssh/commit/fd82a4bcf55935f0801b14bca6be9c71e32ae914): global forwarding callback-before-reply, missing-callback rejection and cancel reply framing.
|
||||
- [PR919 commit 0317c40fc131fab952d291d43c56c7b7ce5f4303](https://github.com/wolfSSL/wolfssh/commit/0317c40fc131fab952d291d43c56c7b7ce5f4303): reject direct-tcpip channel without forwarding callback.
|
||||
|
||||
`review.py` independently reverses only the new notice, the two initial handler
|
||||
changes and nine exact channel-name predicates, then requires the complete prior
|
||||
generated-source hash. The complete original `DoChannelRequest` is recovered by
|
||||
reversing just those predicates. Any other source change, including ordering,
|
||||
client parsing, request branch bodies, password wiping or crypto callers, fails
|
||||
this fence. Original/version/anchor guards remain;
|
||||
no silent repin or removal of existing strict build-source checks.
|
||||
|
||||
## PR899: distinguish client host keys from server authentication
|
||||
|
||||
`ParseRSAPubKey` reads an unchecked algorithm-name length then adds it to its index.
|
||||
`ParseECCPubKey` does the same for the curve name. An oversized/wrapping length can
|
||||
invalidate the intended cursor progression. PR899 replaces these skips with
|
||||
`GetSkip`; it does not establish full curve-name/key-blob semantic validation.
|
||||
These are real dependency defects, not evidence of current server exploitation.
|
||||
|
||||
Exact static call chain: `DoKexDhReply` → `ParsePubKey` → `ParseRSAPubKey` or
|
||||
`ParseECCPubKey`. There is one call to `ParsePubKey`, in `DoKexDhReply`. The current
|
||||
server user-key verifier instead uses `DoUserAuthRequestPublicKey` →
|
||||
`DoUserAuthRequestEcc`/`DoUserAuthRequestEd25519`; it does not call either PR899 key
|
||||
parser. Application authorization additionally validates and exactly matches the
|
||||
stored key blob before successful proof-of-possession verification.
|
||||
|
||||
This exclusion is **not just a server-role assertion**:
|
||||
|
||||
1. `src/ssh_transport.c` creates only `WOLFSSH_ENDPOINT_SERVER` contexts.
|
||||
2. Generated `DoPacket` checks `IsMessageAllowed(..., WS_MSG_RECV)` before dispatch.
|
||||
3. The current two KEX choices set the server expectation to `MSGID_KEXDH_INIT`
|
||||
(30), then `MSGID_NEWKEYS` (21), never `MSGID_KEXDH_REPLY` (31) or GEX reply (33).
|
||||
Both initial KEX and rekey use those restrictions. Before peer KEXINIT or with
|
||||
no nonzero matching expectation, reply messages are rejected as well.
|
||||
4. Both dispatch routes to `DoKexDhReply` (31 and 33) therefore fail the generated
|
||||
expectation gate. Disabled DH/GEX must not be confused with the compiled case
|
||||
labels. The full generated ordering suite was rerun, including wrong-message
|
||||
rejection and both KEX exchanges/rekeys.
|
||||
5. Production macro replay confirms `WOLFSSH_NO_RSA`, `WOLFSSH_NO_DH`, and disabled
|
||||
certificates. Client code is not generally compile-disabled: ECC client parser
|
||||
safety depends on the role/ordering contract, not on dead-code assumptions.
|
||||
|
||||
**Disposition:** retain both key parsers byte-identical, fence the call chain and
|
||||
ordering source, and require this decision to be revisited before enabling client
|
||||
use or widening KEX. No standalone client parsing safety claim.
|
||||
|
||||
### CHANNEL_FAILURE correction and behavior
|
||||
|
||||
The pinned `DoChannelFailure` did not read a recipient at all: `len != 0` returned
|
||||
`WS_BAD_ARGUMENT`; an empty payload returned `WS_CHANOPEN_FAILED`. It did not have
|
||||
the out-of-bounds read implied by blindly treating it as the newer parser. But
|
||||
CHANNEL_FAILURE is a connection-protocol message allowed after authentication by
|
||||
the current server gate, so its malformed-input contract is relevant even though
|
||||
the application has no useful outstanding channel-request workflow needing it.
|
||||
|
||||
The local adaptation validates pointers, uses `GetUint32` on a local cursor,
|
||||
requires exactly one remaining recipient field (`begin == len`), and verifies the
|
||||
recipient through `ChannelFind(..., WS_CHANNEL_ID_SELF)`. Only then does it publish
|
||||
the cursor and return the existing `WS_CHANOPEN_FAILED`. Truncation/wrapping offsets
|
||||
and trailing data return `WS_BUFFER_E`; an unknown recipient returns
|
||||
`WS_INVALID_CHANID`. Failure leaves the caller index unchanged; no channel/session
|
||||
state is mutated. No new queue, allocation, retry or callback is introduced.
|
||||
|
||||
This **does not turn CHANNEL_FAILURE into a recoverable reply** or implement
|
||||
request correlation. Both old nonempty rejection and new parsed failure remain
|
||||
fatal to this application's worker path, which treats only its explicit
|
||||
would-block/receive statuses as retryable. The correction establishes bounded
|
||||
framing and the appropriate existing failure result, not an authentication-bypass
|
||||
or memory-corruption exploit fix. It is a local adaptation, not a full PR899 patch.
|
||||
|
||||
## PR902 and present channel callback policy
|
||||
|
||||
The old bounded `DoServiceRequest` accepted any short service string and advanced
|
||||
to `CLIENT_USERAUTH_REQUEST_DONE`. PR902 really is applicable before user
|
||||
authentication: ordering permits SERVICE_REQUEST at `ACCEPT_KEYED` but does not
|
||||
validate its name. The generated handler now requires length 12 and exact bytes
|
||||
`ssh-userauth`; mismatch returns `WS_INVALID_STATE_E` before index/state publication.
|
||||
Length comparison short-circuits before the fixed-span comparison. Existing bounds
|
||||
and the strict name-capacity limit remain. Unlike upstream's later-tree patch,
|
||||
this subset does not queue a best-effort disconnect: the owner already closes on
|
||||
this error. Valid-service transition is unchanged. `DoServiceAccept` is unchanged;
|
||||
the current server gate rejects SERVICE_ACCEPT before dispatch.
|
||||
|
||||
For the channel/forwarding question:
|
||||
|
||||
- Actual Xtensa replay confirms `WOLFSSH_FWD`, `WOLFSSH_AGENT`, `WOLFSSH_CERTS`,
|
||||
`WOLFSSH_SFTP`, and `WOLFSSH_SCP` absent. PR918's `DoGlobalRequestFwd` call sites
|
||||
and PR919's direct-tcpip handling are under `WOLFSSH_FWD`. Unsupported forwarding
|
||||
channel types take the default unknown-type failure before channel allocation;
|
||||
global forwarding requests fall through to failure if a reply is requested.
|
||||
- `create_context` registers shell, exec and subsystem callbacks, not a channel-open
|
||||
or global-request callback. Default session-channel acceptance is intentional:
|
||||
the pinned handler limits it to one channel, and auth ordering precedes it.
|
||||
- Shell callback marks `shell_requested`; exec/subsystem callbacks reject.
|
||||
`process_handshake` additionally requires an authenticated/current principal,
|
||||
that flag and `WOLFSSH_SESSION_SHELL` before broker/admin routing. Callback
|
||||
rejection alone is not the whole policy: the library stores session type and
|
||||
completion state even for rejected requests, while the application gate stops
|
||||
exec/subsystem admission. The registered callbacks never execute commands.
|
||||
- The pinned generic channel-open callback rejection path appends the channel even
|
||||
after callback failure; no callback is installed here, so that dormant path is
|
||||
not patched by this review. Revisit before adding one. Do not infer that PR919
|
||||
repairs generic channel-open callbacks; its archived hunk is forwarding-only.
|
||||
|
||||
### Completed follow-up: exact channel-request names
|
||||
|
||||
The concrete prefix issue is **closed**, without refactoring the request parser.
|
||||
All nine `WSTRNCMP(type, literal, typeSz) == 0` predicates are replaced by
|
||||
`typeSz == sizeof(literal) - 1 && WMEMCMP(type, literal, sizeof(literal) - 1) == 0`.
|
||||
The length check short-circuits before any comparison on a short name. `memcmp`
|
||||
compares through embedded NULs instead of accepting a terminated prefix. Existing
|
||||
bounded `GetString` copies at most 31 bytes; every recognized name is shorter, so
|
||||
an oversized name truncated to 31 bytes cannot alias a recognized name. No new
|
||||
allocation, helper, protocol response, state transition or feature setting.
|
||||
|
||||
The full handler and application gates were rechecked, not just the shell branch:
|
||||
|
||||
| Exact name | Existing branch / actual production gate |
|
||||
| --- | --- |
|
||||
| `env` | Parses two strings; no environment-setting callback. Always compiled. |
|
||||
| `shell` | Sets shell session type, calls registered `accept_shell`, marks library completion. Application still requires the callback's `shell_requested` flag and shell session type. |
|
||||
| `exec`, `subsystem` | Parse command, store their session type, call registered rejecting callbacks. Library completion is not application admission; no command is executed by these callbacks. |
|
||||
| `pty-req` | Under `WOLFSSH_TERM`, **present** in production. Parses term/dimensions/modes; resize callback is optional and not installed by this application. Does not authorize a shell. |
|
||||
| `window-change` | Requires both `WOLFSSH_TERM` and `WOLFSSH_SHELL`; **absent** because production has no `WOLFSSH_SHELL`. Remains on the unknown path in that profile. |
|
||||
| `exit-status`, `exit-signal` | Under TERM or SHELL; **present** via TERM. Existing payload parsing preserved. |
|
||||
| `auth-agent-req@openssh.com` | Under `WOLFSSH_AGENT`; **absent**. Optional enabled-branch comparison tested only in a host fixture, not enabled in firmware. |
|
||||
|
||||
Empty names, proper prefixes, same-prefix suffixes, same-length wrong bytes,
|
||||
embedded NULs and overlong names no longer select any recognized branch. They take
|
||||
the **unchanged unknown-request path**: no branch callback/session-type update,
|
||||
consume the payload and return success (send channel success if requested).
|
||||
This deliberately does not introduce unknown-request rejection or strict trailing
|
||||
payload validation. Malformed header/name/boolean framing still fails before
|
||||
lookup/callback. Existing exec/subsystem behavior of calling their rejecting
|
||||
callbacks even after a command-payload parse error is also preserved; those real
|
||||
callbacks cannot execute commands. A prior accepted shell does not authorize a
|
||||
later exec/subsystem: the actual application session-type gate still rejects it,
|
||||
with or without a requested reply. No unauthenticated route is introduced.
|
||||
|
||||
`channel_request.c` executes actual generated helpers and the complete handler.
|
||||
`channel_request.py` separately hash-pins and extracts the real `accept_shell`,
|
||||
`reject_channel_request` and complete `process_handshake` bodies, checks their
|
||||
registration/context wiring, and executes them with platform/routing doubles.
|
||||
Tests exercise both broker/admin shell admission and rejection for missing callback
|
||||
context/flag, missing authentication/principal, stale principal, non-shell session
|
||||
and unsupported role. This is not a live broker/admin or task-lifecycle test.
|
||||
|
||||
## Generic signature API: finite caller closure, not library closure
|
||||
|
||||
Rechecked exact pinned `signature.c` and generated SSH calls, supplementing the
|
||||
[key-validation trace](ssh_key_validation_review.md#raw-signatures-and-cve-2026-5194-applicability):
|
||||
|
||||
- `wc_SignatureVerifyHash` and `wc_SignatureGenerateHash_ex` reject zero sizes and
|
||||
invalid hash types but do **not** require the supplied hash length to equal the
|
||||
algorithm's digest length. That generic weakness remains; no global PR10131
|
||||
backport or crypto configuration change is made.
|
||||
- Current server ECC authentication is the sole enabled SSH
|
||||
`wc_SignatureVerifyHash` caller. `DoUserAuthRequestPublicKey` derives the digest
|
||||
size from `HashForId(pkTypeId)` and `wc_HashGetDigestSize`, checks errors, hashes
|
||||
locally, then passes it to `DoUserAuthRequestEcc`. Authorized P256 implies SHA256,
|
||||
32 bytes. A peer signature field does not supply this digest length. The other
|
||||
SSH VerifyHash call is certificate-gated and absent.
|
||||
- `SignHEcdsa` hashes exchange H locally using the negotiated P256 host-key hash and
|
||||
calls `wc_ecc_sign_hash` with the full 32-byte digest. Ed25519 authentication uses
|
||||
streamed message verification, not generic prehash verification.
|
||||
- Both `wc_SignatureVerify` call sites are in blocked client `DoKexDhReply`; that
|
||||
wrapper also derives/hashes a full digest internally. Client ECC auth signing
|
||||
and certificate signing are not current server paths; agent signing is disabled.
|
||||
No application `src/` call to generic signature generation/verification APIs or
|
||||
raw `wc_ecc_sign_hash`/`wc_ecc_verify_hash` was found outside these vendor paths.
|
||||
- No wolfSSL TLS context/connect/accept use was found in application `src/`;
|
||||
HTTPS uses mbedTLS. This is application reachability evidence, **not** a claim
|
||||
that wolfSSL TLS or generic ASN/signature APIs are compiled out or fixed.
|
||||
|
||||
**Closed question:** no short-digest/OID-confusion trigger in this inspected current
|
||||
SSH caller set. **Reopen on:** certificate/client/agent enablement, new raw API
|
||||
callers, key/KEX widening or a changed authorization/hash construction. General
|
||||
wolfSSL TLS/ASN/API auditing remains outside this finite scope.
|
||||
|
||||
## Validation and remaining handoff
|
||||
|
||||
Executed in this slice:
|
||||
|
||||
| Command (all prefixed `CCACHE_DISABLE=1`) | Result |
|
||||
| --- | --- |
|
||||
| `python3 tests/wolfssh_parser_contract/run.py` | PASS: existing 3,258 cases × two stack modes / 11 mutations, plus channel matrix below / 20 additional rejected mutations; independent full-source/provenance fences. |
|
||||
| `python3 tests/wolfssh_parser_contract/review.py --profile` | PASS: actual saved Xtensa feature replay and fresh-source syntax. Explicitly reports production input is the reviewed **prior** baseline. |
|
||||
| `python3 tests/wolfssh_auth_contract/run.py --host-only` | PASS: 135 password/control-flow/wipe cases. |
|
||||
| `python3 tests/sdk_security_overrides/run.py` | PASS: generator and CMake fixtures, including existing SDK corrections. No actual build-registration option used. |
|
||||
| `python3 tests/wolfssh_order_contract/run.py` | PASS: 8,028 full-generated-source/real-crypto checks and seven rejected mutations. No OpenSSH interop option used. |
|
||||
| `python3 tests/wolf_crypto_policy/run.py --host-only` (initial review, not rerun for name-only follow-up) | PASS: 20 guards, PUBLIC CMake fixture, real vendor small-math/P256/ASN vectors. No strict production crypto rerun. |
|
||||
| `python3 tests/ssh_protocol_policy/run.py` (initial review; not rerun while build remains stale) | **Blocked as expected:** `Generated wolfSSH source differs from render_entry; reconfigure the build`. Its strict guard was not changed or bypassed. |
|
||||
|
||||
Follow-up channel matrix: **2,737 cases per stack mode** for production TERM-only,
|
||||
TERM+SHELL, and TERM+SHELL+AGENT profiles; **2,735 per stack mode** for no-terminal
|
||||
and SHELL-only profiles. All five profiles run both stack modes with guard pages
|
||||
and UBSan trap instrumentation. The alternative features are host-only coverage,
|
||||
not production settings. Tests cover every proper prefix, valid names, appended
|
||||
bytes/NUL suffixes, every embedded-NUL/same-length wrong-byte position, 31–65-byte
|
||||
names, every packet/payload truncation, oversized/wrapping declared lengths,
|
||||
nonzero offsets, want-reply both ways, known/unknown channels, PTY callbacks and
|
||||
real application admission. Instrumented comparison asserts that the compared
|
||||
span equals the initialized name length. **18 name/length mutations and two real
|
||||
application shell-admission gate mutations are rejected**, in addition to the
|
||||
existing 11 parser mutations. Ordering/auth/SDK suites and Xtensa profile/syntax
|
||||
were rerun successfully after the follow-up; `git diff --check` also passed.
|
||||
|
||||
The new parser tests use crypto/channel doubles; they establish dispatch/gating
|
||||
and preserved state contracts, not cryptographic arithmetic or actual channel lifetime. The
|
||||
ordering suite supplies separate full-library host evidence. No resource/timing,
|
||||
firmware link, device, broad fuzzing, new network SSH or Phase9 acceptance claim.
|
||||
|
||||
Remaining handoff is bounded:
|
||||
|
||||
1. Parent-approved regeneration/build and strict production-source suites after
|
||||
integrating concurrent work; production generated bytes are deliberately stale.
|
||||
2. The requested channel-name dispatch and callback-gate review is **complete**.
|
||||
Existing unknown/trailing-payload behavior is explicitly preserved, not certified
|
||||
as a generally strict parser and not expanded into another parser inventory.
|
||||
3. Existing whole-phase hardware/rekey cleanup, compatibility and heap/stack/CPU
|
||||
gates remain as recorded in the ordering/key-validation reviews. No new target
|
||||
cost measurement is claimed for these allocation-free checks.
|
||||
4. Dormant client-key/forwarding/certificate/generic-API defects are documented
|
||||
profile exclusions, not fixed dependency features. Re-audit only if those
|
||||
capabilities or the pinned source/role/ordering contract change.
|
||||
@@ -1,6 +1,6 @@
|
||||
# User administration and authentication tests
|
||||
|
||||
Reusable procedures, **not execution evidence**. [Phase 8 is accepted](roadmap.md#phase8-acceptance-evidence); overall 8D.22 user sign-off does not assert that every checklist item below ran. Historical 8A/B sections describe their original baselines only; current cookie authentication, removed bootstrap/credentials and UART0-only recovery override them. Use current contracts and the integrated regression section for present behavior. Never log generated/entered passwords, cookies/tickets or private/verifier material.
|
||||
Reusable procedures, **not execution evidence**. [Phase 8 is accepted](web_administration_acceptance.md); overall 8D.22 user sign-off does not assert that every checklist item below ran. Historical 8A/B sections describe their original baselines only; current cookie authentication, removed bootstrap/credentials and UART0-only recovery override them. Use current contracts and the integrated regression section for present behavior. Never log generated/entered passwords, cookies/tickets or private/verifier material.
|
||||
|
||||
## Current Broker management regression procedure — 8D.16
|
||||
|
||||
@@ -26,7 +26,7 @@ The [Network contract](web_administration.md#network) defines the current byte c
|
||||
|
||||
## Current legacy-removal regression procedure
|
||||
|
||||
The legacy bootstrap/credential/reconciliation instructions in the phase baselines below are **historical only**, superseded by [legacy credential removal](roadmap.md#phase8-legacy-credential-compatibility) and the current [command reference](command_reference.md). Basic authentication is also historical; current HTTPS uses cookie login. The current overrides apply to later Phase 8C/browser procedures too: no `user bootstrap` or `web credentials` command remains, first-admin creation uses normal UART0 `user add`, and recovery rebuilds empty. Never treat the checklist below as evidence of execution.
|
||||
The legacy bootstrap/credential/reconciliation instructions in the phase baselines below are **historical only**, superseded by [legacy credential removal](legacy_credential_removal.md) and the current [command reference](command_reference.md). Basic authentication is also historical; current HTTPS uses cookie login. The current overrides apply to later Phase 8C/browser procedures too: no `user bootstrap` or `web credentials` command remains, first-admin creation uses normal UART0 `user add`, and recovery rebuilds empty. Never treat the checklist below as evidence of execution.
|
||||
|
||||
1. On a disposable controlled NVS image with only `user_db/database` missing, boot and confirm an empty database is persisted with zero accounts/admins and no imported credential. Reboot and confirm it remains empty. Keep physical UART0 attached; do not factory-erase the device for this test.
|
||||
2. Run `user add maint admin` on UART0, check hidden password confirmation and cancellation, then confirm account/password persistence after reboot. Separately exercise `user add operator user --generate` and secure one-time display. Final-administrator delete/demotion must still fail. Existing SSH own-password generation restrictions and typed browser generated-password support remain unchanged.
|
||||
@@ -36,7 +36,7 @@ The legacy bootstrap/credential/reconciliation instructions in the phase baselin
|
||||
6. Confirm help/completion/status expose no legacy credential/bootstrap operation or secret. Removed commands must reject without mutation. Exercise retained user generation and `web certificate rotate --force` through supported frontends. `web reset --force` must change TLS only, require new certificate trust/login after HTTPS restart, leave user credentials/generations unchanged, and not revoke unrelated SSH sessions.
|
||||
7. Recheck UART0/native USB availability and broker one-writer/isolated-observer behavior with network authentication unavailable. Record only nonsecret counters and telemetry. Older v1-only firmware cannot read HTTPS v2; logical NVS replacement is not secure flash erasure and no factory erase is required.
|
||||
|
||||
Acceptance and prior evidence limits are recorded in [legacy compatibility](roadmap.md#phase8-legacy-credential-compatibility) and [overall acceptance](roadmap.md#phase8-acceptance-evidence); the preceding procedure is not an execution log.
|
||||
Acceptance and prior evidence limits are recorded in [legacy compatibility](legacy_credential_removal.md) and [overall acceptance](web_administration_acceptance.md); the preceding procedure is not an execution log.
|
||||
|
||||
## Historical Phase 8A baseline — role-based database and UART0 administration
|
||||
|
||||
@@ -204,7 +204,7 @@ Finally, issue commands concurrently from UART0 and admin SSH, including `user l
|
||||
|
||||
## Integrated web administration regression procedure
|
||||
|
||||
Current retained-scope checks for future changes. [8D.22 sign-off](roadmap.md#phase8-acceptance-evidence) closes the phase; these are **not additional acceptance conditions or claims of individual execution**. Record exact revision, workload/client mix, duration, counters and heap/stack evidence for any new run. Do not treat host doubles as target timing/power-loss proof or reserve approval. Prepare UART0 recovery/native USB before disruptive tests and do not erase persisted data without explicit approval.
|
||||
Current retained-scope checks for future changes. [8D.22 sign-off](web_administration_acceptance.md) closes the phase; these are **not additional acceptance conditions or claims of individual execution**. Record exact revision, workload/client mix, duration, counters and heap/stack evidence for any new run. Do not treat host doubles as target timing/power-loss proof or reserve approval. Prepare UART0 recovery/native USB before disruptive tests and do not erase persisted data without explicit approval.
|
||||
|
||||
### 1. Integrated login and authorization
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Web administration contracts
|
||||
|
||||
Current implementation contracts; [Phase 10 firmware upload is complete by explicit user acceptance on 2026-09-18](roadmap.md#phase-10--simple-admin-web-firmware-upload), confirming upload and normal operation, not specific fault or recovery checks. Phase status and executed-evidence limits belong in the [roadmap](roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and [acceptance record](roadmap.md#phase8-acceptance-evidence), not in implementation timelines. [Regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) describe checks, not results. Source is authoritative; start with the [code map](agent/code-map.md).
|
||||
Current, accepted firmware behavior. Phase status and executed-evidence limits belong in the [roadmap](roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and [acceptance record](web_administration_acceptance.md), not in implementation timelines. [Regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) describe checks, not results. Source is authoritative; start with the [code map](agent/code-map.md).
|
||||
|
||||
## Authentication and admission
|
||||
|
||||
@@ -9,7 +9,7 @@ Current implementation contracts; [Phase 10 firmware upload is complete by expli
|
||||
- Mutation admission requires current cookie/principal, strict Origin and CSRF validation; administration additionally requires current `admin`. Normal users retain serial/status but cannot invoke administration directly. Authentication POST fetches use CORS mode with fixed same-origin URLs and same-origin credentials: do not accept Origin `null` to compensate for browser no-referrer behavior.
|
||||
- Session-store initialization is part of admitted HTTPS start; authentication failure gates HTTPS. Failed start/accepted stop disables and wipes session state. Logout invalidates only the originating session before socket cleanup; account mutation invalidates that account's sessions/tickets, including deletion/recreation, without revoking unrelated accounts. Currentness checks remain authoritative if best-effort notifications fail.
|
||||
- Four serial tickets and two admin tickets are digest-only, single-use, 30-second, session/principal-bound records. Cookie/Origin/ticket/currentness and transport admission precede explicit WebSocket 101. Store RNG/SHA/database calls run outside short spinlocks; IDs/expiry/epochs fence stale publication without nested store/transport locks.
|
||||
- HTTPD remains bounded to six sockets, two serial WebSockets, one admin WebSocket and 40 method/path handlers; LRU eviction is disabled. Sessions, sockets, tickets and the two shared remote-console slots are separate capacity limits. Optional settings/admin failures preserve unrelated routes where their initialization contract permits; UART0 and native USB remain independent of web readiness.
|
||||
- HTTPD remains bounded to six sockets, two serial WebSockets, one admin WebSocket and 39 method/path handlers; LRU eviction is disabled. Sessions, sockets, tickets and the two shared remote-console slots are separate capacity limits. Optional settings/admin failures preserve unrelated routes where their initialization contract permits; UART0 and native USB remain independent of web readiness.
|
||||
|
||||
`web_httpd_adapter` alone accesses private IDF 5.5.0 HTTPD state. It rejects duplicate/ambiguous headers, postpones 101 until admission, and wipes consumed header scratch while preserving right-aligned unread bytes. Optional Settings registration stages descriptor/name allocations before publishing either, avoiding the pinned public registration failure path. Re-audit these private boundaries on SDK upgrades and same-version SDK patches: the version guard does not detect patches that retain the same version number. HTTPD response headers are pointer-backed, not copied; both `Set-Cookie` value buffers must remain valid and distinct through response send. Do not reuse or wipe those buffers before sending completes. Do not enable header/ticket debug logging. Auth documents, scripts and sensitive responses are no-store with CSP/no-referrer/frame-denial protections; authored loader changes require matching CSP hashes. Generated assets are not a normal documentation/build output.
|
||||
|
||||
@@ -28,14 +28,12 @@ HTTPD alone owns browser-admin socket IO and its 1,552-byte PSRAM-only payload.
|
||||
Typed Settings permissions do not expand shell permissions. Parsed canonical arguments, not raw prefixes or completion suggestions, control admission:
|
||||
|
||||
- Browser `web` permits only `web status`, `web stop`, and exact `web certificate rotate --force`; certificate info/reset, diagnostics/performance and other web forms are denied.
|
||||
- Browser `wifi` uses the canonical command handler, including bare `wifi` (status), profile/AP settings, persistence, lifecycle and network diagnostics. Profile/AP secret entry uses the shared hidden prompt: no input echo, history or completion of secret bytes; cancellation, disconnect and failed currentness discard the input. Explicit `wifi ap show-secret` reveals the AP password only in the invoking admin terminal; routine status and completion do not reveal credentials. Browser `mdns` still permits only exact `mdns status`.
|
||||
- Browser `wifi`/`mdns` permit only status. Network mutation belongs to typed Settings or UART0/admin SSH.
|
||||
- Browser `user` permits status/list/show and interactive add/password plus forced role/delete for **other accounts only**. Self changes, generated passwords, key commands and recovery are denied there; typed Accounts supports the separately bounded self/generated/key workflows.
|
||||
- Browser SSH stop/disconnect/reset and host-key mutation are denied; typed SSH Settings has its own safe owner path. Do not claim full browser-shell parity.
|
||||
- Browser `reboot` and owner-relative `exit` are supported. First-admin provisioning uses normal `user add` on UART0; unavailable-database recovery is UART0-only. The legacy `user bootstrap` and web credential commands no longer exist.
|
||||
|
||||
Wi-Fi changes use the existing canonical manager path, not deferred browser drain/acknowledgement. Stop, reconnect, profile/AP edits, load/defaults/reset can disconnect network clients before output arrives. A lost result does not cancel admitted work: reconnect and inspect before retrying; use UART0 if networking is unavailable. Changes are RAM-only until `wifi save`, except `wifi reset`, which also persists defaults. Native USB remains independent UART1 access, not an administration console.
|
||||
|
||||
Deferred self-affecting shell actions use the existing bounded drain/control path (up to ten seconds plus a short delay), not guaranteed peer delivery. Browser certificate rotation hands a typed action after drain/200 ms to the existing 12 KiB dispatcher, never crypto/NVS on the 4 KiB control stack. Pending input is discarded through execution, and an executing slot remains reserved across self-detach. UART0/admin SSH retain canonical recovery actions.
|
||||
Self-affecting shell actions use the existing bounded drain/control path (up to ten seconds plus a short delay), not guaranteed peer delivery. Browser certificate rotation hands a typed action after drain/200 ms to the existing 12 KiB dispatcher, never crypto/NVS on the 4 KiB control stack. Pending input is discarded through execution, and an executing slot remains reserved across self-detach. UART0/admin SSH retain canonical recovery actions.
|
||||
|
||||
## Typed settings API and operation lifetime
|
||||
|
||||
@@ -45,7 +43,7 @@ All routes below are under `/api/settings/`. Each domain has bodyless GET snapsh
|
||||
|---|---|---|
|
||||
| `web_serial_settings`, serial service | `serial` / `serial-operation` | 256 / 256 / 96 |
|
||||
| `web_account_settings`, user database | `accounts` / `account-operation` | 768 / 1024 (accounts), 512 (keys) / 96 |
|
||||
| `web_network_settings`, Wi-Fi + mDNS | `network` / `network-operation` | 768 / 2304 / 128 |
|
||||
| `web_network_settings`, Wi-Fi + mDNS | `network` / `network-operation` | 768 / 2048 / 128 |
|
||||
| `web_display_settings`, local status UI | `display` / `display-operation` | 256 / 128 / 96 |
|
||||
| `web_broker_settings`, session broker | `broker` / `broker-operation` | 256 / 2048 / 96 |
|
||||
| `web_ssh_settings`, SSH owner/security | `ssh` / `ssh-operation` | 256 / 768 / 96 |
|
||||
@@ -69,7 +67,7 @@ Separate bodyless POST `accounts/generate-password` returns one 24-character val
|
||||
|
||||
### Network
|
||||
|
||||
Wi-Fi config/runtime is one zero-wait consistent projection; mDNS is a separate projection, not cross-domain atomic authorization. Four stable profiles carry enabled/priority/security/SSID/password-configured metadata. `mixed` means WPA2-or-stronger, not open. `announced` is expected STA announcement, not verified DNS. Runtime `ipv6_addresses` contains up to three preferred addresses from the same snapshot as the availability flags, encoded as eight lowercase four-digit hextets without a zone. Network settings display separate link-local, ULA and GUA lists; absent groups show `none`. Link-local access needs the client's interface scope, and address presence does not assert a route or Internet reachability. Encoding uses the existing snapshot only, not netif/driver calls on HTTPD; the 2304-byte response buffer adds 256 bytes of bounded stack storage.
|
||||
Wi-Fi config/runtime is one zero-wait consistent projection; mDNS is a separate projection, not cross-domain atomic authorization. Four stable profiles carry enabled/priority/security/SSID/password-configured metadata. `mixed` means WPA2-or-stronger, not open. `announced` is expected STA announcement, not verified DNS.
|
||||
|
||||
SSID wire values are reversible **bytes**, maximum 32: printable ASCII, standard single-character JSON escapes and `\u00HH`, with no raw non-ASCII, non-byte Unicode or surrogates. NUL/non-UTF-8 round-trip. UI text is UTF-8-encoded before byte serialization; exact reversible text or literal hex preserves existing bytes and BOM, with no silent replacement/truncation.
|
||||
|
||||
@@ -113,14 +111,6 @@ HTTPS ordering is **commit → stop → restart**. Precommit generation/RNG/stor
|
||||
|
||||
Save drafts; rotation/restart invalidates all web logins and closes both browser routes. Inspect `web certificate info` through trusted UART0, verify fingerprint before renewing trust, then reload/sign in freshly. Accepting a warning alone is not trusted verification. Use canonical UART0/admin SSH `web stop` / `web start` for retained-server recovery. Network/SSH/USB are not stopped by HTTPS-only operations; whole-device reboot interrupts all transports and loses unsaved RAM.
|
||||
|
||||
### Application firmware upload
|
||||
|
||||
The admin-only firmware card is in **Settings → HTTPS / Reboot**. It sends a raw `POST /api/firmware` with `application/octet-stream`, known length, session cookie, same-origin Origin and `X-CSRF-Token`; it is not a JSON Settings operation or dispatcher/result-slot workflow. Standard SDK OTA APIs stream through a 4 KiB buffer to the inactive application slot and select it only after validation/currentness checks. Only that slot and `otadata` are written; NVS/data partitions are untouched.
|
||||
|
||||
One upload reserves HTTPS lifecycle/identity and excludes ordinary software reboot. The synchronous HTTPD handler can stall/drop browser serial/admin sessions. Its 120-second total receive-loop and ten-second stall checks are not preemptive flash deadlines or uninterrupted-traffic guarantees. Successful response send schedules a delayed reboot, not proof of peer receipt. Failed response after commit leaves the image selected, schedules no automatic reboot, releases reservations for manual reboot, and latches subsequent admissible uploads to 409 `firmware_selected_reboot_required` until reset. Never automatically retry an uncertain outcome.
|
||||
|
||||
See [firmware update](roadmap.md#phase-10--simple-admin-web-firmware-upload) for wired-first installation, exact image selection, manual recovery, acceptance limits and reusable future hardware regression checks. Phase 10 is accepted; there are no signature/version policies or automatic rollback.
|
||||
|
||||
### SSH
|
||||
|
||||
GET `ssh` supplies service/session state plus identity generation, fixed P-256 algorithm, unpadded OpenSSH `SHA256:` base64 fingerprint and rotatable flag. Service actions use exactly `action`, `generation`, `target`; rotate adds `identity_generation` and requires target zero. Start/stop and exact-session disconnect use published state, saturated service generation and the command mutex; exhausted SSH session slots retire rather than wrap. Disconnect success is an owner close request, not completed teardown. HTTPD never calls wolfSSH or waits for the SSH task.
|
||||
@@ -133,6 +123,6 @@ SSH changes leave invoking HTTPS available, so they use the ordinary ID-dispatch
|
||||
|
||||
## Diagnostics, recovery and scope boundaries
|
||||
|
||||
See [admission diagnostics](web_admission_diagnostics.md), [ordinary HTTPS idle cleanup](https_idle_cleanup.md), [throughput diagnostics](web_throughput_diagnostics.md) and [legacy storage compatibility](roadmap.md#phase8-legacy-credential-compatibility). Broker read means transport handoff, not peer receipt; capture non-consuming counters before disconnect. TLS `-0x004C` is generic NET_RECV_FAILED, not evidence of OOM. Resource minima and counter observations require attribution, not inferred causes.
|
||||
See [admission diagnostics](web_admission_diagnostics.md), [ordinary HTTPS idle cleanup](https_idle_cleanup.md), [throughput diagnostics](web_throughput_diagnostics.md) and [legacy storage compatibility](legacy_credential_removal.md). Broker read means transport handoff, not peer receipt; capture non-consuming counters before disconnect. TLS `-0x004C` is generic NET_RECV_FAILED, not evidence of OOM. Resource minima and counter observations require attribution, not inferred causes.
|
||||
|
||||
Phase 8D.15's dedicated typed network-diagnostics UI/API was removed: diagnostics remain shell-based, subject to frontend policy. The unimplemented 8D.19 ordinary browser-session/native-USB control expansion was removed; existing SSH controls remain. No full shell parity, browser identity recovery/reset/export, encryption or secure boot is implied by Phase 8 acceptance. Phase 10 application upload is complete by explicit user acceptance of upload and normal operation; unreported fault, NVS-comparison, power-loss and recovery checks are not implied. UART0 is the administrative recovery authority; native USB is binary-transparent, network-independent UART1 access. Neither permits bypassing the broker's single writer or recalling already-admitted work.
|
||||
Phase 8D.15's dedicated typed network-diagnostics UI/API was removed: diagnostics remain shell-based, subject to frontend policy. The unimplemented 8D.19 ordinary browser-session/native-USB control expansion was removed; existing SSH controls remain. No full shell parity, browser identity recovery/reset/export, encryption, secure boot or OTA is implied by acceptance. UART0 is the administrative recovery authority; native USB is binary-transparent, network-independent UART1 access. Neither permits bypassing the broker's single writer or recalling already-admitted work.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Web administration acceptance
|
||||
|
||||
## Phase 8D.22 sign-off — 2026-09-13
|
||||
|
||||
The user explicitly accepted the tested firmware: **“Yep, I tested the firmware thats a 8d.22 signoff.”** Phase 8D is complete for its retained scope. This supersedes earlier per-slice target-pending, parent-review and integration-acceptance gates; it does not require additional testing to establish the user's sign-off. Phase 8A/B/C were already recorded as complete and target-hardware validated in the roadmap, so [Phase 8 is complete](roadmap.md#phase-8--role-based-users-and-administrative-access--complete).
|
||||
|
||||
Accepted functionality comprises cookie authentication, isolated browser serial/admin terminals, typed Serial/Accounts/authorized-key/Network/Display/Broker/SSH/HTTPS settings, contextual controls, confirmed generation-safe writer/service/identity actions and retained UART0/native-USB recovery. [Current contracts](web_administration.md) define actual permissions, bounded failures and partial effects; acceptance is not a claim of unrestricted browser-shell parity.
|
||||
|
||||
Scope decisions remain effective: 8D.15 dedicated typed network diagnostics was removed (shell diagnostics retained); the unimplemented 8D.19 ordinary browser-session/native-USB control remainder was removed (SSH controls retained). Browser identity Reset/recovery/export was not added. Phase 9 security/production hardening is next only on a separate user request.
|
||||
|
||||
## Latest firmware evidence
|
||||
|
||||
The **prior final firmware build passed**, reporting **100,556 bytes linked RAM / 1,828,573 bytes flash**, at **160 MHz**. This is recorded prior build evidence, not a build run during documentation consolidation. Host regression suites and independent implementation reviews were previously reported passing; no new host/runtime test pass is asserted here.
|
||||
|
||||
Latest user telemetry, bytes:
|
||||
|
||||
| Sample / memory capability | Free | Minimum free | Largest block |
|
||||
|---|---:|---:|---:|
|
||||
| Boot internal 8-bit | 59,808 | 58,840 | 31,744 |
|
||||
| Boot internal DMA | 52,052 | 51,084 | 31,744 |
|
||||
| Boot PSRAM | 8,196,968 | 8,183,972 | 8,126,464 |
|
||||
| Loaded after burst, internal 8-bit | 31,508 | 2,052 | 18,432 |
|
||||
| Loaded after burst, internal DMA | 23,752 | 460 | 18,432 |
|
||||
| Loaded after burst, PSRAM | 8,136,624 | 8,065,972 | 7,995,392 |
|
||||
|
||||
Loaded SSH minimum-free stack was **15,028 bytes**. The capture had two active SSH sessions across the serial/admin roles, two serial WebSockets and USB, with SSH holding the serial writer. Browser admin had been used and then closed; it was **not active in the captured loaded sample**. Web send/queue/protocol error counters were zero; SSH IO errors were zero, with one handshake failure and one session revocation retained without attributing a cause.
|
||||
|
||||
Latest broker/serial counters were not supplied, so these transport counters do **not** establish an exact latest zero-drop or byte-integrity result. No latest full-mix-with-browser-admin-active, individual fault-injection, exact duration, soak, cleanup-cycle or reserve-floor result is inferred. Generic SDK TLS `-0x004C` / NET_RECV_FAILED is not an OOM diagnosis. Two boot authentication failures could plausibly involve stale browser cookies, but that explanation is unconfirmed.
|
||||
|
||||
## Earlier acceptance retained without replaying the timeline
|
||||
|
||||
- M1 browser login/logout and M2 shared browser administration were explicitly accepted by the user; later Serial/account/Network presentation and legacy-credential cleanup also received scoped acceptance.
|
||||
- The user explicitly accepted the combined binary WebSocket-send fix at **160 MHz, 230400 baud with the full client mix, including browser admin**. That prior acceptance stands independently of the latest capture's closed browser-admin socket. It does not imply an unreported latest exact counter comparison or soak duration. Keep the combined send and bounded failed-send isolation, not the earlier frequency-only experiment.
|
||||
- Ordinary HTTPS idle cleanup was accepted as working; that is not a guarantee against all future admission failures or owner delays.
|
||||
|
||||
## Nonblocking follow-ups and evidence limits
|
||||
|
||||
The extremely low internal/DMA lifetime minima remain an unresolved transient-headroom follow-up, **not a blocker reopening 8D sign-off and not an approved reserve**. Capability pools overlap; summed per-region lifetime minima can be conservative/non-simultaneous and do not prove an allocation failure. Do not add internal and DMA numbers together or attribute an error to memory pressure without correlated evidence. HTTPD/dispatcher stack margins, peak correlation, allocation reserve policy and long-run cleanup/soak evidence remain distinct future measurements.
|
||||
|
||||
[Regression procedures](user_administration_tests.md#integrated-web-administration-regression-procedure) and focused test runners remain available for future changes; listing them is not evidence they all ran on hardware. Documentation-only consolidation changes no firmware, configuration, generated assets or test implementation and performs no build/upload/erase/commit. No new Phase 9 work is authorized by this acceptance.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Web admission diagnostics
|
||||
|
||||
Current default-disabled instrumentation, not an admission fix or an allocation-failure detector. `src/web_diagnostics.{c,h}` observes public synchronous HTTPS create/close callbacks and the four serial/admin ticket/upgrade handlers. It adds no owner task, queued probe or socket-capacity change. See [ordinary idle cleanup](https_idle_cleanup.md) for the independent cleanup policy and [acceptance evidence](roadmap.md#phase8-acceptance-evidence) for user reports.
|
||||
Current default-disabled instrumentation, not an admission fix or an allocation-failure detector. `src/web_diagnostics.{c,h}` observes public synchronous HTTPS create/close callbacks and the four serial/admin ticket/upgrade handlers. It adds no owner task, queued probe or socket-capacity change. See [ordinary idle cleanup](https_idle_cleanup.md) for the independent cleanup policy and [acceptance evidence](web_administration_acceptance.md) for user reports.
|
||||
|
||||
## Usage and interpretation
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Current contract and accepted fix
|
||||
|
||||
Default-disabled instrumentation measures broker fan-out and binary WebSocket TX, not CPU usage, peer receipt or pure scheduler latency. The user separately accepted the combined binary header/payload-send fix at **160 MHz / 230400 baud with full mix including browser admin**. [Acceptance evidence](roadmap.md#phase8-acceptance-evidence) distinguishes that report from the latest capture and unmeasured reserves. The old frequency-only experiment is not the retained solution.
|
||||
Default-disabled instrumentation measures broker fan-out and binary WebSocket TX, not CPU usage, peer receipt or pure scheduler latency. The user separately accepted the combined binary header/payload-send fix at **160 MHz / 230400 baud with full mix including browser admin**. [Acceptance evidence](web_administration_acceptance.md) distinguishes that report from the latest capture and unmeasured reserves. The old frequency-only experiment is not the retained solution.
|
||||
|
||||
`web_httpd_ws_send_binary` is an IDF-5.5.0-pinned owner-only session-override send with a bounded 516-byte header/payload scratch copy and 512-byte payload. Generation validation and one outstanding item per slot remain. A non-full send installs a reject-only override before close/shutdown, preventing SDK automatic PONG/CLOSE from reentering TLS with different arguments after incomplete output, even if shutdown fails. No retry/replay; HTTPD owns TLS destruction. Text/control/admin retain the SDK sender. One API call does not promise one TLS record/packet or peer receipt. Keep CPU160, priorities, scheduling and 4096-byte broker/512-byte web payload bounds unless separately changing them with evidence.
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# Wi-Fi management-frame security update plan
|
||||
|
||||
Initial review date: **2026-09-16**; integration status updated **2026-09-18**. The original review was a focused documentation-only follow-up to [dependency advisory coverage](dependency_advisory_coverage.md#new-release-note-finding-wi-fi-management-frame-corruption). **At that initial review, no correction was implemented, exploit reproduced, build run, device accessed, or upgrade approved.** The fix-bearing complete IDF 5.5.3 vendor bundle is now integrated in the validated candidate; this is not radio-hardware vulnerability closure or deployment approval. See [current integration evidence](idf_candidate_integration.md#final-integration-evidence--2026-09-18) and [semantic rebase review](idf_553_rebase_review.md).
|
||||
|
||||
## Decision
|
||||
|
||||
**Prefer a complete, immutable Espressif IDF 5.5 maintenance-release bundle, integrated through a compatible pinned PlatformIO package/toolchain, over transplanting Wi-Fi archives into IDF 5.5.0.** IDF **5.5.3 is the verified fix-bearing reference candidate/minimum demonstrated release here**, not a recommendation to deploy it in preference to all later maintenance releases. The earlier coverage records 5.5.4/5.5.5; selecting a newer deployment candidate requires the same immutable provenance and compatibility gates below, not a repeat broad advisory audit.
|
||||
|
||||
Do not change PMF/WPA3 policy as an automatic mitigation. Do not replace only `libnet80211.a`, edit only a callback declaration, relax source guards, or overwrite the installed SDK. The vendor correction is integrated in the fresh candidate build; deployed old firmware remains exposed, and target/radio validation remains pending. PMF/WPA3 has not been weakened.
|
||||
|
||||
## Current applicability and trigger limits
|
||||
|
||||
Rechecked authoritative source `src/wifi_manager.c:315–363`:
|
||||
|
||||
- SoftAP uses `WIFI_AUTH_WPA2_WPA3_PSK`, four clients, `pmf_cfg.capable=true`, `required=false`, `WPA3_SAE_PWE_BOTH`. AP/APSTA are real supported paths, including live fallback transitions (`set_runtime_ap_enabled`). Optional PMF is **not PMF disabled**.
|
||||
- Station uses PMF capability in both profiles; mixed has WPA2 minimum/optional PMF, WPA3 requires PMF. Do not downgrade station profiles to address a demonstrated SoftAP trigger.
|
||||
- Existing generated `sdkconfig.h` identifies ESP32-S3 and enables SoftAP, WPA3 SAE, SoftAP SAE, SAE H2E and SAE-PK support. This is build metadata, not proof of a running device's current mode or negotiated association.
|
||||
- Architecture: the permanent Wi-Fi manager owns driver operations and bounded events; application NVS is authoritative, driver storage is RAM-only, AP policy is off/fallback/always. Working edits require explicit save. Preserve manager ownership, secret wiping and secret-free status. UART0 recovery and native USB UART1 access must remain independent of network success. Broker single-writer/multiple-isolated-observer behavior is unchanged.
|
||||
|
||||
The official contained fix says that when the supplicant sends an **authentication response for an already-connected station, with keys installed after a successful four-way handshake**, management encryption was selected from `bss->pmf_enable` without checking whether the frame was robust. The encryption code assumed header space that had not been allocated, causing memory corruption. The fix checks robust-management-frame status before encryption.
|
||||
|
||||
This supports a current-feature exposure candidate when SoftAP is active with the relevant station/key/PMF state. Radio delivery of the relevant management exchange, rather than application HTTPS/SSH authentication, is the pertinent access path (inference from the described Wi-Fi operation). The public text does **not** establish whether an attacker must know the PSK, personally complete association, or can spoof an existing station's exchange. It supplies no packet-level proof, CVSS, reliable arbitrary-code-execution claim, or exhaustive affected-mode matrix. Do not characterize this as proven unauthenticated remote exploitation, nor assume application login protects it. STA-only exclusion is limited to the described AP-response path, not a blanket clean bill for the opaque bundle.
|
||||
|
||||
The same vendor change also fixes an RSN-IE length/allocation mismatch, deauthentication-reason parsing, and default SAE PWE values. The project's short configured IE set has not been shown to trigger the allocation issue; do not conflate that uncertainty with the established old ABI/bundle identity.
|
||||
|
||||
## Official immutable provenance
|
||||
|
||||
Official sources fetched in this review:
|
||||
|
||||
1. [5.5.3 release](https://github.com/espressif/esp-idf/releases/tag/v5.5.3), also [release API](https://api.github.com/repos/espressif/esp-idf/releases/tags/v5.5.3): explicitly names the management-frame memory-corruption correction under `a3927641`.
|
||||
2. [Merge commit](https://github.com/espressif/esp-idf/commit/a3927641bfd0aedfaab3514cba3d7fb01378ce4d), checked through its official commit API: **`a3927641bfd0aedfaab3514cba3d7fb01378ce4d`**. Its title emphasizes SAE defaults; the title alone omits the security detail.
|
||||
3. [Contained fix patch](https://github.com/espressif/esp-idf/commit/caf4cad3b422be5c2f93afeff4cd07796a262b46.patch): **`caf4cad3b422be5c2f93afeff4cd07796a262b46`**, “Backport some wifi fixes,” with the trigger explanation. This is the merge's second parent, not a conflicting identification.
|
||||
4. [Annotated release tag object](https://api.github.com/repos/espressif/esp-idf/git/tags/b31fcc7a314a44ad992b58f589f7d1d8a4fadff6): tag object **`b31fcc7a314a44ad992b58f589f7d1d8a4fadff6`** resolves to IDF commit **`2c211b236707889e8400c4dc5644dd5c4ee071e0`**. [Immutable comparison](https://api.github.com/repos/espressif/esp-idf/compare/a3927641bfd0aedfaab3514cba3d7fb01378ce4d...2c211b236707889e8400c4dc5644dd5c4ee071e0) reports ahead 195 / behind 0, establishing inclusion of the merge.
|
||||
|
||||
### Exact published source/ABI delta
|
||||
|
||||
The fix changes four paths:
|
||||
|
||||
| Path relative to IDF | Published change |
|
||||
| --- | --- |
|
||||
| `components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h` | `wpa_funcs.wpa_ap_get_wpa_ie`: `uint8_t *(*)(uint8_t *len)` becomes `uint8_t *(*)(size_t *len)` |
|
||||
| `components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c` | Matching callback implementation takes `size_t *ie_len`; assignment from `hapd->wpa_auth->wpa_ie_len` remains |
|
||||
| `components/esp_wifi/lib` | Submodule `d7373a90dc3f0be841b29911e33de5f99988dbff` → **`a4e903fe43bf09a95022f9802db43d39740ccc0b`** |
|
||||
| `components/esp_wifi/include/esp_wifi_types_generic.h` | Two SAE PWE documentation comments record default `WPA3_SAE_PWE_BOTH`; not structure-layout changes |
|
||||
|
||||
The historical shared IDF 5.5.0 header and callback take `uint8_t *`; `esp_wpa_main.c` registers the callback for SoftAP. The callback slot/pointer size need not change for this to be an ABI break: the width and interpretation of the pointed-to output change (one byte versus a `size_t`, four bytes on ESP32-S3). Mixed old/new caller and callee can corrupt storage or leave an incorrect length. Casting the callback does not fix the contract. Both sides, including the opaque caller, must agree.
|
||||
|
||||
The visible C edits are the length correction/interface marker. **The management-encryption fix is in the vendor binary update, not a public C hunk that can be copied into this application.**
|
||||
|
||||
### Bundle identity: baseline, fix point, release point are different
|
||||
|
||||
- Historical shared installed baseline matches Wi-Fi submodule **`8a1b7bbc00e895d040c5c9a6fb9d1db2bbfc7958`** from IDF v5.5, not the fix's immediate predecessor `d7373a90…`.
|
||||
- The exact fix-point bundle is **`a4e903fe43bf09a95022f9802db43d39740ccc0b`**.
|
||||
- The complete 5.5.3 release points to **`e0befaa593277b4e80726079fbd521b4681754c2`**, verified again at the immutable release commit. [Wi-Fi comparison](https://api.github.com/repos/espressif/esp32-wifi-lib/compare/a4e903fe43bf09a95022f9802db43d39740ccc0b...e0befaa593277b4e80726079fbd521b4681754c2) reports ahead 3 / behind 0. Do not call the fix-point bundle the final 5.5.3 bundle.
|
||||
|
||||
Calculated Git blob SHA-1 (`blob <length>\0` plus bytes) of **all seven installed S3 archives** and compared against official metadata for [baseline](https://api.github.com/repos/espressif/esp32-wifi-lib/contents/esp32s3?ref=8a1b7bbc00e895d040c5c9a6fb9d1db2bbfc7958), [fix point](https://api.github.com/repos/espressif/esp32-wifi-lib/contents/esp32s3?ref=a4e903fe43bf09a95022f9802db43d39740ccc0b), and [release point](https://api.github.com/repos/espressif/esp32-wifi-lib/contents/esp32s3?ref=e0befaa593277b4e80726079fbd521b4681754c2):
|
||||
|
||||
| Archive | Installed = baseline Git blob | Fix-point Git blob | 5.5.3-release Git blob |
|
||||
| --- | --- | --- | --- |
|
||||
| `libcore.a` | `ec2e76caad4c29ee92ec45432e0dc3bf946ea4c7` | `af63b0f8254bde084b2f474cdba1c1d63b4ae750` | `9f7b14a8bf6eec64973da8adc65d35b5ba9bee49` |
|
||||
| `libespnow.a` | `d4925dc7f73173077e8ed58abbbe5a59b5bb7be5` | `da0537c3f754a9d638fbecd4f4d806bfa66316b4` | `132b4f67e339ca2081d2add91c14eefa39476ff9` |
|
||||
| `libmesh.a` | `5cd43bf5a0a0388fb6f093a154401f00227d9570` | `6ae30c90ba59ab949e98db89b6b51daab434a5a0` | `2e9dc1c8c7afbf033337b4175032e9b1161e3262` |
|
||||
| `libnet80211.a` | `6ee20bec142638dc0a442b9642de9fa62db6ddd0` | `8ce8a2a6a9a753e31508bd609bf0a0d1d44c332f` | `2800d447ec385d33869373696ba8191292647694` |
|
||||
| `libpp.a` | `c9fc98fe529c9ff2891839fdb9a41b1189c03c49` | `536fd25e30a1be76ff122d93e7481f1da22e6892` | `8944bcad7371621045f376cf74c62fde6f368cbb` |
|
||||
| `libsmartconfig.a` | `31518eb9336f07de14b4a71c0389217a54ed3ec7` | `19e8274e0961fdf88351a0dec6e0d696b8ee2b94` | `4dc759e25617aa00b9e12887fd092a1d5780a170` |
|
||||
| `libwapi.a` | `0604ff76aa9134b475daf08496e03350d5ef8f09` | `e43863a22e93990b8a4deb7c4efc8e4c27566789` | `65655b6feab0572a6e8a1200946d53a21f3f4722` |
|
||||
|
||||
All historical shared-SDK archives matched baseline; all differ from both newer bundles. This is artifact identity, not proof that every archive is linked or contains the defect. Remote binaries were **not downloaded during the initial review**. Later candidate archive download/verification and complete Wi-Fi/PHY/coexistence/supplicant package comparisons are recorded in the integration/rebase reviews; the candidate uses the release-point column, not this old baseline. Git IDs are provenance identifiers, not independently measured remote SHA-256 digests or a vendor signature verification.
|
||||
|
||||
The release API advertises the submodule-inclusive `esp-idf-v5.5.3.zip`, asset ID **357881088**, SHA-256 **`d16e6b57b1df1a523aff6a1d0ef2f99cf058cb71e73f694429769eaaea171428`**. This is vendor-published metadata, not a locally verified download. The release is marked `immutable=false`; therefore pin the resolved commit/submodules and verify artifact hashes, not merely a tag URL. Espressif warns that GitHub's automatic source archives omit submodules; those are not complete SDK inputs.
|
||||
|
||||
## PMF/WPA3 mitigation assessment
|
||||
|
||||
Both the [v5.5 documentation source](https://raw.githubusercontent.com/espressif/esp-idf/v5.5/docs/en/api-guides/wifi-security.rst) and [immutable candidate documentation](https://raw.githubusercontent.com/espressif/esp-idf/2c211b236707889e8400c4dc5644dd5c4ee071e0/docs/en/api-guides/wifi-security.rst), plus the installed type header, establish:
|
||||
|
||||
- `pmf_cfg.capable` is deprecated, effectively true internally. Setting it false is **not a supported PMF-off mitigation**.
|
||||
- `required=false` permits optional PMF; it does not disable it.
|
||||
- `esp_wifi_disable_pmf_config()` is the documented optional-PMF disable API. Disabling PMF on WPA3 or WPA2/WPA3 mixed SoftAP returns an error.
|
||||
- WPA3 mandates PMF. WPA2-only mode by itself still does not prove PMF is disabled.
|
||||
|
||||
**No issue-specific official workaround was found in the inspected release/fix text.** The API documentation explains general configuration, not an endorsement that disabling PMF safely mitigates all defects in this change. The trigger supplies a rationale that actually eliminating `bss->pmf_enable` could avoid the described encryption path; the opaque implementation and lifecycle have not been proved to honor that invariant across existing associations/restarts.
|
||||
|
||||
Consequently, a WPA2-only plus explicit PMF-disable implementation is at most an **unapproved conditional workaround candidate**. It would remove SAE benefits and management-frame protection, require explicit owner acceptance, documented threat trade-offs, correct manager-owned stop/reconfigure/start sequencing, checked API results, and target evidence that negotiated PMF cannot reappear across APSTA/fallback transitions. It would not repair the RSN-IE allocation defect. Do not implement it as a silent downgrade, global WPA3 Kconfig removal, or station-profile change.
|
||||
|
||||
If operational containment is urgently required before an update, the owner may explicitly choose existing AP-off policy (removes the described AP response path) or Wi-Fi-off (removes radio service), accepting lost network/fallback access. These are feature-withdrawal containment options, **not vendor-certified workarounds or a source fix**. Preserve and verify UART0/native USB access first; do not automatically persist policy changes. No containment action was taken here.
|
||||
|
||||
## What a coherent replacement entails
|
||||
|
||||
### Preferred: complete vendor maintenance release
|
||||
|
||||
Use all IDF source, private/public headers, Wi-Fi/PHY/coexistence libraries and other submodules from one resolved release, with its toolchain and Python/build requirements. Do not install final-release blobs alongside fix-point-only C edits or mix two release revisions. Preserve the application security policies separately through audited overrides.
|
||||
|
||||
Current integration disposition (supplied parent evidence, 2026-09-18):
|
||||
|
||||
| Input | Current state | Remaining boundary |
|
||||
| --- | --- | --- |
|
||||
| Root `platformio.ini` | Platform 6.13.0, framework 3.50503.0 / IDF 5.5.3, Xtensa and RISC-V 14.2.0+20251107 | Exact version pins, not complete immutable root dependency closure; default root build timed out during installation at 200 seconds before compilation |
|
||||
| Isolated `app-validated` | Fresh build PASS; 95,552 B RAM / 1,749,493 B flash (+1,212 / −19,408 B versus historical 94,340 / 1,768,901 B) | Linked size is not runtime reserve |
|
||||
| Protected corrections | Nine C sources plus forced header; per-entry semantic rebase, five new signed WS receive checks; 982 cases / 10 mutation checks | No original correction removed as superseded; retain exact fail-closed guards |
|
||||
| Actual build/host validation | Explicit candidate SDK/core/build with interop and web performance PASS 24/24; source equality 3,237 files, identical pre/post hash recorded in [validation](phase9_validation.md#current-candidate-execution--2026-09-18) | Not radio-hardware vulnerability closure or full target acceptance |
|
||||
| Radio delivery | All packaged Wi-Fi/PHY/coexistence/supplicant files match locked framework archive; candidate callback is `size_t *` | Not independent opaque-code correctness or recursive upstream submodule provenance |
|
||||
| Notices | Nine entries semantically rebased, 66 unchanged, 75 total; 36 fixtures PASS; two actual bundles each 77 files / 4,433,930 bytes | Recipient/source/legal clearance pending |
|
||||
|
||||
The completed [semantic rebase review](idf_553_rebase_review.md#3-per-entry-semantic-disposition) covers candidate originals, not only Wi-Fi: HTTPS cleanup/private-key release, HTTPD scratch/null-read handling, server-local TLS policy, DHCP bounds, TLS EMS failure, X.509 allocation failure; retain/revalidate wolfSSH password/parser/ordering/ABI changes and wolfCrypt settings. Mark each correction **retained, rebased, or superseded with source/test evidence**. Upstream fixes do not automatically supersede local lifetime/policy additions in the same source. Also retain silent-panic/no-coredump and heap-wipe policy guards. The durable architecture's older four-correction summary is not the complete current inventory; the current generator and code map are authoritative.
|
||||
|
||||
### Contingency: vendor-supported backport onto 5.5.0
|
||||
|
||||
The smallest **published delta against the fix's own parent** is the four-path change above. The smallest **proven safe delta against this project's 5.5.0** is **not established**.
|
||||
|
||||
A candidate backport must at least pair both callback changes with one complete ESP32-S3 Wi-Fi archive set from the same vendor revision, retain the vendor patch/provenance, and establish compatibility with all private Wi-Fi headers, OS adapter tables, supplicant structures/callbacks, PHY/coexistence interfaces, configuration ABI and toolchain. All seven S3 archives form the conservative replacement set; do not mix old optional archives with new ones simply because current linking omits them. This minimum set is necessary for coherent provenance, **not sufficient proof** of cross-baseline compatibility.
|
||||
|
||||
Ask Espressif for a supported 5.5.0 backport bundle or an explicit compatibility statement with its required source/header/library/toolchain prerequisites, confirming both management-frame and IE-length corrections. The immediate-parent blob pointer differs from baseline; successful linking alone cannot establish binary layout/calling-contract compatibility. Without that evidence or a complete audited dependency closure, choose the full-release path rather than treating the two C signature edits plus blobs as approved.
|
||||
|
||||
## Implementation plan history and remaining acceptance
|
||||
|
||||
Steps 1–4 below preserve the original plan: package selection, isolated preparation, override rebase and fresh candidate build/host checks now have the evidence above. Complete immutable dependency closure is not claimed. Steps 5–6 remain acceptance/release gates; historical “no code change yet” language is not a new blocker.
|
||||
|
||||
1. **Compatibility/provenance gate first, no code change yet.** Resolve one deployment release and PlatformIO delivery combination. Record immutable SDK commit, recursive submodule pins, package/toolchain identities and SHA-256 manifests. Confirm vendor inclusion of the fix. Obtain missing packaging/ABI evidence; if unavailable, report the blocker instead of guessing a framework URL or transplanting archives.
|
||||
2. **Prepare isolated inputs after approval.** Do not modify the user's shared installed SDK. Obtain complete vendor inputs in a dedicated location, verify provenance/hashes, and compare only relevant integration surfaces and protected-source entries. Keep existing managed components pinned where compatible; explicitly review any resolver-required changes. Review license/notice/source-delivery updates for new radio/SDK artifacts.
|
||||
3. **Rebase security contracts.** Implement a reviewed per-entry disposition and exact new source pins, retain fail-closed guards, and verify every generated override/forced header reaches the actual targets. Add a bundle-consistency check for the selected SDK and archive identities so an old/new mixed bundle fails deterministically. Do not enable unrelated features, regenerate web assets, migrate credentials, or alter partitions.
|
||||
4. **Build/host gates in the later implementation task only.** Clean candidate build with the selected toolchain; inspect linked archive paths/map, generated configuration and actual compilation inputs. Run SDK override, crypto, parser, ordering/interop, authentication/protocol, build-policy and notice validations as applicable to the resolved input changes. Record sizes and compare resource budgets; a host pass is not radio validation. Existing historical passes do not validate this candidate.
|
||||
5. **Target gates under explicit device authorization.** Exercise WPA2 with/without negotiated PMF and WPA3 SoftAP; authenticate/reassociate after keys are installed; cover PMF optional/required behavior, four clients, disconnect/reconnect and AP/STA/APSTA/fallback transitions. Obtain vendor regression guidance or an authorized isolated-radio reproduction for the exact trigger; ordinary reconnect tests alone do not prove vulnerability closure. Stress concurrent HTTPS/WebSocket/SSH and verify bounded recovery, heap/stack reserve, queue behavior, no secret/crash-memory disclosure, and unchanged broker isolation. Verify UART0 recovery and native USB UART1 operation while Wi-Fi fails/stops/restarts. Include persisted-config reboot tests without erasure.
|
||||
6. **Release gate.** Keep PMF/WPA3 security at least unchanged. Record exact artifacts and test evidence, retain a controlled recovery image and configuration backup procedure, and mark the old image as still exposed rather than calling rollback a security fix. No flash erase, upload, pin changes or deployment until separately authorized. Update durable project memory only when implemented contracts/ownership genuinely change.
|
||||
|
||||
## Open evidence and review boundary
|
||||
|
||||
- **Resolved:** official fix IDs/description; exact visible ABI delta; all seven installed S3 archives match baseline; distinct fix and final-release bundles; release/fix ancestry; current application configuration; PMF-disable restrictions; exact-IDF/hash guards; recommended versus installed toolchain mismatch.
|
||||
- **Current resolved integration:** official PlatformIO 6.13.0 / IDF 5.5.3 delivery and exact toolchain selection, semantic override rebase, fresh candidate build and 24/24 host validation, whole packaged radio-component equality and notice rebase.
|
||||
- **Still unresolved:** complete immutable ancillary/tool/Python/managed dependency closure; independent PHY/coexistence upstream submodule provenance; opaque binary implementation correctness; packet-level attacker requirements; target/runtime/resource evidence and deployment acceptance. A 5.5.0 transplant remains unproved and was not selected.
|
||||
- A follow-up GitHub API request for candidate `components/esp_phy/lib` returned **HTTP 403 rate limit exceeded**. The sequential metadata query stopped there, so PHY/coexistence pins were not established; no inference was drawn from that failure. Already completed Wi-Fi/release/ancestry queries succeeded. Official raw documentation remained available. Do not treat this partial metadata boundary as a compatibility clearance.
|
||||
- Historical initial-review validation only: read-only local source/package/config inspection; local seven-archive Git-blob hashing; official patch/release/tag/content/comparison/toolchain/documentation reads. No broad repeated dependency audit, binary download, PlatformIO invocation, build, test suite, device operation, configuration edit, or asset generation. Documentation links/provenance do not constitute signed-vendor attestation, exhaustive security certification, or Phase 9 sign-off.
|
||||
@@ -0,0 +1,192 @@
|
||||
# Phase 9D — wolfSSL / wolfSSH advisory review (partial)
|
||||
|
||||
## Implementation addendum — 2026-09-16
|
||||
|
||||
**Xtensa small-math mitigation implemented; the broader review and security sign-off remain open.** Root `CMakeLists.txt` defines `CURVE25519_SMALL` and `ED25519_SMALL` before component parsing. `cmake/wolf_crypto_policy.cmake` propagates a forced-include resolved-settings guard PUBLIC from wolfSSL to consumers, including wolfSSH/application code. `cmake/wolf_crypto_policy.h` requires both enabled small implementations, rejects `WOLFSSL_CURVE25519_BLINDING`, and rejects enabling Curve448/Ed448 without review. This follows PR 9275's small-math policy for the enabled Xtensa algorithms, with consistent library/consumer production flags and ABI-sensitive layouts. **Blinding is not enabled with small X25519**: wolfSSL 5.8.2 excludes/rejects this combination; do not force it back on or present the historical blinding observation below as current policy.
|
||||
|
||||
**Parser corrections implemented:** `tools/security_overrides.py` extends the existing hash-pinned wolfSSH generated source (now eight C source overrides plus one header overall). The PR892 subset bounds `DoIgnore`/`GetSkip` and service-string reads, accepts boundary-empty skips, rejects zero-capacity `GetString`, and preserves the old strict service-name length limit. The PR881 subset rejects channel-window addition overflow without changing the window. The PR880 subset changes both ECC/Ed25519 key/signature label comparisons to short-circuit OR. Local framing corrections bound ECC r/s reads to the declared sub-blob and require exact inner/outer consumption; Ed25519 also requires exact outer consumption. Valid framing and crypto/digest/message construction remain unchanged; formerly tolerated malformed labels/trailing bytes reject. Existing password bounds/wiping and async-pending retention remain intact. See the [parser contract and deferred scope](../tests/wolfssh_parser_contract/README.md); these are subsets, not complete PR backports. The [finite remaining SSH review](ssh_parser_remaining_review.md) is complete: bounded exact CHANNEL_FAILURE recipient parsing (fatal policy retained), exact `ssh-userauth` service validation, and exact length/byte dispatch for all nine channel-request names are implemented. Unknown-request/trailing-payload behavior is preserved. PR899 client key skips are unchanged and blocked by current server role/ordering; PR918/919 forwarding is disabled. The inspected generic signature caller trace found no attacker-selected short-digest/OID path; generic APIs remain unpatched. These are profile-specific dispositions, not exhaustive parser/library safety; revisit on caller, feature, KEX or source changes.
|
||||
|
||||
**Crypto validation implemented:** PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` enable existing upstream P-256 import validation and X25519 all-zero-result rejection. The guard rejects missing checks and reviewed validator-disabling/hardware-stub configurations. Strict production checks confirm the effective flags without candidate injection. The [key-validation review](ssh_key_validation_review.md) traces the previously missing unauthenticated P-256 KEX point check and low-order X25519 inputs that pass the old precheck. Current raw SSH signature callers supply locally derived full digests; no current short-digest/OID-confusion trigger was found. This does not backport generic PR10131 API hardening or demonstrate an exploit. Import validation adds CPU/allocation cost requiring target measurement.
|
||||
|
||||
Supplied parent build evidence: `pio run` **PASS**, **94,340 B linked RAM / 1,768,901 B flash**, unchanged RAM / **+200 B flash** versus 1,768,701 B. Final parent results: **all seven suites PASS** — ordering `--interop` (8,028 checks, seven rejected mutations, 12 exact-256-KiB sessions with clean channel close/transport EOF), SDK overrides with actual `--build-dir` registration, auth (135 cases), protocol, strict crypto, notices (30), and parser (3,258 cases × two modes plus channel profiles; 11 + 18 + 2 rejected mutations). Interop's unsandboxed approval was solely for local AF_UNIX sockets; no remote network or device operation. See [dated evidence and limits](security_hardening.md#ordering-hostbuild-evidence--2026-09-16). These are supplied results, not build/test reruns by this documentation update or runtime-headroom evidence.
|
||||
|
||||
**Restricted ordering correction implemented for CVE-2025-14942; not a full upstream backport or advisory/phase sign-off.** The finite parser/client/generic-caller questions are dispositioned in the linked report; broader advisory coverage, whole-phase target validation and [release delivery/source/legal gates](release_packaging.md#separate-work-before-distribution) remain open. Offline notice assembly is implemented, not distribution clearance. No dependency upgrade or device operation was performed. Phase 9 is not complete or production-ready.
|
||||
|
||||
### Restricted ordering correction and remaining gates
|
||||
|
||||
The earlier temporary attempt is superseded by the [implemented restricted-profile correction and prerequisite disposition](../tools/wolfssh_order/README.md). wolfSSH remains pinned to **1.4.20** with the existing **Curve25519-SHA256 / ECDH-P256** KEX profile; DH/GEX/PQ widening is rejected. Independent SELF/PEER keying bits, exact nonzero expected-message tracking and service/authentication phase gates reject wrong-direction, premature and duplicate traffic. Receive expectations are separate from send-side rekey fences. Pre-peer-KEXINIT in-flight traffic remains legal when only SELF initiated rekey.
|
||||
|
||||
`SendNewKeys` queues one packet under the old sending keys and installs new sending keys; SUCCESS or WANT_WRITE clears SELF, retaining PEER and its expected NEWKEYS. Existing flush paths finish the same buffered packet without resending NEWKEYS. **EXT_INFO is intentionally disabled**, rather than adding a pending extension continuation: no `ext-info-c`, no outgoing EXT_INFO, incoming EXT_INFO rejected, `sendExtInfo`/`extInfoSent` remain zero. There is **no `server-sig-algs` advertisement** now; the KeyAccepted setter does not produce that wire extension. RSA-SHA2 discovery/RSA user-key interoperability is not claimed.
|
||||
|
||||
The executable consolidated edits are [delta.json](../tools/wolfssh_order/delta.json), not all hunks of PR793/819/840/855/921. [provenance.json](../tools/wolfssh_order/provenance.json) records exact archived patch byte hashes, source URLs and full embedded commit IDs; preserve those archives rather than relying on mutable PR responses. Original source/header hashes are pinned, upstream notices retained, and generated wolfSSH files carry the **2026-09-16** ordering modification/provenance notice. There are **eight C overrides plus one generated `wolfssh/internal.h`**. CMake propagates the ABI overlay BEFORE PUBLIC and as a PUBLIC forced include, with a stale-header guard and configure/compiler dependencies. Joined `-include/path` arguments preserve both ordering and crypto forced headers through PlatformIO sorting/deduplication; the adapter regression exercises a real Xtensa consumer and a failing split-option mutation.
|
||||
|
||||
Review found a misplaced EOF guard; it is now corrected in `SendChannelEof` before lookup/serialization/`eofTxd` mutation. The extra `SendChannelExit` guard remains intentional local hardening, not PR793's EOF hunk. Follow-up verification found no scoped blocker. Supplied ordering evidence: **8,028 checks and seven rejected mutations**, including actual EOF/shutdown/exit-status rekey fences.
|
||||
|
||||
The initial host interoperability run failed a close race despite printing `INTEROP PASS` early; that output was not successful completion. The corrected harness waits for peer channel close and transport EOF, uses a local fd-passing proxy, independently owns/reaps the server, and requires both process exits to be zero. Final agent evidence: **36/36 repeated OpenSSH 10.2p1 sessions**, each exact **256 KiB** binary echo, with **ten** completed exchanges for client-rekey cases or **two** for fragmented server-rekey cases, clean exits and no EXT_INFO. See the [test contract, harness diagnosis and limits](../tests/wolfssh_order_contract/README.md). This establishes bounded host interoperability, not general shutdown correctness or device behavior.
|
||||
|
||||
Official registry queries for wolfSSH **1.5.0** and wolfSSL **5.9.2** returned **404 on 2026-09-15**. Upstream tags exist at wolfSSH commit `8643d7be841184f766374e3b0ed68ced6391543c` and wolfSSL commit `ac01707f552c611fbd135cc723b2682b3e7f80f2`; tag existence is not managed-component availability or ESP compatibility. This is supplied query evidence, not a fresh network check by this documentation update.
|
||||
|
||||
Remaining gates: target cleanup/disconnect during rekey, no-EXT_INFO compatibility with intended clients, initial KEX/rekey under load, and stack/heap/timing measurements during whole-phase validation. Broader advisory coverage and release-specific source/legal/recipient delivery remain open; finite parser review and notice assembly are no longer pending implementation. Any future upstream upgrade still needs isolated packaging/compatibility evaluation, coherent source/header review and explicit rebasing of source overrides and version/callback contracts while preserving password/parser/crypto protections. The restricted correction is not a full wolfSSH 1.4.22/1.5.0 backport, dependency upgrade, or phase sign-off.
|
||||
|
||||
## Historical pre-mitigation research baseline — 2026-09-15
|
||||
|
||||
**The remainder retains the original pre-mitigation research evidence. “Current” macros, generated hashes, priorities and “not applied/tested” statements below describe that historical snapshot, not today's implementation. The addendum and linked key-validation review supersede the scoped small-math, parser, ECC/X25519 and ordering status claims. Restricted ordering is implemented; its target/compatibility gates remain open; the [finite remaining parser review](ssh_parser_remaining_review.md) supersedes historical deferred PR899/service/client/generic-caller questions, without certifying dormant dependency code.**
|
||||
|
||||
Review date: **2026-09-15**. Read-only external research and local applicability inspection; stopped at the user's requested handoff. **Not a completed security review or release clearance.** Only this new report was written. No dependencies, sources, generated inputs, or parent documents were changed; no build, hardware test, network exploit, or public PoC was executed.
|
||||
|
||||
## Release decision / current-path priorities
|
||||
|
||||
1. **Block security sign-off pending mitigation of CVE-2025-12888.** Current SSH advertises X25519 on Xtensa, and actual compiler preprocessing confirms the affected non-small implementation. Upstream recommends the small implementation; existing blinding is not evidence that this separate compiler/architecture issue is resolved. Exploitability on this precise GCC/optimization/device combination was not measured.
|
||||
2. **Block sign-off pending a reviewed wolfSSH message-order correction.** CVE-2025-14942 is critical for clients; upstream explicitly recommends its fix for servers because the same defect exists, without identifying a specific server attack. The generated local server state machine lacks the fix. Do **not** report demonstrated server password disclosure or authentication bypass from this evidence.
|
||||
3. **Close current-path parser gaps before sign-off.** Upstream wolfSSH PR 892 fixes unchecked SSH_IGNORE/service-string parsing still present in the generated source. These are security-relevant malformed-input handling defects, not assigned CVEs in the inspected release notes. Actual out-of-allocation access/exploit impact remains unproven: packet padding and enclosing receive-buffer invariants need auditing. Phase 9C's password fix does not fix these handlers.
|
||||
4. **Keep ECC validation and other non-CVE hardening open.** Current ECC import lacks `WOLFSSL_VALIDATE_ECC_IMPORT`; 5.9.1 release notes explicitly recommend this flag for older versions. Whether the SSH ECDH path already performs equivalent validation before scalar multiplication was not resolved. Do not call this a confirmed invalid-curve vulnerability.
|
||||
|
||||
Smallest proposed mitigation for item 1: enable `CURVE25519_SMALL` consistently for library and consumers through project-owned compile policy; upstream PR 9275 also enables `ED25519_SMALL`, `CURVE448_SMALL`, and `ED448_SMALL` on Xtensa. For enabled algorithms, follow that upstream policy rather than assume X25519 blinding suffices. Check the effective blinding/small combination, ABI, compile guards, performance, and real SSH interoperability. This was **not applied or tested**.
|
||||
|
||||
Item 2 is **not a safe one-line patch**. PR 855 changes `internal.c`, handshake layout in `wolfssh/internal.h`, message ranges, expected-message tracking, and logging; 1.4.20 also predates intervening rekey changes. A backport needs the coherent library/header change set, audit of prerequisites, and later PR 921 follow-up noted in 1.5.0. Prefer evaluating **wolfSSH 1.5.0 + wolfSSL 5.9.2**, the newest stable releases returned by the queried APIs, rather than merely reaching the minimum fixed release. This is a candidate pair, **not verified ESP managed-component availability or compatibility**. Any upgrade requires rebasing exact-hash Phase 9C overrides, auth/version guards, callback changes (including keyboard auth), and contract tests; do not blindly repin.
|
||||
|
||||
For item 3 the smallest candidate backport is the bounded `GetSkip`/`GetString` handling from PR 892, including necessary helper semantics, plus regression cases for truncated/oversized/wrapping lengths. Preserve Phase 9C password framing and wiping. No backport has been proven safe by this report.
|
||||
|
||||
## Local evidence and effective policy
|
||||
|
||||
- `dependencies.lock`: `wolfssl/wolfssl` **5.8.2~1** (upstream **5.8.2**), component hash `4d619e882c19d967bbaa53302e3bd2bdb8c611b5efb13302171aace809fccfdf`; `wolfssl/wolfssh` **1.4.20**, component hash `24d623360c07374a90b1ade8d1218b24bb7b661fd51ace9135dccf510a338927`.
|
||||
- Actual database: `.pio/build/esp32-s3-devkitc-1-n16r8/compile_commands.json`. Replayed four actual compiler commands with `-E -dM`, removing compilation/output/dependency-write flags, capturing stdout only: wolfCrypt `ecc.c`, `curve25519.c`, `random.c`, and generated wolfSSH `security_overrides/wolfssh_internal/internal.c`. All four preprocessing invocations returned 0. This is current compile-policy evidence, not proof the flashed binary matches it.
|
||||
- Compiler: `xtensa-esp32s3-elf-gcc`, `__GNUC__=14`, `__GNUC_MINOR__=2`, `__XTENSA__=1`; representative wolfCrypt commands use **`-Og`**.
|
||||
- Generated wolfSSH input SHA-256: `1c04d26d46f7bef61eefcf0b78c43742662001bf6054ad2383500c9063c62494`. `tools/security_overrides.py` pins original `internal.c` SHA-256 `81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9`; edits bound both password lengths with `GetSize`, prevent malformed new-password framing reaching the callback, and wipe the checked method suffix. No message-order or IGNORE/service parsing correction appears in those edits.
|
||||
- `src/ssh_protocol_policy.c`: KEX `curve25519-sha256,ecdh-sha2-nistp256`; host key `ecdsa-sha2-nistp256`; ciphers AES-128/256-GCM; MAC advertisement HMAC-SHA256; user-key advertisement Ed25519/P256. Database authorization remains separate from advertisement.
|
||||
- `src/ssh_transport.c`: creates `WOLFSSH_ENDPOINT_SERVER`; sets authentication callbacks, explicit keyboard rejection, shell acceptance, and exec/subsystem rejection. Project memory identifies wolfCrypt as SSH crypto, **not the HTTPS provider**. However, **`WOLFCRYPT_ONLY` and `NO_TLS` are absent** in the actual macros: “used only for crypto” must not be mislabeled “TLS compiled out.” No duplicate IDF/mbedTLS advisory assessment is made here.
|
||||
|
||||
| Area | Effective observations | Consequence |
|
||||
| --- | --- | --- |
|
||||
| Math | `USE_FAST_MATH` present; `WOLFSSL_SP_MATH`, `WOLFSSL_SP_MATH_ALL`, `WOLFSSL_HAVE_SP_ECC`, `WOLFSSL_HAVE_SP_DH`, `WOLFSSL_SP_NONBLOCK` absent | TFM, not SP. SP-specific release fixes are not automatically current-path findings. |
|
||||
| DH | `NO_DH` present; generated SSH also has `WOLFSSH_NO_DH` | Finite-field DH and SSH DH-GEX fixes are not current negotiated features. ECDH is distinct and remains enabled. |
|
||||
| ECC | `HAVE_ECC`, `ECC_TIMING_RESISTANT`, `HAVE_ECC_CHECK_KEY` present; `WOLFSSL_VALIDATE_ECC_IMPORT`, `USE_ECC_B_PARAM` absent; `HAVE_ECC_CHECK_PUBKEY_ORDER` appears in `ecc.c` after its internal includes | A compiled key-check implementation is not proof callers invoke it. `ecc.c:10993–10996` gates import-time checking on the absent validation flag. |
|
||||
| X25519 / Ed25519 | `HAVE_CURVE25519`, `HAVE_ED25519`, `WOLFSSL_CURVE25519_BLINDING` present; `CURVE25519_SMALL`, `ED25519_SMALL` absent | CVE-2025-12888 configuration matches; earlier blinding hardening is enabled. |
|
||||
| RNG | `HAVE_HASHDRBG`, `WC_RNG_SEED_CB` present; custom generate-block/seed macros absent; `wc_SetSeed_Cb(ssh_seed)` registration observed | Project callback supplies wolfCrypt Hash-DRBG seeding. Do not confuse this with OpenSSL RAND-after-fork or claim entropy quality was tested. |
|
||||
| AES / hardware | `HAVE_AESGCM` present; streaming GCM absent; ESP32 crypto port present, hardware AES/hash explicitly disabled; RSA hardware-disable macro absent | Current AES/hash software policy is intentional. Not an all-software-crypto claim. |
|
||||
| Optional APIs | `HAVE_PKCS7`, `HAVE_ECCSI`, `HAVE_CHACHA`, `HAVE_XCHACHA`, `HAVE_POLY1305`, `OPENSSL_EXTRA` absent; ASN template present/original absent | Many crypto-only advisories still need feature/API filtering, not just TLS exclusion. |
|
||||
| SSH features | `WOLFSSH_CERTS`, `WOLFSSH_SFTP`, `WOLFSSH_SCP`, `WOLFSSH_FWD`, `WOLFSSH_AGENT` absent; `NO_WOLFSSH_CLIENT` absent | No certificate/file-transfer/forwarding feature; client code not compile-disabled, but inspected application is server-only. |
|
||||
|
||||
## Priority advisory evidence table
|
||||
|
||||
Dates below are CVE record **publication dates (UTC)**, not reservation or release dates. Upstream severity does not by itself establish local exploitability. Source URLs are listed below.
|
||||
|
||||
| ID / upstream severity | Published | Affected / fixed upstream | Local applicability and evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| **CVE-2025-12888 / Low** | 2025-11-21 | `<5.8.4`; fixed 5.8.4, wolfSSL PR 9275 | **Applicable configuration, immediate mitigation priority.** Xtensa + advertised X25519 + small implementation absent. Patch inspected. |
|
||||
| **CVE-2025-14942 / Critical (client)** | 2026-01-06 | `<1.4.22`; fixed 1.4.22, wolfSSH PR 855 | **Potential server impact / upstream recommends server fix.** Generated `internal.c:587–623` has old accept-state filtering, no expected-message tracking. Critical client credential-leak scenario is not this application's role. |
|
||||
| CVE-2025-11625 / Critical | 2025-10-21 | `<=1.4.20` client; fixed 1.4.21, PR 840 | **Not current role.** Host-verification bypass/credential leak concerns wolfSSH clients. CNA version string is malformed (`1.4.20;0`); range taken from official release notes. |
|
||||
| CVE-2025-11624 / Medium | 2025-10-21 | 1.3.0–1.4.20 inclusive; fixed 1.4.21, PR 834 | **Not current feature.** SFTP file-handle stack overflow; SFTP absent. |
|
||||
| CVE-2025-15382 / Medium | 2026-01-06 | Release notes: 1.4.12–1.4.21 inclusive; fixed 1.4.22, PR 859 | **Not current feature.** SCP clean-path overread. CNA incorrectly/differently says `<1.4.21`; use conservative release-note range and retain discrepancy. |
|
||||
| CVE-2026-0930 / Low | 2026-04-20 | 1.4.15–`<1.5.0`; fixed 1.5.0 | **Not current platform/application.** Windows wolfSSHd terminal resize. Release notes name PR 864; CNA references PR 846 instead. Discrepancy not resolved. |
|
||||
| CVE-2026-5194 / Critical | 2026-04-09 | 3.12.0–`<5.9.1`; fixed 5.9.1, wolfSSL PR 10131 | **Not demonstrated on current SSH flow.** Certificate OID/type confusion absent without SSH certificates. Patch also hardens low-level ECC digest lengths, so crypto-only use is not a blanket exclusion. Inspected SSH paths derive digest sizes from hash IDs; finish tracing every current raw-signature call before closure. |
|
||||
| CVE-2026-5187 / Low | 2026-04-09 | `<=5.9.0`; fixed 5.9.1, PR 10025 | **No current trigger established.** `DecodeObjectId`/unknown ASN.1 extension handling; raw SSH keys are not X.509 extensions. ASN is compiled for keys/signatures, so retain API reachability check rather than claim all ASN parsing absent. |
|
||||
| CVE-2025-13912 / Low | 2025-12-11 | `<5.8.4`; fixed 5.8.4, PR 9148 | **Not reported compiler/architecture combination.** Advisory describes Clang on AArch64/RISC-V/x86_64; current GCC14.2 Xtensa. Separate from CVE-2025-12888. |
|
||||
| CVE-2025-7396 / Low | 2025-07-18 | Blinding introduced 5.8.0, enabled by default in applicable 5.8.2 builds | **Existing mitigation present:** actual `WOLFSSL_CURVE25519_BLINDING`. CNA affected string `5.8.0; 0` is not a usable precise range. Not a resolution of the Xtensa advisory. |
|
||||
|
||||
## Post-pin release review, including changes without CVEs
|
||||
|
||||
| Product / version | Release-note date / GitHub publication UTC date | Scope checked |
|
||||
| --- | --- | --- |
|
||||
| wolfSSL 5.8.4 | 2025-11-20 / 2025-11-21 | Xtensa small implementations, Clang hardening, TLS advisories, ESP SHA changes. |
|
||||
| wolfSSL 5.9.0 | 2026-03-18 / 2026-03-19 | TLS/PKCS/CRL advisories; RISC-V SP timing, KCAPI ECC import, RNG/port changes. |
|
||||
| wolfSSL 5.9.1 | 2026-04-08 / 2026-04-08 | Certificate/digest checks; ECC import validation; SP and hardware-port hardening. |
|
||||
| wolfSSL 5.9.2 | 2026-06-23 / 2026-06-25 | Latest stable returned; additional crypto validation, zeroization, SP/DH, PKCS, TLS and platform fixes. Unreleased master material is not a release candidate. |
|
||||
| wolfSSH 1.4.21 | 2025-10-20 / 2025-10-22 | Client/SFTP CVEs; rekey PR 793, auth rejection fixes, keyboard callback changes. |
|
||||
| wolfSSH 1.4.22 | 2026-01-05 / 2026-01-06 | State-machine/SCP CVEs; PRs 854 auth initialization, 857 string parsing, 856 worker/backpressure. |
|
||||
| wolfSSH 1.5.0 | 2026-04-17 / 2026-04-20 | Latest stable returned; parser/bounds, callback defaults, key cleanup, KEX ordering, Windows CVE. |
|
||||
|
||||
Non-CVE findings are not automatically less important:
|
||||
|
||||
- **wolfSSH PR 892 (fixed in 1.5.0):** actual generated `DoIgnore` at line 5912 reads its length with unchecked `ato32`, ignores `len`, and advances by an unchecked peer length. `DoServiceRequest` at line 6124 also reads before checking; later service-name check does not establish four readable input bytes. Patch replaces these with bounded helpers. Password portion is already locally corrected, but the entire PR is not present.
|
||||
- **wolfSSH PR 881 (1.5.0):** patch adds overflow check before `peerWindowSz += bytesToAdd`. Patch inspected; exact local function comparison was not completed. Potential availability/correctness issue, not a confirmed buffer overflow.
|
||||
- **wolfSSH PR 899 (1.5.0):** patch bounds skips in `ParseRSAPubKey`/`ParseECCPubKey` and corrects `DoChannelFailure` length validation. Current role/reachability not finished; do not conflate client host-key parsing with server user-key authentication.
|
||||
- **wolfSSH PR 880 (1.5.0):** release notes describe public-key type validation logical-operator correction. Actual generated `DoUserAuthRequestEcc` contains length mismatch **AND** `memcmp` mismatch checks for both key and signature type. This needs focused review with database key matching and attacker signature framing; exploit impact not established. Terminal-mode, agent, TPM, daemon changes in that PR need separate feature filtering.
|
||||
- **wolfSSH PRs 918/919/902 (1.5.0):** forwarding/channel callback and payload hardening. FWD is absent; shell/exec/subsystem callbacks are registered, but default channel-open handling is not fully audited. No forwarding bypass claim.
|
||||
- **wolfSSL PR 10133 (5.9.1):** ECC validation hardening. Actual `wc_ecc_import_x963_ex` calls `wc_ecc_check_key` only under absent `WOLFSSL_VALIDATE_ECC_IMPORT`. `HAVE_ECC_CHECK_PUBKEY_ORDER` is present inside `ecc.c`; its validation implementation can load curve B even without `USE_ECC_B_PARAM`. Do not infer “no point check anywhere” merely from absent B macro. Smallest candidate hardening is the upstream-recommended validation flag, pending full ECDH call trace and cost tests.
|
||||
- **wolfSSL 5.9.2:** SP integer fixes, P521 SP ECDH 65/66-byte output check (PR 10702), DH subgroup hardening (PR 10560) are not current TFM/P256/no-DH features. Curve25519 all-zero-secret default checking (PR 10374), private-key clamp check (PR 10363), and broader ECC/EdDSA zeroization/validation fixes warrant follow-up. ESP port fixes in 5.9.1 PRs 10003/10080 need per-backend review; AES/hash hardware is disabled but that does not eliminate every ESP port path.
|
||||
|
||||
## Remaining wolfSSL CVE inventory collected
|
||||
|
||||
All identifiers in this section have the prefix **CVE-**. Ranges are CNA metadata, not proof of local reachability; `0` means the record supplied no meaningful historical lower bound. Every listed record was successfully queried at `https://cveawg.mitre.org/api/cve/<full-ID>`. Fixed versions and trigger descriptions came from the official security page and ChangeLog. This is a compact screening inventory, **not completed per-patch validation**.
|
||||
|
||||
| IDs | Published | Affected range / fixed | Current-feature screening |
|
||||
| --- | --- | --- | --- |
|
||||
| 2025-11931 | 2025-11-21 | `<5.8.4` / 5.8.4 | XChaCha API absent. |
|
||||
| 2025-11932, 2025-12889 | 2025-11-21 | `<5.8.4` / 5.8.4 | wolfSSL TLS PSK/digest policy not used. |
|
||||
| 2025-11933, 2025-11934, 2025-11935, 2025-11936 | 2025-11-21 | `[3.12.0,5.8.4)` / 5.8.4 | wolfSSL TLS1.3 not used. |
|
||||
| 2026-0819 | 2026-03-19 | `[5.5.0,5.9.0)` / 5.9.0 | PKCS7 absent. |
|
||||
| 2026-1005, 2026-2646 | 2026-03-19 | `<=5.8.4` / 5.9.0 | No wolfSSL sniffer/session restore use. |
|
||||
| 2026-2645 | 2026-03-19 | `<5.8.4` / 5.8.4; further 5.9.0 hardening | wolfSSL TLS1.2 server not used; table headline 5.9.0 must not obscure earlier effective fix. |
|
||||
| 2026-3229, 2026-3230, 2026-3547, 2026-3548, 2026-3549 | 2026-03-19 | `<5.9.0` / 5.9.0 | Compatibility certificate APIs/TLS/CRL not current SSH features. |
|
||||
| 2026-3503 | 2026-03-19 | `[5.8.2,5.9.0)` / 5.9.0 | ARM Cortex-M PQ fault attack; wrong platform/features. |
|
||||
| 2026-3579, 2026-3580 | 2026-03-19 | `<5.9.0` / 5.9.0 | RISC-V RV32I SP timing; wrong platform/math. |
|
||||
| 2026-3849 | 2026-03-19 | 5.6.0–5.8.4 inclusive / 5.9.0 | HPKE/ECH TLS client not used. |
|
||||
| 2026-4159, 2026-4395 | 2026-03-19 | `<5.9.0`; `<=5.8.4`, respectively / 5.9.0 | PKCS7 / Linux KCAPI ECC not current features. |
|
||||
| 2026-5188 | 2026-04-10 | `<=5.9.0` / 5.9.1 | Original ASN SAN parser; current template parser, no SSH certificates. |
|
||||
| 2026-5263, 2026-5264, 2026-5295, 2026-5392, 2026-5393, 2026-5447, 2026-5448, 2026-5460 | 2026-04-09 | `<5.9.1` / 5.9.1 | Certificate constraints, DTLS, PKCS7, experimental dual certs, compatibility APIs or TLS PQ paths not used. |
|
||||
| 2026-5446 | 2026-04-09 | `[5.2.1,5.9.1)` / 5.9.1 | ARIA TLS not used. |
|
||||
| 2026-5466, 2026-5479 | 2026-04-10 | `<5.9.1` / 5.9.1 | ECCSI / EVP ChaCha absent. |
|
||||
| 2026-5477, 2026-5500, 2026-5501 | 2026-04-10 | `<=5.9.0` / 5.9.1 | EAX/CMAC >4GiB, PKCS7, compatibility X509 not used. |
|
||||
| 2026-5503, 2026-5504, 2026-5507, 2026-5772, 2026-5778 | 2026-04-09 | `<=5.9.0` / 5.9.1 | ECH, PKCS7, session restore, hostname matcher, sniffer not used. |
|
||||
| 2026-10097 | 2026-06-25 | `[5.7.0,5.9.1]` / 5.9.2 | AVX2 ML-KEM; wrong platform/feature. |
|
||||
| 2026-10098 | 2026-06-25 | `[4.6.0,5.9.1]` / 5.9.2 | OCSP not used. |
|
||||
| 2026-10512 | 2026-06-25 | `[5.6.4,5.9.1]` / 5.9.2 | X25519 x86_64 assembly; wrong implementation. |
|
||||
| 2026-10592, 2026-6731, 2026-6412 | 2026-06-25 | `[3.9.10,5.9.1]` / 5.9.2 | Certificate name constraints/digest policy not current feature. |
|
||||
| 2026-11310 | 2026-06-25 | `[5.8.4,5.9.1]` / 5.9.2 | Pin predates introduction; compatibility X509 absent. |
|
||||
| 2026-11703 | 2026-06-25 | `[3.15.0,5.9.1]` / 5.9.2 | TLS SNI/ALPN resumption not used. |
|
||||
| 2026-11999, 2026-6091, 2026-55964 | 2026-06-25 | `[5.7.4,5.9.1]` / 5.9.2 | Certificate path verification not current feature. |
|
||||
| 2026-12340, 2026-55960 | 2026-06-25 | `[5.6.4,5.9.1]` / 5.9.2 | SM2 certificate/RPK TLS not used. |
|
||||
| 2026-55958 | 2026-06-25 | `[5.4.0,5.9.1]` / 5.9.2 | Renesas TSIP TLS; wrong platform. |
|
||||
| 2026-55961 | 2026-06-25 | `[3.15.7,5.9.1]` / 5.9.2 | Compatibility PKCS7 absent. |
|
||||
| 2026-55962 | 2026-06-25 | `[5.5.4,5.9.1]` / 5.9.2 | TLS post-handshake authentication not used. |
|
||||
| 2026-55967 | 2026-06-25 | `[4.8.0,5.9.1]` / 5.9.2 | GCM streaming >64GiB per message; streaming absent, not cumulative SSH session traffic. |
|
||||
| 2026-6092 | 2026-06-25 | `[5.2.0,5.9.1]` / 5.9.2 | TLS Encrypt-then-MAC not used. |
|
||||
| 2026-6094, 2026-7531 | 2026-06-25 | `[5.8.0,5.9.1]` / 5.9.2 | PKCS7 / TLS PQ hybrid not used. |
|
||||
| 2026-6291 | 2026-06-25 | `[3.9.10,5.9.1]` / 5.9.2 | PKCS7 RSA padding oracle, not SSH raw key authentication. |
|
||||
| 2026-6325 | 2026-06-25 | `[4.8.0,5.9.1]` / 5.9.2 | TLS signature-list setup not used. |
|
||||
| 2026-6329 | 2026-06-25 | `[3.10.0,5.9.1]` / 5.9.2 | PKCS12 MAC verification not a current SSH workflow. |
|
||||
| 2026-6330 | 2026-06-25 | `[5.7.4,5.9.1]` / 5.9.2 | ARM64 ML-KEM; wrong platform/feature. |
|
||||
| 2026-6331, 2026-6678, 2026-7511 | 2026-06-25 | `[3.15.5,5.9.1]` / 5.9.2 | EVP HMAC / PKCS7 APIs not used. |
|
||||
| 2026-6450 | 2026-06-25 | `[4.3.0,5.9.1]` / 5.9.2 | CRL not used. |
|
||||
| 2026-6679 | 2026-06-25 | `[5.4.0,5.9.0]` / 5.9.1 | DTLS not used. Listed under 5.9.2 but narrative says already fixed 5.9.1. |
|
||||
| 2026-6681 | 2026-06-25 | `[3.10.0,5.9.0]` / 5.9.1 | PKCS7 absent; same release-table/narrative discrepancy. |
|
||||
| 2026-7532 | 2026-06-25 | `<=5.9.1` / 5.9.2 | X509 IP constraints not used. Related advisory ID TALOS-2026-2409 appears in upstream ChangeLog; Talos page not queried. |
|
||||
| 2026-8720 | 2026-06-25 | `[5.9.0,5.9.1]` / 5.9.2 | HMAC-BLAKE2 APIs introduced after pin. |
|
||||
|
||||
## Queried sources and caveats
|
||||
|
||||
All following requests occurred during this **2026-09-15** review. Network requests succeeded; no blocked-network limitation applies to collected external evidence. The last attempted local read/status command failed with `authorization channel closed`; no final repository status/diff validation was obtained.
|
||||
|
||||
Official index and release sources fetched:
|
||||
|
||||
- https://www.wolfssl.com/docs/security-vulnerabilities/
|
||||
- https://github.com/wolfSSL/wolfssh/releases
|
||||
- https://raw.githubusercontent.com/wolfSSL/wolfssh/master/ChangeLog.md
|
||||
- https://raw.githubusercontent.com/wolfSSL/wolfssl/master/ChangeLog.md
|
||||
- https://api.github.com/repos/wolfSSL/wolfssl/releases?per_page=6
|
||||
- https://api.github.com/repos/wolfSSL/wolfssh/releases?per_page=6
|
||||
|
||||
Patch URLs actually queried (read only; no patch applied):
|
||||
|
||||
- https://github.com/wolfSSL/wolfssl/pull/9275.diff
|
||||
- https://github.com/wolfSSL/wolfssl/pull/10131.diff
|
||||
- https://github.com/wolfSSL/wolfssl/pull/10133.diff
|
||||
- https://github.com/wolfSSL/wolfssl/pull/10025.diff
|
||||
- https://github.com/wolfSSL/wolfssh/pull/855.diff
|
||||
- https://github.com/wolfSSL/wolfssh/pull/892.diff
|
||||
- https://github.com/wolfSSL/wolfssh/pull/881.diff
|
||||
- https://github.com/wolfSSL/wolfssh/pull/899.diff
|
||||
|
||||
CVE metadata: exact URL construction is `https://cveawg.mitre.org/api/cve/` followed by each full CVE identifier in the priority table and inventory above. Every such ID was queried; some twice. These are vendor CNA records served by the CVE API. Full paths are mechanically recoverable without search, e.g. https://cveawg.mitre.org/api/cve/CVE-2025-12888 and https://cveawg.mitre.org/api/cve/CVE-2025-14942 . No NVD or GitHub advisory API query was made.
|
||||
|
||||
Additional upstream patch links **identified in release notes, not fetched**: wolfSSH [840](https://github.com/wolfSSL/wolfssh/pull/840), [834](https://github.com/wolfSSL/wolfssh/pull/834), [859](https://github.com/wolfSSL/wolfssh/pull/859), [864](https://github.com/wolfSSL/wolfssh/pull/864), [793](https://github.com/wolfSSL/wolfssh/pull/793), [854](https://github.com/wolfSSL/wolfssh/pull/854), [856](https://github.com/wolfSSL/wolfssh/pull/856), [857](https://github.com/wolfSSL/wolfssh/pull/857), [880](https://github.com/wolfSSL/wolfssh/pull/880), [902](https://github.com/wolfSSL/wolfssh/pull/902), [918](https://github.com/wolfSSL/wolfssh/pull/918), [919](https://github.com/wolfSSL/wolfssh/pull/919), [921](https://github.com/wolfSSL/wolfssh/pull/921); wolfSSL [9148](https://github.com/wolfSSL/wolfssl/pull/9148), [10003](https://github.com/wolfSSL/wolfssl/pull/10003), [10080](https://github.com/wolfSSL/wolfssl/pull/10080), [10363](https://github.com/wolfSSL/wolfssl/pull/10363), [10374](https://github.com/wolfSSL/wolfssl/pull/10374), [10560](https://github.com/wolfSSL/wolfssl/pull/10560), [10702](https://github.com/wolfSSL/wolfssl/pull/10702).
|
||||
|
||||
Evidence limitations:
|
||||
|
||||
- The consolidated official page omitted the four wolfSSH CVEs published with 1.4.21/1.4.22 despite listing older and newer entries. **Neither that omission nor the user's earlier empty GitHub advisory tabs proves absence of vulnerabilities.** GitHub advisory tabs were not re-queried here.
|
||||
- Release-note dates differ from GitHub publication dates. CNA metadata has malformed ranges and conflicting references as noted. No date was inferred from a CVE year.
|
||||
- Master ChangeLogs and PR diffs are mutable. Release tags/timestamps were queried, but upstream full commit hashes and archived evidence snapshots were not captured. Some long patch responses were output-limited; do not interpret a successful fetch as a complete hunk-by-hunk audit.
|
||||
- Macro evidence is stronger than default Kconfig assumptions but not disassembly/timing evidence. Whole-library API reachability/linker elimination, every math/port fix, low-order X25519 behavior, RNG error paths, and ECC validation call chains remain unfinished.
|
||||
- Older entries such as wolfSSH CVE-2024-2873 (fixed 1.4.17) were visible in the index; this report is focused on post-pin maintenance and does not certify every historical advisory.
|
||||
- Parent follow-up: resolve the current-path priorities, choose a coherent upgrade/backport strategy, add negative message-order/parser/key-validation tests against actual generated sources, and run firmware plus whole-Phase-9 hardware gates. No hardware evidence is implied here.
|
||||
@@ -1,7 +0,0 @@
|
||||
# KiCad per-user state, locks and automatic backups
|
||||
*.kicad_prl
|
||||
*.lck
|
||||
~*.lck
|
||||
*-backups/
|
||||
*.bak
|
||||
__pycache__/
|
||||
@@ -1,116 +0,0 @@
|
||||
(kicad_symbol_lib (version 20231120) (generator "kicad_symbol_editor")
|
||||
(symbol "HW678_N16R8" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "U" (at 0 34.29 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "HW678_N16R8" (at 0 31.75 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:HW678_2x22_Provisional" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "HW678_N16R8_0_1" (rectangle (start -17.78 29.21) (end 17.78 -29.21) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "HW678_N16R8_1_1"
|
||||
(pin power_out line (at -22.86 26.67 0) (length 5.08) (name "3V3" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at -22.86 24.13 0) (length 5.08) (name "3V3" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 21.59 0) (length 5.08) (name "EN" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 19.05 0) (length 5.08) (name "GPIO4" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 16.51 0) (length 5.08) (name "GPIO5" (effects (font (size 1.016 1.016)) )) (number "5" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 13.97 0) (length 5.08) (name "GPIO6" (effects (font (size 1.016 1.016)) )) (number "6" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 11.43 0) (length 5.08) (name "GPIO7" (effects (font (size 1.016 1.016)) )) (number "7" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 8.89 0) (length 5.08) (name "GPIO15" (effects (font (size 1.016 1.016)) )) (number "8" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 6.35 0) (length 5.08) (name "GPIO16" (effects (font (size 1.016 1.016)) )) (number "9" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 3.81 0) (length 5.08) (name "GPIO17" (effects (font (size 1.016 1.016)) )) (number "10" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 1.27 0) (length 5.08) (name "GPIO18" (effects (font (size 1.016 1.016)) )) (number "11" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -1.27 0) (length 5.08) (name "GPIO8" (effects (font (size 1.016 1.016)) )) (number "12" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -3.81 0) (length 5.08) (name "GPIO3" (effects (font (size 1.016 1.016)) )) (number "13" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -6.35 0) (length 5.08) (name "GPIO46" (effects (font (size 1.016 1.016)) )) (number "14" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -8.89 0) (length 5.08) (name "GPIO9" (effects (font (size 1.016 1.016)) )) (number "15" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -11.43 0) (length 5.08) (name "GPIO10" (effects (font (size 1.016 1.016)) )) (number "16" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -13.97 0) (length 5.08) (name "GPIO11" (effects (font (size 1.016 1.016)) )) (number "17" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 -16.51 0) (length 5.08) (name "GPIO12" (effects (font (size 1.016 1.016)) )) (number "18" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -19.05 0) (length 5.08) (name "GPIO13" (effects (font (size 1.016 1.016)) )) (number "19" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -21.59 0) (length 5.08) (name "GPIO14" (effects (font (size 1.016 1.016)) )) (number "20" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at -22.86 -24.13 0) (length 5.08) (name "5V" (effects (font (size 1.016 1.016)) )) (number "21" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_out line (at -22.86 -26.67 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "22" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 26.67 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "23" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 24.13 180) (length 5.08) (name "GPIO43" (effects (font (size 1.016 1.016)) )) (number "24" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 21.59 180) (length 5.08) (name "GPIO44" (effects (font (size 1.016 1.016)) )) (number "25" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 19.05 180) (length 5.08) (name "GPIO1" (effects (font (size 1.016 1.016)) )) (number "26" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 16.51 180) (length 5.08) (name "GPIO2" (effects (font (size 1.016 1.016)) )) (number "27" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 13.97 180) (length 5.08) (name "GPIO42" (effects (font (size 1.016 1.016)) )) (number "28" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 11.43 180) (length 5.08) (name "GPIO41" (effects (font (size 1.016 1.016)) )) (number "29" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 8.89 180) (length 5.08) (name "GPIO40" (effects (font (size 1.016 1.016)) )) (number "30" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 6.35 180) (length 5.08) (name "GPIO39" (effects (font (size 1.016 1.016)) )) (number "31" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 3.81 180) (length 5.08) (name "GPIO38" (effects (font (size 1.016 1.016)) )) (number "32" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 1.27 180) (length 5.08) (name "GPIO37" (effects (font (size 1.016 1.016)) )) (number "33" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -1.27 180) (length 5.08) (name "GPIO36" (effects (font (size 1.016 1.016)) )) (number "34" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -3.81 180) (length 5.08) (name "GPIO35" (effects (font (size 1.016 1.016)) )) (number "35" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -6.35 180) (length 5.08) (name "GPIO0" (effects (font (size 1.016 1.016)) )) (number "36" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -8.89 180) (length 5.08) (name "GPIO45" (effects (font (size 1.016 1.016)) )) (number "37" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -11.43 180) (length 5.08) (name "GPIO48" (effects (font (size 1.016 1.016)) )) (number "38" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -13.97 180) (length 5.08) (name "GPIO47" (effects (font (size 1.016 1.016)) )) (number "39" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -16.51 180) (length 5.08) (name "GPIO21" (effects (font (size 1.016 1.016)) )) (number "40" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -19.05 180) (length 5.08) (name "GPIO20" (effects (font (size 1.016 1.016)) )) (number "41" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -21.59 180) (length 5.08) (name "GPIO19" (effects (font (size 1.016 1.016)) )) (number "42" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 -24.13 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "43" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 -26.67 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "44" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "MAX3243_Module_Reference" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "U" (at 0 22.86 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "MAX3243_Module_Reference" (at 0 20.32 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "MAX3243_Module_Reference_0_1" (rectangle (start -15.24 17.78) (end 15.24 -17.78) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "MAX3243_Module_Reference_1_1"
|
||||
(pin power_in line (at -20.32 13.97 0) (length 5.08) (name "Vin" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_in line (at -20.32 11.43 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 8.89 0) (length 5.08) (name "DCD" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 6.35 0) (length 5.08) (name "RX" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 3.81 0) (length 5.08) (name "TX" (effects (font (size 1.016 1.016)) )) (number "5" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 1.27 0) (length 5.08) (name "DTR" (effects (font (size 1.016 1.016)) )) (number "6" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -1.27 0) (length 5.08) (name "DSR" (effects (font (size 1.016 1.016)) )) (number "7" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 -3.81 0) (length 5.08) (name "RTS" (effects (font (size 1.016 1.016)) )) (number "8" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -6.35 0) (length 5.08) (name "CTS" (effects (font (size 1.016 1.016)) )) (number "9" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -8.89 0) (length 5.08) (name "RI" (effects (font (size 1.016 1.016)) )) (number "10" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -11.43 0) (length 5.08) (name "VLD" (effects (font (size 1.016 1.016)) )) (number "11" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 -13.97 0) (length 5.08) (name "OFF_N" (effects (font (size 1.016 1.016)) )) (number "12" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "OLED_I2C_26mm" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "DS" (at 0 12.7 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "OLED_I2C_26mm" (at 0 10.16 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:OLED_26mm_I2C_Provisional" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "OLED_I2C_26mm_0_1" (rectangle (start -10.16 7.62) (end 10.16 -7.62) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "OLED_I2C_26mm_1_1"
|
||||
(pin power_in line (at -15.24 3.81 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_in line (at -15.24 1.27 0) (length 5.08) (name "VCC" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -15.24 -1.27 0) (length 5.08) (name "SCL" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -15.24 -3.81 0) (length 5.08) (name "SDA" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "SW_NO" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "SW" (at 0 7.62 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "SW_NO" (at 0 5.08 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "SW_NO_0_1" (polyline (pts (xy -2.54 0) (xy 2.54 1.524)) (stroke (width 0.254) (type default)) (fill (type none)))(circle (center -2.54 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none)))(circle (center 2.54 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none))))
|
||||
(symbol "SW_NO_1_1"
|
||||
(pin passive line (at -5.08 0 0) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 5.08 0 180) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "R" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "R" (at 0 6.096 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "R" (at 0 3.556 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Resistor, axial DIN0207 footprint; exact MPN pending" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "R_0_1" (rectangle (start -2.54 1.016) (end 2.54 -1.016) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "R_1_1"
|
||||
(pin passive line (at -5.08 0 0) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 5.08 0 180) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1,56 +0,0 @@
|
||||
(footprint "HW678_2x22_Provisional"
|
||||
(version 20260206)
|
||||
(generator "pcbnew")
|
||||
(layer "F.Cu")
|
||||
(descr "HW-678, component/top view antenna up USB down; origin pad 1; left top-bottom 1..22, right top-bottom 23..44; only header centres verified; no GPIO mapping, outline, courtyard or antenna keepout; PROVISIONAL connector fit: 1.70 mm pads / 1.00 mm drills from installed KiCad PinHeader_1x04_P2.54mm_Vertical, awaiting selected actual pin/socket datasheet")
|
||||
(tags "module provisional THT")
|
||||
(attr through_hole)
|
||||
(property "Reference" "REF**" (at 12.7 -3) (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(property "Value" "HW678_2x22_Provisional" (at 12.7 57) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "PROVISIONAL CONNECTOR FIT" (at 12.7 25) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "ANTENNA UP / USB DOWN" (at 12.7 28) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(pad "1" thru_hole rect (at 0.00 0.00) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "2" thru_hole circle (at 0.00 2.54) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "3" thru_hole circle (at 0.00 5.08) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "4" thru_hole circle (at 0.00 7.62) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "5" thru_hole circle (at 0.00 10.16) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "6" thru_hole circle (at 0.00 12.70) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "7" thru_hole circle (at 0.00 15.24) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "8" thru_hole circle (at 0.00 17.78) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "9" thru_hole circle (at 0.00 20.32) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "10" thru_hole circle (at 0.00 22.86) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "11" thru_hole circle (at 0.00 25.40) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "12" thru_hole circle (at 0.00 27.94) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "13" thru_hole circle (at 0.00 30.48) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "14" thru_hole circle (at 0.00 33.02) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "15" thru_hole circle (at 0.00 35.56) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "16" thru_hole circle (at 0.00 38.10) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "17" thru_hole circle (at 0.00 40.64) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "18" thru_hole circle (at 0.00 43.18) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "19" thru_hole circle (at 0.00 45.72) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "20" thru_hole circle (at 0.00 48.26) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "21" thru_hole circle (at 0.00 50.80) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "22" thru_hole circle (at 0.00 53.34) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "23" thru_hole circle (at 25.40 0.00) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "24" thru_hole circle (at 25.40 2.54) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "25" thru_hole circle (at 25.40 5.08) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "26" thru_hole circle (at 25.40 7.62) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "27" thru_hole circle (at 25.40 10.16) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "28" thru_hole circle (at 25.40 12.70) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "29" thru_hole circle (at 25.40 15.24) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "30" thru_hole circle (at 25.40 17.78) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "31" thru_hole circle (at 25.40 20.32) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "32" thru_hole circle (at 25.40 22.86) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "33" thru_hole circle (at 25.40 25.40) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "34" thru_hole circle (at 25.40 27.94) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "35" thru_hole circle (at 25.40 30.48) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "36" thru_hole circle (at 25.40 33.02) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "37" thru_hole circle (at 25.40 35.56) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "38" thru_hole circle (at 25.40 38.10) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "39" thru_hole circle (at 25.40 40.64) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "40" thru_hole circle (at 25.40 43.18) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "41" thru_hole circle (at 25.40 45.72) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "42" thru_hole circle (at 25.40 48.26) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "43" thru_hole circle (at 25.40 50.80) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "44" thru_hole circle (at 25.40 53.34) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
)
|
||||
@@ -1,34 +0,0 @@
|
||||
(footprint "MAX3243_Reference_Provisional"
|
||||
(version 20260206)
|
||||
(generator "pcbnew")
|
||||
(layer "F.Cu")
|
||||
(descr "PROVISIONAL female 5988 CAD reference / VERIFY MALE 6253 geometry and pad map. Top view DE9 toward -Y; origin JP2 pin 1. Female reference: 1 Vin, 2 GND, 3 DCD, 4 RX, 5 TX, 6 DTR, 7 DSR, 8 RTS, 9 CTS, 10 RI, 11 VLD, 12 OFF. PROVISIONAL connector fit: carrier pads 1.70 mm / drill 1.00 mm. No verified DE9 envelope or courtyard. CAD-derived outline: Limor Fried/Ladyada for Adafruit Industries, CC BY-SA 3.0; see rs232-footprint-notes.md")
|
||||
(tags "MAX3243 module female5988 reference male6253 provisional THT")
|
||||
(attr through_hole)
|
||||
(property "Reference" "REF**" (at 13.97 -23) (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(property "Value" "MAX3243_Reference_Provisional" (at 13.97 -20.5) (layer "F.Fab") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(fp_text user "PROVISIONAL" (at 13.97 -16) (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "FEMALE CAD REF" (at 13.97 -14) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(fp_text user "VERIFY MALE" (at 13.97 -12.3) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(fp_text user "1" (at 0 1.7) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.12))))
|
||||
(fp_line (start 29.845 0.127) (end 29.845 -26.67) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_line (start 29.845 -26.67) (end -1.905 -26.67) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_line (start -1.905 -26.67) (end -1.905 0.127) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_arc (start -1.905 0.127) (mid -1.161051224 1.923051224) (end 0.635 2.667) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_line (start 0.635 2.667) (end 27.305 2.667) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(fp_arc (start 27.305 2.667) (mid 29.101051224 1.923051224) (end 29.845 0.127) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
|
||||
(pad "1" thru_hole rect (at 0 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "2" thru_hole circle (at 2.54 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "3" thru_hole circle (at 5.08 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "4" thru_hole circle (at 7.62 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "5" thru_hole circle (at 10.16 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "6" thru_hole circle (at 12.7 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "7" thru_hole circle (at 15.24 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "8" thru_hole circle (at 17.78 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "9" thru_hole circle (at 20.32 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "10" thru_hole circle (at 22.86 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "11" thru_hole circle (at 25.4 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "12" thru_hole circle (at 27.94 0) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "" np_thru_hole circle (at 1.905 -8.763) (size 3 3) (drill 3) (layers "*.Cu" "*.Mask"))
|
||||
(pad "" np_thru_hole circle (at 26.035 -8.763) (size 3 3) (drill 3) (layers "*.Cu" "*.Mask"))
|
||||
)
|
||||
@@ -1,21 +0,0 @@
|
||||
(footprint "OLED_26mm_I2C_Provisional"
|
||||
(version 20260206)
|
||||
(generator "pcbnew")
|
||||
(layer "F.Cu")
|
||||
(descr "User-verified front/display view, header at top; origin top-left PCB corner; 1 GND, 2 VCC, 3 SCL, 4 SDA; PROVISIONAL connector fit: 1.70 mm pads / 1.00 mm drills from installed KiCad PinHeader_1x04_P2.54mm_Vertical, awaiting selected actual pin/socket datasheet")
|
||||
(tags "module provisional THT")
|
||||
(attr through_hole)
|
||||
(property "Reference" "REF**" (at 13 -2) (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(property "Value" "OLED_26mm_I2C_Provisional" (at 13 28) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_text user "PROVISIONAL CONNECTOR FIT" (at 13 30) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))
|
||||
(fp_rect (start 0 0) (end 26 26) (stroke (width 0.1) (type solid)) (fill no) (layer "F.Fab"))
|
||||
(fp_rect (start 1 4.5) (end 25.5 21) (stroke (width 0.1) (type dash)) (fill no) (layer "F.Fab"))
|
||||
(pad "1" thru_hole rect (at 9.19 1.50) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "2" thru_hole circle (at 11.73 1.50) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "3" thru_hole circle (at 14.27 1.50) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "4" thru_hole circle (at 16.81 1.50) (size 1.7 1.7) (drill 1) (layers "*.Cu" "*.Mask") (remove_unused_layers no))
|
||||
(pad "" np_thru_hole circle (at 1.75 1.75) (size 2 2) (drill 2) (layers "*.Cu" "*.Mask"))
|
||||
(pad "" np_thru_hole circle (at 24.25 1.75) (size 2 2) (drill 2) (layers "*.Cu" "*.Mask"))
|
||||
(pad "" np_thru_hole circle (at 1.75 24.25) (size 2 2) (drill 2) (layers "*.Cu" "*.Mask"))
|
||||
(pad "" np_thru_hole circle (at 24.25 24.25) (size 2 2) (drill 2) (layers "*.Cu" "*.Mask"))
|
||||
)
|
||||
@@ -1,261 +0,0 @@
|
||||
# KiCad 10 module carrier — schematic and routed PCB draft
|
||||
|
||||
Open **`serial-carrier.kicad_pro`** in KiCad 10, then open
|
||||
**`serial-carrier.kicad_sch`**. A readable export is **`serial-carrier.pdf`**.
|
||||
This is a native, editable schematic with actual connected nets, not an image
|
||||
or a SKiDL-only netlist. **`serial-carrier.kicad_pcb` now contains a routed,
|
||||
82 × 80 mm two-copper-layer carrier draft.** It is **not fabrication-ready**.
|
||||
See **`pcb-draft-notes.md`** for placement, chosen dimensions/rules, provisional
|
||||
mechanical assumptions and validation. A review drawing is `pcb-placement-draft.pdf`.
|
||||
|
||||
The schematic contains the complete ESP32 development-board interface, MAX3243
|
||||
breakout logic interface, I²C OLED, three Omron B3F-1000 buttons and three 2.2 kΩ
|
||||
button pull-ups. USB connectors, regulator, reset/boot circuit, DE-9 and MAX3243
|
||||
charge pump are already on their respective modules and are not duplicated on
|
||||
the carrier. All 3.3 V and ground connections are explicit named nets.
|
||||
|
||||
## Files and use
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `serial-carrier.kicad_pro`, `serial-carrier.kicad_sch` | Native KiCad project and A3 single-sheet schematic |
|
||||
| `serial-carrier.pdf` | Schematic review/print export |
|
||||
| `serial-carrier.kicad_pcb` | Routed 82 × 80 mm stepped, two-copper-layer PCB draft |
|
||||
| `pcb-draft-notes.md`, `pcb-placement-draft.pdf` | PCB design decisions/limits and 1:1 placement review |
|
||||
| `validation/pcb-draft.png`, `validation/pcb-draft-3d.png` | Copper/fab preview and carrier-only 3D render |
|
||||
| `validate_pcb_draft.py` | Fresh schematic-to-PCB net checks, bounds/layers and DRC/parity report |
|
||||
| `generate_pcb_draft.py`, `route_pcb_draft.py` | Optional destructive-regeneration tools with explicit overwrite guards |
|
||||
| `Carrier.kicad_sym`, `sym-lib-table` | Project-local module, switch and resistor symbols |
|
||||
| `Carrier.pretty/`, `fp-lib-table` | OLED/ESP32/RS-232-reference provisional footprints; standard KiCad 10 switch/resistor/mounting-hole library references |
|
||||
| `oled-fit-check.svg` | User-fit-verified OLED mechanical template |
|
||||
| `footprint-notes.md`, `rs232-footprint-notes.md` | Footprint origins, provisional pad/drill choices, validation and RS-232 adaptation attribution |
|
||||
| `adafruit-research.md`, `button-notes.md`, `reference/` | Manufacturer evidence, pad maps and source/license records |
|
||||
| `bom-draft.csv` | Draft procurement list and unresolved selections |
|
||||
| `validate_schematic.py`, `validation/` | Repeatable netlist/firmware/ERC checks and exported evidence |
|
||||
| `validate_footprints.py` | Native KiCad footprint geometry regression checks via system Python/pcbnew |
|
||||
| `generate_schematic.py` | Optional stdlib-only initial design generator; not needed to open/edit in KiCad |
|
||||
|
||||
Keep these files together so project-relative libraries resolve. Standard KiCad
|
||||
10 footprint libraries must be installed for the THT buttons, resistors and mounting holes.
|
||||
SKiDL 2.3.0 is installed, but no SKiDL dependency is required for this native design.
|
||||
|
||||
**Normal validation from the repository root:**
|
||||
|
||||
```sh
|
||||
python3 hardware/PCB/validate_schematic.py
|
||||
```
|
||||
|
||||
This checks the current schematic without regenerating it, exports its netlist,
|
||||
runs KiCad ERC, checks all 17 connected nets and all 15 firmware GPIO assignments,
|
||||
and refreshes PDF/SVG review exports. It requires Python 3 and `kicad-cli`.
|
||||
The expected-net snapshot records the initial design contract; intentional wiring
|
||||
changes need a deliberate update to the checks and snapshot.
|
||||
|
||||
**Do not casually rerun `generate_schematic.py`:** it overwrites the schematic,
|
||||
symbol library, library tables and expected-net snapshot, discarding subsequent
|
||||
manual edits to those files. It preserves existing project settings and does not
|
||||
modify footprints. The generated KiCad files are the editable design artifacts.
|
||||
|
||||
## Schematic conventions and design decisions
|
||||
|
||||
- U1 numbering is project-specific: viewed from the component side, antenna up
|
||||
and USB down, left row is 1–22 and right row is 23–44, both top-to-bottom.
|
||||
These numbers map directly to the local HW678 footprint, not to GPIO numbers
|
||||
or the official DevKit header reference designators. Signal order was transcribed
|
||||
from the user-supplied component-side board-label photos; the user confirmed
|
||||
matching board markings. This is photo/label evidence, not a continuity test:
|
||||
verify actual pin-to-function mapping before release, especially on clones.
|
||||
- DS1 pins 1–4 are GND, VCC, SCL, SDA, front/display view with header at top.
|
||||
- U2 pin numbering follows published Adafruit **5988 female** CAD as a provisional
|
||||
reference for the selected **6253 male** module. Its footprint is deliberately
|
||||
**unassigned in the schematic** until the actual male header and mechanics are
|
||||
verified. The PCB draft assigns `Carrier:MAX3243_Reference_Provisional` on the
|
||||
PCB only, causing one documented footprint-parity warning. Its reference nets
|
||||
are routed for review, not male-board qualification. See `adafruit-research.md`
|
||||
and `pcb-draft-notes.md`; this is not a generic DE-9 footprint.
|
||||
- No-connect crosses mean unused on the carrier, not disconnected internally.
|
||||
Native USB GPIO19/20, UART0 GPIO43/44, memory-conflicting GPIO35–37 and the 5 V
|
||||
header are not connected to carrier circuitry.
|
||||
- U1 pin 1 (3V3) and pin 22 (GND) model the onboard supply as power outputs for
|
||||
ERC; the other duplicate rail pins are passive. This does not verify the
|
||||
regulator's capacity or USB power isolation. GPIO9 is modeled as a programmable
|
||||
bidirectional pad; firmware **must use open-drain mode** for OFF. ERC does not
|
||||
enforce that configuration or model the breakout's internal OFF pull-up.
|
||||
- Selected buttons are Omron B3F-1000 with their verified KiCad THT footprint.
|
||||
Added R1–R3, **2.2 kΩ, 1%, at least 0.125 W**, from each button input to 3.3 V.
|
||||
They provide nominal 1.5 mA contact current / 4.95 mW dissipation while pressed,
|
||||
rather than relying on the internal pull-up for the silver-contact rated load.
|
||||
Firmware's internal pull-ups and debouncing remain enabled; no firmware changes
|
||||
are required. Final supply tolerance/load budget still needs checking.
|
||||
DIN0207 axial footprints are selected; exact resistor MPNs remain pending.
|
||||
- External I²C pull-ups are **not assigned guessed values**. Verify OLED onboard
|
||||
pull-ups, resistance and rail before electrical sign-off and add appropriately
|
||||
sized carrier pull-ups if needed. The OLED must be powered from 3.3 V.
|
||||
|
||||
## Validation status
|
||||
|
||||
KiCad CLI 10.0.6 loaded the schematic and exported PDF, SVG and XML netlist.
|
||||
ERC reported **zero errors/warnings**, with no project exclusions added. Exact
|
||||
net memberships, all 44 U1 pins and firmware GPIO assignments were checked.
|
||||
The PDF was rendered and visually inspected for layout/readability. Both local
|
||||
footprints loaded in KiCad's native footprint loader. Two retained regression tests
|
||||
passed with `/usr/bin/python3 -B hardware/PCB/validate_footprints.py`; the binding
|
||||
emits startup PROPERTY_ENUM assertions but completes the checks. See
|
||||
`footprint-notes.md` for scope and limitations.
|
||||
|
||||
OLED PCB/header/display/mounting-hole geometry was physically fit-verified by
|
||||
the user. This does **not** validate connector drills, male RS-232 geometry,
|
||||
electrical operation, power budget, procurement or fabrication.
|
||||
|
||||
The routed PCB draft has **0 physical DRC violations and 0 unconnected items**;
|
||||
a fresh netlist-to-PCB check passes. One deliberate U2 footprint-parity warning
|
||||
remains (PCB reference footprint versus blank schematic assignment). No added
|
||||
DRC exclusions hide this warning. Missing courtyards are ignored by default DRC
|
||||
and remain a manual release check. Run `/usr/bin/python3 -B
|
||||
hardware/PCB/validate_pcb_draft.py` from the repository root; see
|
||||
`pcb-draft-notes.md` for the complete verification scope and remaining gates.
|
||||
No firmware build or device tests were run: this PCB work did not modify firmware.
|
||||
|
||||
## Evidence and components
|
||||
|
||||
Repository sources: `../../docs/wiring.md`, `../../src/board_pins.h`,
|
||||
`../../docs/electrical_tests.md`, and `../../README.md`.
|
||||
|
||||
| Assembly | Repository identification | Footprint status |
|
||||
|---|---|---|
|
||||
| ESP32 development board | User confirmed HW-678 V0.0.0 markings and S3-N16R8 shield, two USB-C ports | Two 22-pin rows; nominal 2.54 mm pitch and 25.40 mm row spacing supported by simultaneous breadboard fit. Mechanical outline registration and socket/drill selection remain pending. |
|
||||
| RS-232 module | Adafruit MAX3243 full-pinout breakout; male is product 6253, female is 5988 | Published female CAD pin map and geometry verified; male mapping/fit remains provisional. Reference footprint used on PCB only; schematic assignment remains blank. |
|
||||
| Display | 128×64 yellow/blue I²C OLED, address 0x3c; Amazon ASIN B0F13SSSFB | 26 mm square board, 22.50 mm hole spacing, Ø2 mm holes and centred header 1.50 mm from top edge are user-fit-verified. Local footprint assigned; header pad/drill selection remains provisional. |
|
||||
| Three buttons | Omron B3F-1000; previous, select, next; normally open to ground, active low | Datasheet and `Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x` pad mapping verified. External 2.2 kΩ pull-ups included. Live distributor stock remains unverified. |
|
||||
|
||||
Adafruit manufacturer CAD starting point:
|
||||
https://learn.adafruit.com/adafruit-rs-232-full-pinout-level-shifter-breakout/downloads
|
||||
|
||||
Published female-module Eagle CAD and its header map were retrieved and verified;
|
||||
see `adafruit-research.md` for pinned revision, hashes, attribution and geometry.
|
||||
The original files and CC BY-SA 3.0 notices are retained in `reference/`; they
|
||||
remain under their upstream license. Omron reference documents retain their
|
||||
manufacturer notices. No male-specific module footprint is claimed verified.
|
||||
|
||||
## Verified logical connections for the existing firmware profile
|
||||
|
||||
This table uses signal names, **not physical connector positions**. The schematic
|
||||
and its local symbols additionally capture the project's physical pad numbering.
|
||||
The wiring guide warns that compatible ESP32 boards may have different layouts.
|
||||
|
||||
| ESP32 connection | Peripheral connection |
|
||||
|---|---|
|
||||
| 3V3 | RS-232 Vin; existing I²C OLED VCC |
|
||||
| GND | RS-232 GND; OLED GND; one contact of each button |
|
||||
| GPIO17 | RS-232 TX |
|
||||
| GPIO18 | RS-232 RX |
|
||||
| GPIO15 | RS-232 RTS |
|
||||
| GPIO16 | RS-232 CTS |
|
||||
| GPIO7 | RS-232 DTR |
|
||||
| GPIO5 | RS-232 DSR |
|
||||
| GPIO4 | RS-232 DCD |
|
||||
| GPIO6 | RS-232 RI |
|
||||
| GPIO8 | RS-232 VLD |
|
||||
| GPIO9 | RS-232 OFF (active-low shutdown; open-drain drive) |
|
||||
| GPIO11 | Existing I²C OLED SDA |
|
||||
| GPIO12 | Existing I²C OLED SCL |
|
||||
| GPIO10 | Previous/back switch to GND |
|
||||
| GPIO13 | Select/confirm switch to GND |
|
||||
| GPIO14 | Next switch to GND |
|
||||
|
||||
## Electrical constraints from the repository
|
||||
|
||||
- Keep UART0 / GPIO43 and GPIO44 available through the onboard USB-UART bridge.
|
||||
- Reserve GPIO19 and GPIO20 for onboard native USB.
|
||||
- Existing firmware profile powers RS-232 Vin and OLED VCC from 3.3 V, not 5 V.
|
||||
- Confirm the exact development board's regulator capacity and USB power-path
|
||||
behavior before finalizing the power design, especially with both USB ports attached.
|
||||
- Display pull-up presence, resistance and termination rail must be checked on
|
||||
the actual module. Do not select external pull-up values without this information.
|
||||
- Firmware enables internal button pull-ups; this carrier additionally uses 2.2 kΩ
|
||||
external pull-ups for the selected buttons' contact-current requirement.
|
||||
- RS-232 OFF uses the breakout's onboard pull-up; GPIO9 releases it to enable.
|
||||
- The breakout is not galvanically isolated; signal grounds are common.
|
||||
- Preserve access to both development-board USB connectors and account for the
|
||||
antenna keepout using the exact board/module documentation.
|
||||
|
||||
## User-confirmed design direction
|
||||
|
||||
- ESP32 purchase: https://www.amazon.de/dp/B0F3XMYYQY
|
||||
- OLED purchase: https://www.amazon.de/dp/B0F13SSSFB — I²C, not SPI.
|
||||
- Both Amazon URLs returned an access-check page on inspection; the ASINs alone
|
||||
do not establish a verified manufacturer drawing or footprint.
|
||||
- Use the male Adafruit breakout. User reports male/female board dimensions match;
|
||||
verify the selected male board against its manufacturer CAD.
|
||||
- Carrier for complete modules: soldered module headers for final assembly,
|
||||
sockets for beta testing without a case. Header pitch, row spacing, drill size
|
||||
and socket clearance must be validated against selected hardware.
|
||||
- Power only through the ESP32 development board's USB connectors, typically a
|
||||
powerbank or the serial peer's USB port, with common ground. Do not introduce
|
||||
another carrier power input or assume simultaneous USB supplies are isolated.
|
||||
- Select readily available THT buttons; the existing random switches need not be reused.
|
||||
- Case will be 3D-printed after PCB finalization. Include mounting provisions in
|
||||
the layout, but screw size, hole diameter and insert bosses are not yet selected.
|
||||
|
||||
## User-supplied mechanical evidence
|
||||
|
||||
- ESP32 listing pictures show HW-678 V0.0.0; user confirms matching markings and
|
||||
actual shield S3-N16R8. Disregard the listing picture's N8R2 marking for memory
|
||||
identification and its erroneous USB labels: native USB is GPIO19 D− / GPIO20 D+.
|
||||
- ESP32 raw measurements: header first-to-last centres initially 52.6 mm, revised
|
||||
to 52.93 mm; row spacing measured 25.00 mm. User subsequently confirmed both
|
||||
rows fit a standard breadboard simultaneously without forcing. Use nominal
|
||||
2.54 mm pitch (53.34 mm over 21 intervals) and 25.40 mm row spacing based on
|
||||
that fit check, not the raw caliper measurements. Verify with a 1:1 print before fabrication.
|
||||
- ESP32 seller drawing states 28 mm width and annotates 63 mm length; its 20 mm
|
||||
arrow is not header row spacing. Antenna extent and header-to-outline offsets
|
||||
are not fully dimensioned.
|
||||
- OLED actual PCB outline measured by user: 26 × 26 mm, superseding the seller's
|
||||
28.7 × 27.8 mm dimensions. Seller's 4.1 mm height remains unverified.
|
||||
- OLED front view, connector at top: GND, VCC, SCL, SDA from left to right,
|
||||
confirmed on actual module. Raw first-to-last span revised from 7 mm to 7.49 mm;
|
||||
user confirms standard breadboard fit and agrees nominal pitch is 2.54 mm
|
||||
(7.62 mm span).
|
||||
- OLED mounting-hole diameter measured as 2 mm. User approved the latest edge
|
||||
measurements as superseding the earlier 22.16 mm hole-spacing measurement:
|
||||
centres are 1.75 mm from adjacent edges, giving 22.50 mm spacing in both axes.
|
||||
- User verified the template's PCB outline and mounting-hole placement against
|
||||
the actual OLED module: both fit perfectly. Header-pin centres were subsequently
|
||||
corrected to 1.50 mm from the top PCB edge, superseding 0.75 mm. The header row
|
||||
is therefore 0.25 mm nearer the top edge than the upper mounting-hole centres.
|
||||
- User measured the display outline margins: top 4.5 mm, bottom 5 mm, left 1 mm,
|
||||
right 0.5 mm. On the 26 mm PCB this defines a 24.5 × 16.5 mm rectangle from
|
||||
(1, 4.5) to (25.5, 21), shown dashed in the template. This records the supplied
|
||||
display boundary, not an independently verified active-pixel area or case cutout.
|
||||
- User subsequently confirmed the revised template is "about a perfect fit",
|
||||
validating the PCB outline, mounting-hole placement, corrected 1.50 mm header
|
||||
position and display outline against the actual module. This is a user-reported
|
||||
mechanical fit check, not electrical or fabrication validation.
|
||||
- User explicitly approved a horizontally centred header, superseding the earlier
|
||||
approximate 7.33 mm hole-to-pin measurements.
|
||||
- Approved coordinates in mm, viewed from the display side with the header at
|
||||
the top, origin at the top-left PCB corner, X right and Y down:
|
||||
GND (9.19, 1.50), VCC (11.73, 1.50), SCL (14.27, 1.50), SDA (16.81, 1.50).
|
||||
Mounting-hole centres: (1.75, 1.75), (24.25, 1.75), (1.75, 24.25),
|
||||
(24.25, 24.25), each diameter 2 mm. Board outline: (0, 0) to (26, 26).
|
||||
- `oled-fit-check.svg` is a 1:1 mechanical template of that geometry. Print at
|
||||
100%, without fit-to-page, and verify its 20 mm scale before checking the module.
|
||||
Crosshairs denote header centres, not specified pad or drill diameters. This
|
||||
template is not a KiCad footprint or a fabrication file.
|
||||
|
||||
## Remaining mechanical/electrical checks
|
||||
|
||||
1. OLED outline, mounting-hole placement, revised header position and display
|
||||
outline are user fit-verified. Select carrier header/socket pads and drills
|
||||
from the actual connector drawings.
|
||||
2. Before final module outlines/courtyards, establish their registration to header
|
||||
centres, connector overhang and display position from drawings or measurements.
|
||||
3. Verify power-path schematic/regulator capacity and OLED pull-up implementation.
|
||||
Photos alone do not establish those electrical properties.
|
||||
|
||||
The native schematic, local libraries and routed two-layer PCB draft are present.
|
||||
Before fabrication, finish the above checks, qualify the male RS-232 module map
|
||||
and footprint, and confirm procurement. The carrier now proposes an 82 × 80 mm
|
||||
stepped outline and four Ø3.2 mm M3 mounting holes; case hardware is not yet fixed.
|
||||
The PCB's RF reserve is a draft design allocation, not a verified module antenna
|
||||
keepout. Module/USB/DE-9 clearances must still be established from actual hardware.
|
||||
@@ -1,142 +0,0 @@
|
||||
# Adafruit MAX3243 breakout: authoritative CAD header research
|
||||
|
||||
Research date: 2026-09-20. Scope: source retrieval and pad/geometry extraction only; no project schematic or layout modified.
|
||||
|
||||
## Critical product distinction
|
||||
|
||||
**Product 5988 is the DE-9 female version. The male version is product 6253.** The product-5988 page's male variant link goes to `/product/6253`; each product description explicitly identifies its connector gender. Both link to the same Learn guide. Do not describe the downloaded CAD as verified male-specific CAD: its connector is `X2`, value `DE9-F`, package `F09HP`, and the repository README identifies product 5988.
|
||||
|
||||
The exact header map below is proven for the published 5988 board. It is useful reference for the family, but identical male-board geometry/pad numbering has **not** been established by a male-specific CAD file or physical measurement. Confirm the actual 6253 module before releasing a male carrier footprint. No inference from connector gender or a generic DE-9 footprint is substituted for that check.
|
||||
|
||||
## Provenance and downloaded originals
|
||||
|
||||
Authoritative chain:
|
||||
|
||||
1. [Adafruit Learn downloads](https://learn.adafruit.com/adafruit-rs-232-full-pinout-level-shifter-breakout/downloads) links to the Eagle repository below, a TI datasheet, fab print and 3D models.
|
||||
2. [Adafruit PCB repository](https://github.com/adafruit/Adafruit-RS-232-Level-Shifter-Breakout-PCB).
|
||||
3. Retrieved `main` commit **`63c6200bb3ef17d491089cfa6441b1810d79bb20`**, committed 2024-08-12T22:42:27Z. All four local originals were downloaded from this immutable revision, not a moving branch.
|
||||
4. [Product 5988, female](https://www.adafruit.com/product/5988), [product 6253, male](https://www.adafruit.com/product/6253), and [Learn pinouts](https://learn.adafruit.com/adafruit-rs-232-full-pinout-level-shifter-breakout/pinouts) corroborate identity and signal names.
|
||||
|
||||
Local files in `reference/`, unchanged upstream contents:
|
||||
|
||||
| File | SHA-256 |
|
||||
|---|---|
|
||||
| `Adafruit RS-232 Full Pinout Level-Shifter Breakout.brd` | `6a7b35ef909f5a2d243c326c34db3957aaa01275bf2aac8e0c749ff18162e870` |
|
||||
| `Adafruit RS-232 Full Pinout Level-Shifter Breakout.sch` | `4efbbe0ad14194e4e4563dd5f7dac007dfd52eba636f7390ffb822f3e81cd896` |
|
||||
| `README.md` | `2825ba741a8ce83abdaf2c7cb9170641473357630ccbee40a0016dcdb5cd80bb` |
|
||||
| `license.txt` | `075dad5e5fc96c27014fabc269f4f5732909cffd178a486f546d982b6cf86b74` |
|
||||
|
||||
Raw URL prefix for each filename (URL-encode spaces):
|
||||
|
||||
`https://raw.githubusercontent.com/adafruit/Adafruit-RS-232-Level-Shifter-Breakout-PCB/63c6200bb3ef17d491089cfa6441b1810d79bb20/`
|
||||
|
||||
**License:** upstream `license.txt` is **Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)**. README attribution: “Designed by Limor Fried/Ladyada for Adafruit Industries.” README explicitly requires all its preceding text in redistribution; the full original README and license are therefore retained. Preserve these notices when redistributing CAD and comply with applicable attribution/share-alike obligations for adaptations. The CAD-derived content of this report is attributed to that design and provided under CC BY-SA 3.0.
|
||||
|
||||
The guide also links [5988 3D CAD](https://github.com/adafruit/Adafruit_CAD_Parts/tree/main/5988%20RS-232%20Level%20Shifter). Its directory listing contains `.f3d`, `.step`, `.stl`, and `.jpg`; these were **not downloaded or used for measurements**, and their licensing was not independently assessed. No male-specific 3D model was verified in this research.
|
||||
|
||||
## Exact header numbering and net map
|
||||
|
||||
Source: Eagle XML `drawing/board/elements/element[@name='JP2']`, embedded `microbuilder` package `1X12_ROUND_76MIL`, and `drawing/board/signals/signal/contactref`. Cross-checked against schematic `JP2`, deviceset `HEADER-1X12`, selected device `76MIL`: symbol pin N maps directly to physical pad N.
|
||||
|
||||
**Viewing convention:** component/top view, DE-9 pointing toward the top of the page (+Y), logic header along the bottom. X increases rightward, Y upward. The origin is the lower-left bounding-box corner of the bare PCB (the corner itself is rounded). These are Eagle board coordinates in **millimetres**, not a mating-face view and not a bottom view.
|
||||
|
||||
```text
|
||||
DE-9 / +Y
|
||||
+-----------------------+
|
||||
| |
|
||||
| o o | module mounting holes
|
||||
| |
|
||||
| 1 2 3 ... 12 | JP2, y = 2.667 mm
|
||||
+-----------------------+
|
||||
Vin OFF
|
||||
+X ->
|
||||
```
|
||||
|
||||
| JP2 physical pad | Label / function | CAD net | X (mm) | Y (mm) | MAX3243 IC1 pad | DE-9 X2 pad via level shifter |
|
||||
|---:|---|---|---:|---:|---:|---:|
|
||||
| 1 | Vin | VCC | 1.905 | 2.667 | 26 | — |
|
||||
| 2 | GND | GND | 4.445 | 2.667 | 25 | 5 (direct ground) |
|
||||
| 3 | DCD | DCD | 6.985 | 2.667 | 15 | 1 |
|
||||
| 4 | RX | RXD | 9.525 | 2.667 | 16 | 2 |
|
||||
| 5 | TX | TXD | 12.065 | 2.667 | 13 | 3 |
|
||||
| 6 | DTR | DTR | 14.605 | 2.667 | 14 | 4 |
|
||||
| 7 | DSR | DSR | 17.145 | 2.667 | 17 | 6 |
|
||||
| 8 | RTS | RTS | 19.685 | 2.667 | 12 | 7 |
|
||||
| 9 | CTS | CTS | 22.225 | 2.667 | 19 | 8 |
|
||||
| 10 | RI | RI | 24.765 | 2.667 | 18 | 9 |
|
||||
| 11 | VLD | !INVALID | 27.305 | 2.667 | 21 | — |
|
||||
| 12 | OFF | !FORCEOFF | 29.845 | 2.667 | 22 | — |
|
||||
|
||||
**Left-to-right order: `Vin GND DCD RX TX DTR DSR RTS CTS RI VLD OFF`.** This is physical pad order, not the order of rows in the project's wiring table.
|
||||
|
||||
At the logic header, TX/DTR/RTS are inputs to the MAX3243; RX/DCD/DSR/CTS/RI are outputs from it. Schematic gates explicitly map TX to T2IN, DTR to T1IN, RTS to T3IN, RX to R4OUT, DCD to R5OUT, DSR to R3OUT, CTS to R1OUT and RI to R2OUT. RS-232 and logic signal nets are separated by the chip; matching signal names do not mean direct continuity.
|
||||
|
||||
OFF is **active low** (`!FORCEOFF`, IC1 pad 22) with 10 kΩ pull-up R1 to VCC. The Learn pinout page agrees. The upstream README and shop prose incorrectly say high shuts it down; do not propagate that prose error. VLD is `!INVALID`, high on valid input detection. Use 3.3 V Vin for this project's ESP32 interface, as already required by `docs/wiring.md`.
|
||||
|
||||
## Exact published PCB geometry
|
||||
|
||||
All figures below are nominal CAD dimensions, not measured tolerances.
|
||||
|
||||
### Logic header JP2
|
||||
|
||||
- Single row, **12 plated through-hole pads at 2.54 mm (0.100 inch) pitch**.
|
||||
- Element origin `(15.875, 2.667)`; no element rotation or mirroring (R0).
|
||||
- Package local pad centres: `x = -13.970 + 2.540 × (N - 1)`, `y = 0`, N = 1…12.
|
||||
- Board coordinates: `x = 1.905 + 2.540 × (N - 1)`, `y = 2.667`.
|
||||
- First-to-last centre span **27.940 mm (1.100 inch)**.
|
||||
- All holes: **1.000 mm drill**, explicit **1.9304 mm (76 mil) round copper diameter**. Each pad has `rot="R90"`; that does not rotate the header row or change a round pad's outline.
|
||||
- Both end pad centres are **1.905 mm** from the corresponding vertical board edge.
|
||||
- Row is **2.667 mm (0.105 inch)** above the lower bounding-box edge.
|
||||
- No square pad-1 marker is defined by this package: identify pad 1 using Vin and the stated view, not assumed square copper.
|
||||
|
||||
### Bare-board outline and mounting holes
|
||||
|
||||
Source: `drawing/board/plain/wire[@layer='20']` and mounting-hole element/package definitions.
|
||||
|
||||
- Bare PCB bounding box: **31.750 mm X × 29.337 mm Y** (1.250 × 1.155 inches), X = 0…31.750, Y = 0…29.337.
|
||||
- Straight top edge `(0,29.337)` to `(31.750,29.337)`; square top corners.
|
||||
- Bottom corners: **2.540 mm radius**, defined by 90° arcs `(0,2.540)` → `(2.540,0)` and `(29.210,0)` → `(31.750,2.540)`.
|
||||
- Bottom straight edge: X = 2.540…29.210 at Y = 0.
|
||||
- Module mounting holes: `U$2` at **(3.810, 11.430)** and `U$3` at **(27.940, 11.430)**.
|
||||
- Both use `MOUNTINGHOLE_3.0_PLATEDTHIN`, pad `P$1`, **3.000 mm drill / 3.600 mm copper diameter**. They are plated pads, not anonymous NPTH holes, and neither appears in the board's signal contactrefs.
|
||||
- Hole centre spacing **24.130 mm (0.950 inch)**; hole row is **8.763 mm** above the header row.
|
||||
|
||||
For a carrier footprint using header pad 1 as `(0,0)` with Y still upward:
|
||||
|
||||
- Header pads: `(2.540 × (N - 1), 0)`.
|
||||
- Mounting hole centres: **(1.905, 8.763)** and **(26.035, 8.763)**.
|
||||
- Bare-board bounding box: X = **−1.905…29.845**, Y = **−2.667…26.670**; retain corner arcs described above.
|
||||
|
||||
If the destination CAD uses screen coordinates with Y downward, transform Y deliberately; do not silently mirror physical pad numbering. The placement side and whether the module faces up or down must be fixed before generating a carrier footprint.
|
||||
|
||||
### DE-9 on the downloaded female CAD (not male qualification)
|
||||
|
||||
`X2` origin `(15.875,17.907)`, R0, package `F09HP`, value `DE9-F`.
|
||||
|
||||
| X2 pad | Board X (mm) | Board Y (mm) | CAD net |
|
||||
|---|---:|---:|---|
|
||||
| 1 | 10.3886 | 16.637 | RS_DCD |
|
||||
| 2 | 13.1318 | 16.637 | RS_RXD |
|
||||
| 3 | 15.8750 | 16.637 | RS_TXD |
|
||||
| 4 | 18.6182 | 16.637 | RS_DTR |
|
||||
| 5 | 21.3614 | 16.637 | GND |
|
||||
| 6 | 11.7602 | 19.177 | RS_DSR |
|
||||
| 7 | 14.5034 | 19.177 | RS_RTS |
|
||||
| 8 | 17.2466 | 19.177 | RS_CTS |
|
||||
| 9 | 19.9898 | 19.177 | RS_RI |
|
||||
| G1 | 3.3528 | 17.907 | no signal contactref |
|
||||
| G2 | 28.3972 | 17.907 | no signal contactref |
|
||||
|
||||
Signal holes have 1.016 mm drill and 1.778 mm octagonal copper; shell/mechanical pads G1/G2 have 3.302 mm drill and 5.080 mm copper, spaced 25.0444 mm. Shell pads are not shown tied to GND by signal contactrefs; DE-9 pin 5 is grounded. These are **PCB solder-tail coordinates**, not front-face pin coordinates.
|
||||
|
||||
The connector package's layer-21 drawing reaches local Y = 17.907, hence board Y = 35.814, beyond the board's Y = 29.337. This is library drawing evidence, **not a validated mechanical envelope**. Do not size an enclosure from silkscreen. Product pages list assembled dimensions of 33.6 × 32.0 × 14.1 mm (5988) and 33.7 × 31.8 × 14.2 mm (6253), with no coordinate/tolerance definition; these do not replace a male-specific mechanical drawing, and differ from the female library drawing extent.
|
||||
|
||||
## Validation and next-use boundary
|
||||
|
||||
- Parsed the actual Eagle board and schematic XML successfully using Python's standard-library XML parser.
|
||||
- Joined package pad names to board signal contactrefs, applied JP2's unrotated element translation, and cross-checked schematic header numbering/net assignments.
|
||||
- Extracted outline arcs, mounting-hole package dimensions, and connector pad data directly; no dimensions inferred from product photos.
|
||||
- Recorded SHA-256 hashes of the downloaded originals.
|
||||
- No firmware build or hardware tests performed: this task changed research/reference files only.
|
||||
- **Ready:** exact published 5988 header map, pad centres, board outline and hole coordinates for CAD reference.
|
||||
- **Still required for a male-module carrier release:** establish that the purchased module is 6253, verify its header order and mounting geometry against male-specific CAD or the physical module, and verify connector overhang, mating clearance, module height and mounting orientation. The shared guide alone is insufficient proof of identical male geometry.
|
||||
@@ -1,8 +0,0 @@
|
||||
References,Quantity,Description,Manufacturer,MPN or identification,Footprint,Status
|
||||
U1,1,ESP32-S3 N16R8 development board,User supplied,HW-678 V0.0.0 / ASIN B0F3XMYYQY,Carrier:HW678_2x22_Provisional,Header pitch/spacing confirmed; connector drill fit and module envelope pending
|
||||
U2,1,MAX3243 full-pinout RS-232 breakout male,Adafruit,6253,Carrier:MAX3243_Reference_Provisional,PCB-only reference footprint; schematic assignment blank; male header/geometry verification required
|
||||
DS1,1,128x64 yellow-blue I2C OLED 26mm PCB,User supplied,ASIN B0F13SSSFB,Carrier:OLED_26mm_I2C_Provisional,Module geometry user-fit-verified; carrier header drill fit pending
|
||||
SW1 SW2 SW3,3,Normally-open 6mm THT tactile switch,Omron,B3F-1000,Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x,Datasheet/footprint checked; stock unverified
|
||||
R1 R2 R3,3,2.2k ohm 1 percent axial resistor at least 0.125W,TBD,TBD,Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal,Design value selected; exact MPN and body/lead fit pending
|
||||
H1 H2 H3 H4,4,Proposed M3 case mounting locations with 3.2mm NPTH,TBD,TBD,MountingHole:MountingHole_3.2mm_M3,Board-only hole locations; screws and insert/standoff dimensions not selected
|
||||
Assembly hardware,TBD,Module headers and optional beta-test sockets plus mounting hardware,TBD,TBD,,Not yet selected; not included in electrical component count
|
||||
|
@@ -1,107 +0,0 @@
|
||||
# Button selection: Omron B3F-1000
|
||||
|
||||
Verified 2026-09-20 against the Omron datasheet and installed KiCad libraries. This research covers three active-low GPIO-to-GND buttons.
|
||||
|
||||
**Integration decision:** the A0 schematic now implements B3F-1000 switches with one **2.2 kΩ, 1%, at least 0.125 W external pull-up** per input. The lower-current/internal-pull-up-only discussion below records the selection rationale, not an unselected schematic option. Firmware is unchanged. Exact resistor MPN, supply tolerance/budget and stock remain release checks.
|
||||
|
||||
## Schematic-ready recommendation
|
||||
|
||||
**B3F-1000 fits the installed footprint mechanically and its normally-open contact topology matches the circuit. Low-current operation with internal pull-ups needs the qualification below.**
|
||||
|
||||
For each of the three buttons:
|
||||
|
||||
- Value / manufacturer part number: **B3F-1000**, Omron; standard silver-plated contacts, ivory flat plunger, no separate ground terminal.
|
||||
- Symbol: **`Switch:SW_Push`** (normally open, two passive pins).
|
||||
- Explicitly assign footprint: **`Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x`**. The installed symbol's default footprint field is empty.
|
||||
- Symbol pin **1 → GPIO**, symbol pin **2 → GND**. The switch is nonpolar; swapping these two nets is electrically equivalent.
|
||||
|
||||
| Function | Pin 1 net | Pin 2 net |
|
||||
|---|---|---|
|
||||
| Previous/back | GPIO10 | GND |
|
||||
| Select/confirm | GPIO13 | GND |
|
||||
| Next | GPIO14 | GND |
|
||||
|
||||
GPIO assignments verified in `src/board_pins.h`. The PCB README specifies normally-open buttons to GND with internal pull-ups. Released = high; pressed = low. No voltage source connects directly through the switch: any external pull-up goes through a resistor to 3.3 V.
|
||||
|
||||
## Exact terminal and pad mapping
|
||||
|
||||
The installed footprint has **four physical holes but only two distinct electrical pad numbers: `1,1,2,2`**, not four uniquely numbered pads. The installed `SW_Push` symbol has pin 1 at the left and pin 2 at the right in its default orientation; pin numbers are hidden by the symbol.
|
||||
|
||||
Unrotated footprint, front/top view; local coordinates in mm, positive Y downward:
|
||||
|
||||
```text
|
||||
6.5 mm
|
||||
KiCad pad 1 (0,0) ----------- KiCad pad 1 (6.5,0)
|
||||
Omron terminal 4 Omron terminal 3
|
||||
NO
|
||||
contact 4.5 mm
|
||||
Omron terminal 2 Omron terminal 1
|
||||
KiCad pad 2 (0,4.5) --------- KiCad pad 2 (6.5,4.5)
|
||||
```
|
||||
|
||||
The horizontal connections above are permanent internal connections; pressing joins the two rows. Omron datasheet **page 4**, upper-left **“Terminal Arrangement/Internal Connections (Top View)”**, explicitly shows terminals **4–3 common** and **2–1 common**. This maps to KiCad's upper pair numbered 1 and lower pair numbered 2 when aligned as shown. **Omron terminal 1 is not KiCad pad 1 in this orientation.** The manufacturer's separate bottom-view numbering sketch must not be mistaken for a top view. Omron states terminal numbers are not marked on the switches. A 180° insertion swaps the two electrical sides without affecting this nonpolar circuit.
|
||||
|
||||
Both pads numbered 1 receive the symbol pin-1 net; both pads numbered 2 receive pin-2's net. Do not attach a four-unique-pin symbol to this footprint without an explicit remapping. Connecting GPIO and GND to two terminals within the same common pair would permanently ground the GPIO.
|
||||
|
||||
The footprint sets `duplicate_pad_numbers_are_jumpers no`; do not assume PCB routing software will treat the internal metal as a routing jumper. Route/check the duplicate-pad nets normally.
|
||||
|
||||
## Manufacturer dimensions and ratings
|
||||
|
||||
Source: Omron B3F datasheet, ordering table p. 1, ratings/operating characteristics p. 3, B3F-1000 drawing p. 4. Dimensions below are mm.
|
||||
|
||||
| Item | Datasheet specification |
|
||||
|---|---|
|
||||
| Body | 6 ±0.2 × 6 ±0.2 |
|
||||
| Overall height above seating plane | 4.3 ±0.2 |
|
||||
| Plunger diameter | 3.5 |
|
||||
| Terminal row spacing | 4.5 ±0.2 |
|
||||
| Formed terminal span at hole-entry region | 6.5 ±0.5 |
|
||||
| Maximum-width dimension shown across formed leads | 7.7 ±0.5 |
|
||||
| Lead projection below seating plane | 3.5 |
|
||||
| Lead thickness / width callouts | 0.3 / 0.7 |
|
||||
| Recommended PCB hole-centre grid | 6.5 ±0.1 × 4.5 ±0.1 |
|
||||
| Recommended PCB holes | Four, diameter 1 ±0.1; reference drawing for PCB thickness 1.6 |
|
||||
| General dimensional tolerance | ±0.4 unless otherwise specified |
|
||||
| Contact form | SPST-NO |
|
||||
| Rated resistive load, standard silver version | **1–50 mA at 3–24 VDC** |
|
||||
| Minimum applicable load | **10 µA at 1 VDC, reference value** |
|
||||
| Initial contact resistance | 100 mΩ maximum |
|
||||
| Bounce | 5 ms maximum |
|
||||
| Operating force | 0.98 ±0.29 N (100 ±30 gf) |
|
||||
| Release force | 0.2 N minimum |
|
||||
| Pretravel | 0.25 +0.2/−0.1 |
|
||||
| Durability for B3F-1000, 0.98 N | 1,000,000 operations minimum |
|
||||
| Protection / washing | IP00 / washing not possible |
|
||||
|
||||
### Installed footprint comparison
|
||||
|
||||
Inspected `/usr/share/kicad/footprints/Button_Switch_THT.pretty/SW_TH_Tactile_Omron_B3F-100x.kicad_mod` (file format version `20260206`, generator version `10.0`) and `/usr/share/kicad/symbols/Switch.kicad_sym`.
|
||||
|
||||
- Hole centres: `(0,0)`, `(6.5,0)`, `(0,4.5)`, `(6.5,4.5)` — match Omron's nominal PCB grid.
|
||||
- All four plated through-hole pads: circular copper diameter **1.7**, drill **1.0**, front/back copper and mask — nominal drill matches Omron's reference hole diameter. Confirm the fabricator's finished-hole tolerance when ordering.
|
||||
- Fab body: `(0.25,-0.75)` to `(6.25,5.25)`, **6 × 6**, matching nominal body dimensions.
|
||||
- Courtyard: `(-1.1,-1.1)` to `(7.6,5.6)`, **8.7 × 6.7**. This is a library placement boundary, not an enclosure or finger-access clearance specification.
|
||||
- Footprint description specifies H4.3 mm. No physical sample fit, enclosure-height check, or independent 3D-model measurement was performed.
|
||||
|
||||
## Low-current qualification / selection decision
|
||||
|
||||
**Do not claim internal-pull-up operation meets the standard part's full rated-load range.** At 3.3 V, pull-up resistance greater than 3.3 kΩ gives less than 1 mA contact current. For illustration only, 45 kΩ would give about 73 µA; that is not a verified resistance specification for this board's GPIOs.
|
||||
|
||||
Omron's 10 µA / 1 V minimum is explicitly a **reference value**, not an unconditional low-current reliability guarantee. B3F-1000 is a reasonable prototype choice, but internal-pull-up-only contact reliability should be qualified in the intended environment and over life.
|
||||
|
||||
For a conservative silver-contact design within the stated rated load, consider **one external 2.2 kΩ pull-up to 3.3 V per button** (about 1.5 mA while pressed); verify supply/resistor tolerances and GPIO limits before adopting. This recommendation was subsequently adopted in the A0 carrier schematic; see the integration decision above. If low current is important, the datasheet also lists **B3F-1002-G** with gold contacts in the same p. 4 dimensional group, rated 100 µA–50 mA at 3–24 VDC, but with a different operating force (1.76 ±0.49 N); weak internal pull-ups are not automatically within that rated range either. It has not been selected or stock-checked here.
|
||||
|
||||
Retain firmware debouncing; a mechanical switch is not bounce-free. The device is unsealed (IP00), and the datasheet prohibits washing.
|
||||
|
||||
## Supply evidence
|
||||
|
||||
**Live stock not verified; no supply assurance.** On 2026-09-20 a request to the [DigiKey B3F-1000 product URL](https://www.digikey.com/en/products/detail/omron-electronics-inc-emc-div/B3F-1000/33150) returned HTTP 403 / an anti-bot challenge. No quantity, price, lead time, or lifecycle status could be established from that response. Confirm the exact MPN and available quantity with a distributor before BOM release; datasheet availability is not stock evidence.
|
||||
|
||||
## Evidence and remaining checks
|
||||
|
||||
- [Authoritative Omron datasheet](https://omronfs.omron.com/en_US/ecb/products/pdf/en-b3f.pdf), retrieved 2026-09-20.
|
||||
- [Local PDF](reference/button-omron-b3f.pdf).
|
||||
- [Extracted text](reference/button-omron-b3f.txt) for searching; use the PDF for drawing interpretation.
|
||||
- [Rendered page 4](reference/button-omron-b3f-page4.png), visually checked for the internal connections and dimensions.
|
||||
|
||||
Before manufacturing: validate the selected pull-up strategy against supply tolerances/budget; check ERC/net assignment and PCB DRC in the actual design; confirm finished holes, enclosure/actuator access and a sample's fit/continuity; confirm procurement. This review verifies datasheet-to-library compatibility, not a completed-board electrical or mechanical test.
|
||||
@@ -1,105 +0,0 @@
|
||||
# Provisional carrier module footprints
|
||||
|
||||
These project-local footprints are in `Carrier.pretty`. They are mechanical
|
||||
starting points, **not fabrication-ready connector selections**. Both are now
|
||||
assigned to their matching symbols in the native schematic through the project
|
||||
library tables and used in the routed PCB draft. The assembled module 3D models
|
||||
are not provided. See `pcb-draft-notes.md` for placement/routing and
|
||||
`rs232-footprint-notes.md` for the additional provisional RS-232 reference footprint.
|
||||
|
||||
## Shared pad/drill provenance and limits
|
||||
|
||||
Reference inspected locally: installed KiCad library footprint
|
||||
`Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical`, file
|
||||
`/usr/share/kicad/footprints/Connector_PinHeader_2.54mm.pretty/PinHeader_1x04_P2.54mm_Vertical.kicad_mod`
|
||||
(format version `20260206`; local KiCad `10.0.6-1.fc44`).
|
||||
|
||||
Both footprints use that reference's **1.70 × 1.70 mm plated through-hole pads,
|
||||
1.00 mm drills, rectangular pad 1, and circular remaining pads**, on
|
||||
`*.Cu` and `*.Mask`, with unused copper layers retained. Nominal pitch is 2.54 mm.
|
||||
Only the reference's pad/drill convention is reused, not its connector body,
|
||||
courtyard, or 3D model. HW678 pad 23 is circular: only overall pad 1 is rectangular.
|
||||
|
||||
**PROVISIONAL connector fit — awaiting the selected actual pin/socket datasheet.**
|
||||
Breadboard fit supports centre spacing, not drill size, finished-hole tolerance,
|
||||
annular-ring suitability, socket body clearance, mating height, or solderability.
|
||||
Validate those against the actual final headers and beta-test sockets and the PCB
|
||||
fabricator's process before release. No electrical nets or ESP32 GPIO assignments
|
||||
are inferred. No courtyards are supplied: module/connector assembly clearances
|
||||
remain incomplete, so automated courtyard checks cannot establish fit.
|
||||
|
||||
Coordinates below are millimetres, unrotated on the carrier front side, X right
|
||||
and Y down. Views are not mirrored. Placement on the back side or viewing the
|
||||
module from its solder side requires the appropriate KiCad transform; do not
|
||||
silently reverse the numbering.
|
||||
|
||||
## `Carrier:OLED_26mm_I2C_Provisional`
|
||||
|
||||
Origin: top-left corner of the module PCB, viewed from the front/display side,
|
||||
header at the top. User-verified geometry from the current request and the later
|
||||
mechanical-evidence section of `README.md`:
|
||||
|
||||
| Pad | Signal | X | Y |
|
||||
|---|---|---:|---:|
|
||||
| 1 | GND | 9.19 | 1.50 |
|
||||
| 2 | VCC | 11.73 | 1.50 |
|
||||
| 3 | SCL | 14.27 | 1.50 |
|
||||
| 4 | SDA | 16.81 | 1.50 |
|
||||
|
||||
- PCB boundary: (0, 0) to (26, 26), solid rectangle on `F.Fab`.
|
||||
This depicts the module, not the carrier's routed edge; no `Edge.Cuts` is used.
|
||||
- Four unnumbered, non-plated through holes, diameter 2.00 mm, at
|
||||
(1.75, 1.75), (24.25, 1.75), (1.75, 24.25), (24.25, 24.25).
|
||||
Hole centres are 1.75 mm from adjacent edges, with 22.50 mm spacing in both axes.
|
||||
These transfer the verified module hole geometry to the carrier; they do not
|
||||
select screws, inserts, bosses, or hardware clearance envelopes.
|
||||
- Display boundary: (1, 4.5) to (25.5, 21), 24.5 × 16.5 mm, dashed on `F.Fab`.
|
||||
This is the supplied display boundary, not a verified active-pixel area or case cutout.
|
||||
- Pad-to-signal mapping is documented here and in the footprint description;
|
||||
footprint pads themselves have numbers, not assigned nets.
|
||||
|
||||
The approved geometry supersedes the earlier 22.16 mm hole-spacing measurement:
|
||||
22.50 mm hole spacing, Y=1.50 mm header centres, and the centred header coordinates
|
||||
above apply. See README for the user-reported successful template fit check.
|
||||
|
||||
## `Carrier:HW678_2x22_Provisional`
|
||||
|
||||
Component/top view: antenna up, USB connectors down. Origin is the top-left header
|
||||
centre (pad 1), **not a PCB corner**.
|
||||
|
||||
- Left row: pads 1 through 22, top to bottom, X=0.
|
||||
- Right row: pads 23 through 44, top to bottom, X=25.40.
|
||||
- Y=0 through 53.34 in 2.54 mm increments for each row (21 intervals).
|
||||
- User breadboard fit verifies nominal 2.54 mm pitch and 25.40 mm row spacing;
|
||||
these supersede the earlier raw caliper dimensions in README.
|
||||
- Numbering follows the project's explicit convention, not a generic dual-row
|
||||
connector's alternating or counterclockwise numbering. Physical numbers do
|
||||
**not** establish GPIO, supply, or ground assignments.
|
||||
- Only pads and annotation text are present. **No PCB outline, antenna keepout,
|
||||
courtyard, USB envelope, mounting holes, or 3D model is guessed.** Text positions
|
||||
are annotations, not dimensional evidence. Outline registration, antenna extent,
|
||||
connector offsets/overhang, and assembly height remain unknown.
|
||||
|
||||
## Validation and release gates
|
||||
|
||||
Validation performed with KiCad 10.0.6 `pcbnew.FootprintLoad`: both footprints
|
||||
parsed successfully. Assertions passed for pad counts, all numbered pad positions,
|
||||
pad shapes/types/sizes/drills, OLED NPTH positions and diameters, both OLED `F.Fab`
|
||||
rectangle bounds, and absence of courtyards, `Edge.Cuts`, and keepout zones.
|
||||
HW678 was also checked to contain no graphical shapes. The local Python binding
|
||||
emitted three startup `PROPERTY_ENUM` assertions (also seen on a version-only
|
||||
import); these did not prevent loading or the checks from passing.
|
||||
|
||||
Repeat the retained footprint regression checks from the repository root with:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B hardware/PCB/validate_footprints.py
|
||||
```
|
||||
|
||||
This requires the system Python with KiCad's `pcbnew` binding. It checks the
|
||||
native-loaded pad counts, coordinates, pad types, sizes, drills and geometry. Parsing is
|
||||
not DRC, electrical verification, connector fit verification, or fabrication approval.
|
||||
Print both footprints at 1:1 and check the real assemblies, especially HW678 row
|
||||
numbering/orientation. Before layout release, select and verify connectors, map
|
||||
HW678 pins from reliable board evidence, and establish module envelopes,
|
||||
antenna restrictions, USB access, mounting hardware, and courtyards.
|
||||
@@ -1,6 +0,0 @@
|
||||
(fp_lib_table (version 7)
|
||||
(lib (name "Carrier")(type "KiCad")(uri "${KIPRJMOD}/Carrier.pretty")(options "")(descr "Provisional module carrier footprints; see footprint-notes.md"))
|
||||
(lib (name "Button_Switch_THT")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/Button_Switch_THT.pretty")(options "")(descr "KiCad standard THT switches"))
|
||||
(lib (name "Resistor_THT")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/Resistor_THT.pretty")(options "")(descr "KiCad standard axial resistors"))
|
||||
(lib (name "MountingHole")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/MountingHole.pretty")(options "")(descr "KiCad standard mounting holes"))
|
||||
)
|
||||
@@ -1,221 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create the compact placement draft from a fresh KiCad XML netlist.
|
||||
|
||||
Requires system Python + pcbnew. Refuses to overwrite an existing PCB unless
|
||||
--overwrite is explicit. This is a draft generator, not a manufacturing tool.
|
||||
Does not modify the schematic or footprint libraries. KiCad SaveBoard also saves
|
||||
PCB design settings into the project; close the project before regeneration.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
import pcbnew as p
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / "serial-carrier.kicad_pcb"
|
||||
LIBS = Path("/usr/share/kicad/footprints")
|
||||
ORIGIN = (100, 100)
|
||||
OUTLINE = [(0,0), (82,0), (82,80), (36,80), (36,61), (0,61)]
|
||||
PLACEMENT = {
|
||||
"U1": ("Carrier:HW678_2x22_Provisional", 8, 4),
|
||||
"U2": ("Carrier:MAX3243_Reference_Provisional", 43, 30),
|
||||
"DS1": ("Carrier:OLED_26mm_I2C_Provisional", 43, 36),
|
||||
"R1": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 43, 34),
|
||||
"R2": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 54, 34),
|
||||
"R3": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 65, 34),
|
||||
"SW1": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 43, 68),
|
||||
"SW2": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 55, 68),
|
||||
"SW3": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 67, 68),
|
||||
}
|
||||
MOUNTS = [(3.5,3.5), (3.5,56), (78,4), (78,76)]
|
||||
# Deliberate allocation, NOT a measured antenna envelope or an RF guarantee.
|
||||
RF_RESERVE = [(10,0), (31,0), (31,13), (10,13)]
|
||||
|
||||
def mm(v):
|
||||
return p.FromMM(v)
|
||||
|
||||
def xy(x,y):
|
||||
return p.VECTOR2I(mm(x+ORIGIN[0]), mm(y+ORIGIN[1]))
|
||||
|
||||
def text(board, value, x, y, layer=p.F_SilkS, size=1):
|
||||
item = p.PCB_TEXT(board)
|
||||
item.SetText(value)
|
||||
item.SetPosition(xy(x,y))
|
||||
item.SetLayer(layer)
|
||||
item.SetTextSize(p.VECTOR2I(mm(size),mm(size)))
|
||||
item.SetTextThickness(mm(0.15))
|
||||
board.Add(item)
|
||||
return item
|
||||
|
||||
def polygon(zone, points):
|
||||
poly=zone.Outline()
|
||||
poly.NewOutline()
|
||||
for x,y in points:
|
||||
pt=xy(x,y)
|
||||
poly.Append(pt.x,pt.y)
|
||||
|
||||
def load(lib_id):
|
||||
lib,name=lib_id.split(":",1)
|
||||
folder=ROOT / "Carrier.pretty" if lib=="Carrier" else LIBS / (lib+".pretty")
|
||||
fp=p.FootprintLoad(str(folder),name)
|
||||
if fp is None:
|
||||
raise RuntimeError("Cannot load "+lib_id)
|
||||
fp.SetFPIDAsString(lib_id)
|
||||
return fp
|
||||
|
||||
def quiet_footprint(fp):
|
||||
# Retain all mechanical geometry, but replace sprawling research annotation
|
||||
# text with concise board annotations. Libraries are never modified.
|
||||
for graphic in list(fp.GraphicalItems()):
|
||||
if isinstance(graphic,p.PCB_TEXT):
|
||||
fp.Remove(graphic)
|
||||
fp.Value().SetVisible(False)
|
||||
fp.Reference().SetTextSize(p.VECTOR2I(mm(1),mm(1)))
|
||||
fp.Reference().SetTextThickness(mm(0.15))
|
||||
fp.Reference().SetLayer(p.F_SilkS)
|
||||
|
||||
|
||||
def copy_schematic_fields(fp, component):
|
||||
for field in component.findall("fields/field"):
|
||||
name=field.get("name")
|
||||
if name in ("Reference", "Value", "Footprint"):
|
||||
continue
|
||||
fp.SetField(name, field.text or "")
|
||||
fp.GetField(name).SetVisible(False)
|
||||
|
||||
|
||||
def configure_draft_netclasses():
|
||||
# SaveBoard creates the project defaults. Align future interactive routing
|
||||
# with the draft's physical minimum widths and its explicit power routing.
|
||||
path=ROOT/"serial-carrier.kicad_pro"
|
||||
project=json.loads(path.read_text())
|
||||
settings=project["net_settings"]
|
||||
default=next(c for c in settings["classes"] if c["name"]=="Default")
|
||||
default.update(clearance=0.25, track_width=0.25, via_diameter=0.7, via_drill=0.3)
|
||||
power=dict(default, name="Power", priority=0, track_width=0.5, diff_pair_width=0.25)
|
||||
settings["classes"]=[c for c in settings["classes"] if c["name"]!="Power"]+[power]
|
||||
patterns=[v for v in settings.get("netclass_patterns",[]) if v["pattern"] not in ("/+3V3","/GND")]
|
||||
settings["netclass_patterns"]=patterns+[{"netclass":"Power","pattern":name} for name in ("/+3V3","/GND")]
|
||||
path.write_text(json.dumps(project,indent=2)+"\n")
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--overwrite",action="store_true")
|
||||
args=parser.parse_args()
|
||||
if PCB.exists() and not args.overwrite:
|
||||
parser.error("PCB exists; preserve manual edits or explicitly use --overwrite")
|
||||
if any(ROOT.glob("~serial-carrier.*.lck")):
|
||||
parser.error("Project is open in KiCad; close it before regenerating PCB/settings")
|
||||
netfile=ROOT / "validation/pcb-source-netlist.xml"
|
||||
subprocess.run(["kicad-cli","sch","export","netlist","--format","kicadxml","-o",str(netfile),str(ROOT/"serial-carrier.kicad_sch")],check=True)
|
||||
source=ET.parse(netfile).getroot()
|
||||
components={c.get("ref"):c for c in source.findall("components/comp")}
|
||||
assert set(components)==set(PLACEMENT), "Schematic component set changed; review layout"
|
||||
schematic=ROOT.joinpath("serial-carrier.kicad_sch").read_text()
|
||||
root_uuid=re.search(r'\(uuid "?([a-f0-9-]{36})"?\)',schematic).group(1)
|
||||
board=p.BOARD()
|
||||
board.SetCopperLayerCount(2)
|
||||
settings=board.GetDesignSettings()
|
||||
settings.SetBoardThickness(mm(1.6))
|
||||
settings.m_MinClearance=mm(0.25)
|
||||
settings.m_CopperEdgeClearance=mm(0.5)
|
||||
settings.m_HoleClearance=mm(0.25)
|
||||
settings.m_TrackMinWidth=mm(0.25)
|
||||
settings.m_ViasMinSize=mm(0.7)
|
||||
# SaveBoard persists these proposed draft rules into the project settings.
|
||||
title=board.GetTitleBlock()
|
||||
title.SetTitle("Serial Swiss Army Knife - 82x80 mm carrier draft")
|
||||
title.SetRevision("A0 DRAFT")
|
||||
title.SetComment(0,"NOT FOR FABRICATION - male module / USB / antenna geometry unverified")
|
||||
pad_nets={}
|
||||
for source_net in source.findall("nets/net"):
|
||||
net=p.NETINFO_ITEM(board,source_net.get("name"))
|
||||
board.Add(net)
|
||||
for node in source_net.findall("node"):
|
||||
pad_nets[node.get("ref"),node.get("pin")]=(net,node)
|
||||
for ref,(lib_id,x,y) in PLACEMENT.items():
|
||||
comp=components[ref]
|
||||
if ref != "U2":
|
||||
assert comp.findtext("footprint")==lib_id,(ref,"Schematic footprint changed")
|
||||
fp=load(lib_id)
|
||||
fp.SetReference(ref)
|
||||
fp.SetValue(comp.findtext("value"))
|
||||
path=p.KIID_PATH()
|
||||
path.push_back(p.KIID(root_uuid))
|
||||
path.push_back(p.KIID(comp.findtext("tstamps")))
|
||||
fp.SetPath(path)
|
||||
fp.SetSheetfile("serial-carrier.kicad_sch")
|
||||
fp.SetSheetname("serial-carrier")
|
||||
fp.SetPosition(xy(x,y))
|
||||
board.Add(fp)
|
||||
quiet_footprint(fp)
|
||||
copy_schematic_fields(fp,comp)
|
||||
if ref=="U1":
|
||||
fp.Reference().SetPosition(xy(20.7,25))
|
||||
elif ref=="U2":
|
||||
fp.Reference().SetPosition(xy(56.97,18))
|
||||
elif ref=="DS1":
|
||||
fp.Reference().SetPosition(xy(56,49))
|
||||
elif ref.startswith("SW"):
|
||||
fp.Reference().SetPosition(xy(x+3.25,y-2))
|
||||
else:
|
||||
fp.Reference().SetLayer(p.F_Fab)
|
||||
fp.Reference().SetPosition(xy(x+3.81,y-1.6))
|
||||
for pad in fp.Pads():
|
||||
key=ref,pad.GetNumber()
|
||||
if pad.GetNumber():
|
||||
net,node=pad_nets[key]
|
||||
pad.SetNet(net)
|
||||
pad.SetPinFunction(node.get("pinfunction", ""))
|
||||
pad.SetPinType(node.get("pintype", "passive"))
|
||||
for i,(x,y) in enumerate(MOUNTS,1):
|
||||
fp=load("MountingHole:MountingHole_3.2mm_M3")
|
||||
fp.SetReference("H"+str(i))
|
||||
fp.SetValue("M3 mounting proposal / 3.2mm")
|
||||
fp.SetBoardOnly(True)
|
||||
fp.SetExcludedFromBOM(True)
|
||||
fp.SetExcludedFromPosFiles(True)
|
||||
fp.SetPosition(xy(x,y))
|
||||
board.Add(fp)
|
||||
quiet_footprint(fp)
|
||||
fp.Reference().SetVisible(False)
|
||||
for a,b in zip(OUTLINE,OUTLINE[1:]+OUTLINE[:1]):
|
||||
edge=p.PCB_SHAPE(board)
|
||||
edge.SetShape(p.SHAPE_T_SEGMENT)
|
||||
edge.SetStart(xy(*a)); edge.SetEnd(xy(*b))
|
||||
edge.SetLayer(p.Edge_Cuts); edge.SetWidth(mm(0.05))
|
||||
board.Add(edge)
|
||||
reserve=p.ZONE(board)
|
||||
reserve.SetIsRuleArea(True)
|
||||
layers=p.LSET(); layers.AddLayer(p.F_Cu); layers.AddLayer(p.B_Cu)
|
||||
reserve.SetLayerSet(layers)
|
||||
reserve.SetZoneName("PROVISIONAL RF RESERVE - verify actual antenna clearance")
|
||||
reserve.SetDoNotAllowTracks(True); reserve.SetDoNotAllowVias(True)
|
||||
reserve.SetDoNotAllowZoneFills(True); reserve.SetDoNotAllowPads(True)
|
||||
reserve.SetDoNotAllowFootprints(False) # the module itself spans this region
|
||||
polygon(reserve,RF_RESERVE)
|
||||
board.Add(reserve)
|
||||
text(board,"RF RESERVE",20.5,7,p.Dwgs_User,1.2)
|
||||
text(board,"VERIFY ANTENNA",20.5,10,p.Dwgs_User,1)
|
||||
text(board,"ESP32 HW678",20.7,29)
|
||||
text(board,"USB ACCESS - VERIFY",18,64,p.Dwgs_User,1)
|
||||
text(board,"DE-9 OUT / VERIFY MALE",56.97,1.5,p.Dwgs_User,1)
|
||||
text(board,"MAX3243 REF",56.97,21)
|
||||
text(board,"26mm OLED",56,52)
|
||||
for label,x in zip(("BACK","SELECT","NEXT"),(46.25,58.25,70.25)):
|
||||
text(board,label,x,75, size=0.9)
|
||||
text(board,"DRAFT - NOT FOR FAB",56,78,size=0.9)
|
||||
text(board,"82 x 80 mm / 2 copper layers",41,-5,p.Dwgs_User,1.5)
|
||||
text(board,"Carrier envelope only; module overhang and connector clearance not qualified",41,-2.5,p.Dwgs_User,0.9)
|
||||
board.BuildConnectivity()
|
||||
p.SaveBoard(str(PCB),board)
|
||||
configure_draft_netclasses()
|
||||
print("Created",PCB,"with 9 circuit footprints, 4 proposed M3 mounts, 2 copper layers.")
|
||||
print("Placement draft only. Board-only U2 footprint is a female-CAD reference, not male qualification.")
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
@@ -1,167 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the initial native KiCad carrier schematic (Python stdlib only).
|
||||
|
||||
Explicit bootstrap/regeneration tool: overwrites the generated schematic, symbol
|
||||
library and library tables. Do not run over subsequent manual KiCad edits without
|
||||
reviewing/backing them up. It does not regenerate footprints or the fit template.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import uuid
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PROJECT = "serial-carrier"
|
||||
NS = uuid.UUID("b6f75dd1-a843-42f2-9067-57c995968145")
|
||||
|
||||
def uid(key):
|
||||
return str(uuid.uuid5(NS, key))
|
||||
|
||||
def q(value):
|
||||
return json.dumps(str(value), ensure_ascii=False)
|
||||
|
||||
def n(value):
|
||||
return f"{value:.4f}".rstrip("0").rstrip(".") if value else "0"
|
||||
|
||||
def effects(size=1.27, extra=""):
|
||||
return f"(effects (font (size {size} {size})) {extra})"
|
||||
|
||||
def prop(name, value, x=0, y=0, hidden=False):
|
||||
return f'(property {q(name)} {q(value)} (at {n(x)} {n(y)} 0) {effects(extra="(hide yes)" if hidden else "")})'
|
||||
|
||||
# Physical module numbering is a project convention, not ESP32 GPIO numbering:
|
||||
# component side, antenna up / USB down; left row 1..22, right row 23..44.
|
||||
left = ["3V3", "3V3", "EN", "GPIO4", "GPIO5", "GPIO6", "GPIO7", "GPIO15", "GPIO16", "GPIO17", "GPIO18", "GPIO8", "GPIO3", "GPIO46", "GPIO9", "GPIO10", "GPIO11", "GPIO12", "GPIO13", "GPIO14", "5V", "GND"]
|
||||
right = ["GND", "GPIO43", "GPIO44", "GPIO1", "GPIO2", "GPIO42", "GPIO41", "GPIO40", "GPIO39", "GPIO38", "GPIO37", "GPIO36", "GPIO35", "GPIO0", "GPIO45", "GPIO48", "GPIO47", "GPIO21", "GPIO20", "GPIO19", "GND", "GND"]
|
||||
GPIO_NET = {4:"RS_DCD",5:"RS_DSR",6:"RS_RI",7:"RS_DTR",8:"RS_VALID",9:"RS_OFF_N",10:"BTN_PREVIOUS",11:"OLED_SDA",12:"OLED_SCL",13:"BTN_SELECT",14:"BTN_NEXT",15:"RS_RTS",16:"RS_CTS",17:"RS_TX",18:"RS_RX"}
|
||||
OUTPUTS = {7, 12, 15, 17}
|
||||
INPUTS = {4, 5, 6, 8, 10, 13, 14, 16, 18}
|
||||
|
||||
def gpio_type(name):
|
||||
if not name.startswith("GPIO"):
|
||||
return "input" if name == "EN" else "passive"
|
||||
number = int(name[4:])
|
||||
if number == 9:
|
||||
# Model the programmable MCU pad, not a discrete open-collector device.
|
||||
# The required firmware open-drain mode and module pull-up are noted on sheet.
|
||||
return "bidirectional"
|
||||
return "output" if number in OUTPUTS else "input" if number in INPUTS else "bidirectional"
|
||||
|
||||
# Pin tuples: physical number, name, electrical type, local X, local Y, angle.
|
||||
MCU = []
|
||||
for side, names in enumerate((left, right)):
|
||||
for index, name in enumerate(names):
|
||||
number = index + 1 + side * 22
|
||||
kind = "power_out" if number in (1, 22) else gpio_type(name)
|
||||
MCU.append((str(number), name, kind, -22.86 if side == 0 else 22.86, 26.67-index*2.54, 0 if side == 0 else 180))
|
||||
|
||||
# Published Adafruit 5988 CAD reference; male 6253 correspondence is provisional.
|
||||
RS_NAMES = ["Vin", "GND", "DCD", "RX", "TX", "DTR", "DSR", "RTS", "CTS", "RI", "VLD", "OFF_N"]
|
||||
RS = [(str(i+1), name, "power_in" if i < 2 else "input" if name in ("TX","DTR","RTS","OFF_N") else "output", -20.32, 13.97-i*2.54, 0) for i,name in enumerate(RS_NAMES)]
|
||||
OLED = [(str(i+1),name,kind,-15.24,3.81-i*2.54,0) for i,(name,kind) in enumerate((("GND","power_in"),("VCC","power_in"),("SCL","input"),("SDA","bidirectional")))]
|
||||
TWO = [("1","~","passive",-5.08,0,0),("2","~","passive",5.08,0,180)]
|
||||
|
||||
SPECS = {
|
||||
"HW678_N16R8": (MCU, 17.78, 29.21, "U", "Carrier:HW678_2x22_Provisional", "HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained"),
|
||||
"MAX3243_Module_Reference": (RS, 15.24, 17.78, "U", "", "Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE"),
|
||||
"OLED_I2C_26mm": (OLED, 10.16, 7.62, "DS", "Carrier:OLED_26mm_I2C_Provisional", "User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA"),
|
||||
"SW_NO": (TWO, 2.54, 2.54, "SW", "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", "Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"),
|
||||
"R": (TWO, 2.54, 1.016, "R", "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", "Resistor, axial DIN0207 footprint; exact MPN pending")
|
||||
}
|
||||
|
||||
def symbol_definition(name, embedded=False):
|
||||
pins, halfwidth, halfheight, ref, footprint, description = SPECS[name]
|
||||
out = [f'(symbol {q("Carrier:"+name if embedded else name)} (pin_names (offset 0.762)) (in_bom yes) (on_board yes)',
|
||||
prop("Reference", ref, 0, halfheight+5.08), prop("Value", name, 0, halfheight+2.54),
|
||||
prop("Footprint", footprint, hidden=True), prop("Datasheet", "", hidden=True), prop("Description",description,hidden=True)]
|
||||
if name == "SW_NO":
|
||||
shape = '(polyline (pts (xy -2.54 0) (xy 2.54 1.524)) (stroke (width 0.254) (type default)) (fill (type none)))'
|
||||
shape += ''.join(f'(circle (center {x} 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none)))' for x in (-2.54,2.54))
|
||||
else:
|
||||
shape = f'(rectangle (start {-halfwidth} {halfheight}) (end {halfwidth} {-halfheight}) (stroke (width 0.254) (type default)) (fill (type background)))'
|
||||
out.append(f'(symbol {q(name+"_0_1")} {shape})')
|
||||
out.append(f'(symbol {q(name+"_1_1")}')
|
||||
for number,label,kind,x,y,angle in pins:
|
||||
length = abs(x)-halfwidth
|
||||
out.append(f'(pin {kind} line (at {n(x)} {n(y)} {angle}) (length {n(length)}) (name {q(label)} {effects(1.016)}) (number {q(number)} {effects(1.016)}))')
|
||||
out.extend([')', ')'])
|
||||
return '\n'.join(out)
|
||||
|
||||
ROOT.joinpath("Carrier.kicad_sym").write_text('(kicad_symbol_lib (version 20231120) (generator "kicad_symbol_editor")\n'+'\n'.join(symbol_definition(name) for name in SPECS)+'\n)\n')
|
||||
ROOT.joinpath("sym-lib-table").write_text('(sym_lib_table (version 7)\n (lib (name "Carrier")(type "KiCad")(uri "${KIPRJMOD}/Carrier.kicad_sym")(options "")(descr "Project-local module interfaces"))\n)\n')
|
||||
ROOT.joinpath("fp-lib-table").write_text('(fp_lib_table (version 7)\n (lib (name "Carrier")(type "KiCad")(uri "${KIPRJMOD}/Carrier.pretty")(options "")(descr "Provisional module carrier footprints; see footprint-notes.md"))\n (lib (name "Button_Switch_THT")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/Button_Switch_THT.pretty")(options "")(descr "KiCad standard THT switches"))\n (lib (name "Resistor_THT")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/Resistor_THT.pretty")(options "")(descr "KiCad standard axial resistors"))\n (lib (name "MountingHole")(type "KiCad")(uri "${KICAD10_FOOTPRINT_DIR}/MountingHole.pretty")(options "")(descr "KiCad standard mounting holes"))\n)\n')
|
||||
# Preserve project settings on regeneration.
|
||||
project_file = ROOT / (PROJECT + ".kicad_pro")
|
||||
if not project_file.exists():
|
||||
project_file.write_text(json.dumps({"meta":{"filename":project_file.name,"version":3}},indent=2)+"\n")
|
||||
|
||||
root_uuid = uid("root")
|
||||
items = [f'(kicad_sch (version 20250114) (generator "eeschema") (uuid {q(root_uuid)}) (paper "A3")',
|
||||
'(title_block (title "ESP32 Serial Swiss Army Knife - Module Carrier") (date "2026-09-20") (rev "A0 DRAFT") (comment 1 "Not fabrication-ready: provisional module footprints and male RS-232 mapping"))',
|
||||
'(lib_symbols '+'\n'.join(symbol_definition(name,True) for name in SPECS)+')']
|
||||
expected = {}
|
||||
|
||||
def wire(x1,y1,x2,y2,key):
|
||||
items.append(f'(wire (pts (xy {n(x1)} {n(y1)}) (xy {n(x2)} {n(y2)})) (stroke (width 0) (type default)) (uuid {q(uid("wire:"+key))}))')
|
||||
|
||||
def label(net,x,y,key):
|
||||
items.append(f'(label {q(net)} (at {n(x)} {n(y)} 0) {effects(1.016,"(justify left bottom)")} (uuid {q(uid("label:"+key))}))')
|
||||
|
||||
def note(text,x,y,size=1.27):
|
||||
items.append(f'(text {q(text)} (at {n(x)} {n(y)} 0) {effects(size,"(justify left top)")} (uuid {q(uid("text:"+text))}))')
|
||||
|
||||
def place(name,ref,value,x,y,nets,extra=None):
|
||||
pins,hw,hh,_,footprint,description = SPECS[name]
|
||||
instance = [f'(symbol (lib_id {q("Carrier:"+name)}) (at {n(x)} {n(y)} 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid {q(uid(ref))})',
|
||||
prop("Reference",ref,x,y-hh-5.08),prop("Value",value,x,y-hh-2.54),prop("Footprint",footprint,x,y,True),prop("Datasheet","",x,y,True)]
|
||||
for k,v in (extra or {}).items():
|
||||
instance.append(prop(k,v,x,y,True))
|
||||
instance.extend(f'(pin {q(p[0])} (uuid {q(uid(ref+":"+p[0]))}))' for p in pins)
|
||||
instance.append(f'(instances (project {q(PROJECT)} (path {q("/"+root_uuid)} (reference {q(ref)}) (unit 1)))))')
|
||||
items.append('\n'.join(instance))
|
||||
for number,_,_,px,py,angle in pins:
|
||||
ax,ay=x+px,y-py
|
||||
net=nets.get(number)
|
||||
key=ref+":"+number
|
||||
if net is None:
|
||||
items.append(f'(no_connect (at {n(ax)} {n(ay)}) (uuid {q(uid("nc:"+key))}))')
|
||||
else:
|
||||
end=ax-12.7 if angle==0 else ax+12.7
|
||||
wire(ax,ay,end,ay,key)
|
||||
label(net,end,ay,key)
|
||||
expected.setdefault(net,[]).append([ref,number])
|
||||
|
||||
mcu_nets={}
|
||||
for number,name,*_ in MCU:
|
||||
if name == "3V3":
|
||||
mcu_nets[number]="+3V3"
|
||||
elif name == "GND":
|
||||
mcu_nets[number]="GND"
|
||||
elif name.startswith("GPIO") and int(name[4:]) in GPIO_NET:
|
||||
mcu_nets[number]=GPIO_NET[int(name[4:])]
|
||||
place("HW678_N16R8","U1","HW678 / S3-N16R8",95.25,109.22,mcu_nets,{"Status":"Header geometry nominal; outline/antenna clearance pending","Pin_numbering":"Left 1-22; right 23-44; both top-to-bottom, antenna up"})
|
||||
rs_nets=dict(zip(map(str,range(1,13)),["+3V3","GND","RS_DCD","RS_RX","RS_TX","RS_DTR","RS_DSR","RS_RTS","RS_CTS","RS_RI","RS_VALID","RS_OFF_N"]))
|
||||
place("MAX3243_Module_Reference","U2","Adafruit MAX3243 / male 6253",228.6,96.52,rs_nets,{"Status":"VERIFY male header numbering; footprint intentionally unassigned","Datasheet_source":"adafruit-research.md; reference female 5988 CAD"})
|
||||
place("OLED_I2C_26mm","DS1","128x64 I2C OLED / 0x3C",228.6,152.4,{"1":"GND","2":"+3V3","3":"OLED_SCL","4":"OLED_SDA"},{"Status":"Module outline/header/display user fit-verified; pad/drill fit provisional"})
|
||||
for index,(net,function) in enumerate((("BTN_PREVIOUS","Previous / back"),("BTN_SELECT","Select / confirm"),("BTN_NEXT","Next")),1):
|
||||
y=88.9+(index-1)*35.56
|
||||
place("R",f"R{index}","2.2k 1%",327.66,y,{"1":"+3V3","2":net},{"Rating":"At least 0.125 W; exact resistor MPN pending"})
|
||||
place("SW_NO",f"SW{index}","B3F-1000",327.66,y+15.24,{"1":net,"2":"GND"},{"Manufacturer":"Omron","MPN":"B3F-1000","Function":function,"Procurement":"Live stock not verified"})
|
||||
note(function,302.26,y+21.59,1.016)
|
||||
|
||||
note("MODULE CARRIER - INITIAL SCHEMATIC",15.24,15.24,2.54)
|
||||
note("Native KiCad 10 schematic. Named wire labels join matching nets on this sheet.\nRead hardware/PCB/README.md before layout or fabrication.",15.24,24.13)
|
||||
note("U1: complete development board",53.34,57.15,1.778)
|
||||
note("Antenna up / USB down: left row 1-22, right row 23-44.\nNC marks mean unused on carrier, not unused inside module.",53.34,64.77,1.016)
|
||||
note("U2: complete RS-232 breakout",177.8,57.15,1.778)
|
||||
note("Male 6253 selected. Numbers from published 5988 female CAD.\nVERIFY male header before routing; no footprint assigned.\nDE-9 and charge pump are already on the module.",177.8,64.77,1.016)
|
||||
note("Controls: active-low, firmware-debounced",292.1,57.15,1.778)
|
||||
note("External 2.2k pull-ups: ~1.5 mA pressed at 3.3 V.\nChosen for B3F-1000 rated contact load; internal\npull-ups may remain enabled. Exact resistor MPN pending.",292.1,64.77,1.016)
|
||||
note("DS1: front view GND / VCC / SCL / SDA",177.8,128.27,1.524)
|
||||
note("Power at 3.3 V only. Verify on-module I2C pull-ups;\nexternal values intentionally not guessed.",177.8,165.1,1.016)
|
||||
note("POWER / RECOVERY\nPower only through U1's existing USB ports. No carrier 5 V input.\nU1 regulator supplies +3V3; GND pins are common on the module.\nPin 1 +3V3 and pin 22 GND model the onboard supply for ERC.\nGPIO19/20 native USB and GPIO43/44 UART0 remain onboard.\nVerify USB power isolation before attaching two powered hosts.\nKeep both USB connectors and reset/boot buttons accessible.",15.24,185.42)
|
||||
note("RS-232 / ELECTRICAL LIMITS\nTX/RX and modem labels are at the ESP32 logic side.\nOFF_N is active low; GPIO9 must remain open-drain.\nBreakout provides its OFF pull-up. No galvanic isolation.\nNo carrier connection to raw RS-232 voltages.\nCarrier GND, USB GND and RS-232 signal ground are common.\nNever power OLED or MAX3243 logic from 5 V.",152.4,185.42)
|
||||
note("BEFORE PCB RELEASE\nVerify male breakout pin order/footprint.\nConfirm header/socket finished-hole requirements.\nComplete module courtyards and antenna clearance.\nVerify 3.3 V budget, OLED pull-ups and USB power path.\nChoose mounting hardware and confirm button stock.\nERC is not hardware or fabrication approval.",292.1,185.42)
|
||||
items.append(')')
|
||||
ROOT.joinpath(PROJECT+".kicad_sch").write_text('\n'.join(items)+'\n')
|
||||
ROOT.joinpath("validation/expected-nets.json").write_text(json.dumps(expected,indent=2,sort_keys=True)+'\n')
|
||||
print("Generated native KiCad schematic, project-local symbol library and tables.")
|
||||
@@ -1,37 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100mm" height="90mm" viewBox="0 0 100 90">
|
||||
<title>OLED module mechanical fit check — front view</title>
|
||||
<desc>26 mm square PCB, four 2 mm mounting holes at 1.75 mm edge offsets. Centred four-pin header at 2.54 mm pitch, 1.50 mm from top edge. Display outline at (1, 4.5), measuring 24.5 × 16.5 mm. Header crosses indicate centres only, not drill sizes.</desc>
|
||||
<g font-family="sans-serif" font-size="3" fill="black">
|
||||
<text x="5" y="7">OLED mechanical fit check — front view</text>
|
||||
<text x="5" y="12">Print at 100%; disable fit-to-page.</text>
|
||||
<text x="5" y="17">Verify the scale below before use.</text>
|
||||
</g>
|
||||
<g transform="translate(35 27)" stroke="black" stroke-width="0.15" fill="none">
|
||||
<rect width="26" height="26"/>
|
||||
<rect id="display-outline" x="1" y="4.5" width="24.5" height="16.5" stroke-dasharray="0.6 0.4"/>
|
||||
<circle cx="1.75" cy="1.75" r="1"/>
|
||||
<circle cx="24.25" cy="1.75" r="1"/>
|
||||
<circle cx="1.75" cy="24.25" r="1"/>
|
||||
<circle cx="24.25" cy="24.25" r="1"/>
|
||||
<path id="header-centres" d="M8.79 1.5h.8 M9.19 1.1v.8 M11.33 1.5h.8 M11.73 1.1v.8 M13.87 1.5h.8 M14.27 1.1v.8 M16.41 1.5h.8 M16.81 1.1v.8"/>
|
||||
<g stroke="none" fill="black" font-family="sans-serif" font-size="1.5" text-anchor="middle">
|
||||
<text x="9.19" y="-1">GND</text>
|
||||
<text x="11.73" y="-1">VCC</text>
|
||||
<text x="14.27" y="-1">SCL</text>
|
||||
<text x="16.81" y="-1">SDA</text>
|
||||
<text x="13" y="7">Dashed: display outline</text>
|
||||
<text x="13" y="10">24.5 × 16.5 mm</text>
|
||||
<text x="13" y="13">26 × 26 mm PCB</text>
|
||||
<text x="13" y="16">Holes: Ø2 mm</text>
|
||||
<text x="13" y="19">Hole spacing: 22.50 mm</text>
|
||||
</g>
|
||||
</g>
|
||||
<g stroke="black" stroke-width="0.15" fill="none">
|
||||
<path d="M35 65h20 M35 63v4 M55 63v4"/>
|
||||
</g>
|
||||
<g font-family="sans-serif" font-size="2.5" fill="black">
|
||||
<text x="39" y="71">20 mm scale</text>
|
||||
<text x="5" y="79">Header crosses mark pin centres, not drill sizes.</text>
|
||||
<text x="5" y="84">Mechanical reference only — not a fabrication file.</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -1,173 +0,0 @@
|
||||
# Compact two-layer PCB — A0 draft
|
||||
|
||||
Open **`serial-carrier.kicad_pcb`** in KiCad 10. The routed board belongs to the
|
||||
existing `serial-carrier.kicad_pro` / `.kicad_sch` project. **Not for fabrication.**
|
||||
No Gerbers, production drills or manufacturing archive were generated.
|
||||
|
||||
## Size and placement
|
||||
|
||||
- **Carrier bounding dimensions: 82 × 80 mm**, within the requested 100 × 100 mm.
|
||||
- **Two copper layers**, F.Cu and B.Cu. All components mount on the front.
|
||||
- Proposed **1.6 mm** carrier thickness; fabrication stackup/process not selected.
|
||||
- Stepped outline: a **36 × 19 mm lower-left cutaway** leaves cable space below
|
||||
the ESP32 while the OLED/buttons occupy the deeper right-hand section.
|
||||
- Module and mating-plug overhangs are not included in the carrier dimensions.
|
||||
The final complete assembly/enclosure envelope still needs measurement.
|
||||
|
||||
Coordinates in the following table are local millimetres from the carrier's
|
||||
upper-left corner, X right/Y down, viewed from the component side. In the KiCad
|
||||
file add (100, 100) mm to obtain absolute board coordinates. All footprints are
|
||||
unrotated; their origins are defined in the footprint notes, not necessarily
|
||||
component body corners.
|
||||
|
||||
| Reference | Local origin (X, Y) | Placement |
|
||||
|---|---|---|
|
||||
| U1 | (8, 4) | ESP32 left header pin 1; antenna toward upper edge, USB toward cutaway |
|
||||
| U2 | (43, 30) | MAX3243 header pin 1; DE-9 toward upper edge |
|
||||
| DS1 | (43, 36) | OLED module upper-left PCB corner; display faces up |
|
||||
| R1 / R2 / R3 | (43 / 54 / 65, 34) | Axial pull-ups in the gap between RS-232 and OLED |
|
||||
| SW1 / SW2 / SW3 | (43 / 55 / 67, 68) | Back, select, next below display; switch pad 1 origins |
|
||||
| H1 | (3.5, 3.5) | Proposed case mounting hole |
|
||||
| H2 | (3.5, 56) | Proposed case mounting hole |
|
||||
| H3 | (78, 4) | Proposed case mounting hole |
|
||||
| H4 | (78, 76) | Proposed case mounting hole |
|
||||
|
||||
Outline vertices: (0,0), (82,0), (82,80), (36,80), (36,61), (0,61), closed.
|
||||
The board area is **58.76 cm²**; its bounding rectangle is 65.6 cm².
|
||||
|
||||
H1–H4 are proposed **Ø3.2 mm NPTH M3 clearance holes**, not a previously agreed
|
||||
case specification. They are marked board-only and excluded from the electrical
|
||||
BOM. The OLED and RS-232 module mounting holes are additional, separate holes.
|
||||
Screw heads, spacers, heat-set inserts, tool access and underside lead clearance
|
||||
must be checked against the final case and assembly heights. No insert dimensions
|
||||
or module stand-off heights are assumed.
|
||||
|
||||
## Routing and electrical status
|
||||
|
||||
The PCB contains actual copper, not just ratsnest placement:
|
||||
|
||||
- All **38 required physical-pad spanning connections** across **17 connected
|
||||
nets** routed, including both duplicated pads of each switch contact.
|
||||
- **569 segments**, signal width **0.25 mm**, 3.3 V and GND width **0.50 mm**.
|
||||
- **20 through vias**, **0.70 mm copper / 0.30 mm drill**.
|
||||
- **No copper pours**; ground is explicitly routed. This is an initial routing
|
||||
solution, not a reviewed return-current/EMI or power-integrity design.
|
||||
- Proposed minimum different-net clearance **0.25 mm** and copper-edge clearance
|
||||
**0.50 mm**. These are selected draft design rules, not fabrication qualification.
|
||||
- Project netclasses align interactive routing defaults with the draft: Default
|
||||
uses 0.25 mm tracks / 0.25 mm clearance, while Power matches `/+3V3` and `/GND`
|
||||
with 0.50 mm tracks. Both use 0.70 / 0.30 mm vias.
|
||||
- Schematic pad-net assignments, component values and UUID linkage are retained.
|
||||
U1's intentionally unused GPIO/USB/UART0/5 V header pads remain unused.
|
||||
|
||||
The bounded draft router prioritizes connectivity and conservative geometric
|
||||
clearance, not optimal trace topology. Do not treat DRC passage as evidence of
|
||||
current-carrying capacity, supply stability, I²C signal quality or EMC compliance.
|
||||
|
||||
## Explicit provisional mechanical choices
|
||||
|
||||
### ESP32 and RF
|
||||
|
||||
A two-layer **no-tracks/no-vias/no-pads/no-zone-fill rule area** reserves local
|
||||
X=10…31 mm, Y=0…13 mm toward the antenna. This is a deliberate carrier design
|
||||
allocation, **not a measured antenna envelope or a manufacturer RF keepout**.
|
||||
The module itself may span the rule area; footprint placement is not forbidden.
|
||||
No carrier copper enters the reserve, but copper runs close to its boundary.
|
||||
|
||||
U1's exact PCB/antenna/USB offsets are still unmeasured. The intended antenna
|
||||
overhang and USB access must be checked with the actual board and a printed
|
||||
placement template. Move U1, change the edge or enlarge the reserve if required.
|
||||
Missing RF clearance cannot be signed off from the current footprint alone.
|
||||
|
||||
### RS-232
|
||||
|
||||
U2 uses the new **`Carrier:MAX3243_Reference_Provisional`** footprint **on the PCB
|
||||
only**. Its bare-module outline/header/holes are adapted from published female
|
||||
5988 CAD for the intended male 6253 module. The user reports identical dimensions,
|
||||
but the male header map and assembled DE-9 envelope are still unverified.
|
||||
See `rs232-footprint-notes.md` and its CC BY-SA attribution.
|
||||
|
||||
The schematic footprint remains intentionally blank, preserving its caution
|
||||
and avoiding edits to the schematic that was open in KiCad. Consequently there
|
||||
is **one known schematic-parity warning**: U2 footprint differs from the empty
|
||||
schematic assignment. This is not excluded/suppressed, and is not a net mismatch.
|
||||
Once the male footprint is qualified, explicitly assign it in the schematic
|
||||
before relying on Update PCB from Schematic to manage U2's footprint.
|
||||
|
||||
### OLED and controls
|
||||
|
||||
OLED geometry, including its display boundary, is the user-fit-verified geometry.
|
||||
The module schematic/library files are unchanged. The new overall placement still
|
||||
needs an assembly fit check: adjacent resistor bodies, module underside parts,
|
||||
header/socket bodies, button actuation and finger/plunger clearance are not
|
||||
represented by complete assembly courtyards. In particular the axial resistors
|
||||
are tightly packed between the module outlines; confirm clearance and height.
|
||||
|
||||
## Review outputs
|
||||
|
||||
- `validation/pcb-draft.svg` / `pcb-draft.png`: combined copper/outline/fab review.
|
||||
- `validation/pcb-draft-3d.png`: KiCad render of the carrier and available standard
|
||||
component models. **ESP32, OLED and RS-232 assembled module models are absent**;
|
||||
the render is not an assembled-device or enclosure fit check.
|
||||
- `pcb-placement-draft.pdf`: **1:1** front placement/pad/outline drawing. Print at
|
||||
100%, disable fit-to-page, and confirm the outer width is 82 mm before use.
|
||||
It includes only known module outlines; no guessed U1 body or DE-9 envelope.
|
||||
|
||||
## Validation and limits
|
||||
|
||||
Run from the repository root with the system Python containing KiCad bindings:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B hardware/PCB/validate_pcb_draft.py
|
||||
/usr/bin/python3 -B hardware/PCB/validation/pcb-routing-check.py
|
||||
```
|
||||
|
||||
The first script exports a **fresh saved-schematic netlist** and checks the actual
|
||||
PCB pad assignments, component linkage, dimensions, layer count, nominal thickness,
|
||||
mounting holes, track/via sizes and KiCad DRC. It never rewrites the PCB or project.
|
||||
It reports the single expected U2 parity warning explicitly, and fails on any
|
||||
other parity issue, DRC violation or unconnected item. Reports are retained in
|
||||
`validation/pcb-parity-drc.json` and `pcb-validation-summary.json`.
|
||||
|
||||
The second independently checks continuous copper/hole/edge geometry and original
|
||||
placement/net/UUID preservation against the initial unrouted snapshot. It needs
|
||||
locally installed NumPy and Shapely in addition to pcbnew. It is not a substitute
|
||||
for current-schematic net validation and is specific to this initial layout.
|
||||
|
||||
Results with KiCad **10.0.6**:
|
||||
|
||||
- **0 physical DRC errors/warnings; 0 unconnected items.**
|
||||
- All saved-schematic pad nets match the PCB; **1 expected U2 footprint parity warning**.
|
||||
- Independent minimum different-net/copper-to-NPTH gap **0.275 mm**; minimum
|
||||
copper/NPTH-to-board-edge gap **0.525 mm**.
|
||||
- All 9 electrical footprints, 4 board-only mounts, outline and RF area retained.
|
||||
- Startup `PROPERTY_ENUM` assertions from the local pcbnew binding are still
|
||||
emitted, but the checks complete successfully.
|
||||
|
||||
DRC uses the existing default ignored checks: `missing_courtyard`,
|
||||
`track_not_centered_on_via`, `tuning_profile_track_geometries`,
|
||||
`footprint_filters_mismatch`, `footprint_type_mismatch`. No new exclusions were
|
||||
added to hide problems. **Missing module courtyards therefore remain an explicit
|
||||
manual release gate**. The draft rules do not represent a chosen fabricator's
|
||||
complete capability set. No electrical/hardware/fabrication tests were performed.
|
||||
|
||||
## Regeneration and project settings
|
||||
|
||||
Normal use is to **edit the native PCB in KiCad**, not regenerate it. The optional
|
||||
`generate_pcb_draft.py` recreates the *unrouted* initial placement and discards
|
||||
manual PCB work only with explicit `--overwrite`; it refuses an open-project lock.
|
||||
KiCad's initial SaveBoard populated the previously minimal `.kicad_pro` with PCB
|
||||
settings and draft clearance/width rules. The schematic was not modified. If the
|
||||
project was already open, reopen it to load the saved PCB settings before review.
|
||||
|
||||
`route_pcb_draft.py` requires NumPy and pcbnew. By default it never saves; replacing
|
||||
existing tracks requires explicit `--route --overwrite --replace-routes`. Do not
|
||||
run that over manual routing without a backup. It is a bounded initial-draft
|
||||
router, **not** a general-purpose autorouter. See `validation/pcb-routing-summary.md`
|
||||
for algorithm limits and commands. These scripts do not need downloaded packages
|
||||
or services in the current environment and create no manufacturing outputs.
|
||||
|
||||
Before fabrication: qualify all module pin maps/footprints and connectors,
|
||||
complete assembly courtyards and RF/USB/DE-9 checks, review power/return paths,
|
||||
verify the existing USB power circuit and OLED pull-ups, confirm procurement and
|
||||
case hardware, rerun ERC/DRC, and perform an independent hardware design review.
|
||||
@@ -1,32 +0,0 @@
|
||||
## Adafruit RS-232 Full Pinout Level-Shifter Breakout PCB
|
||||
|
||||
<a href="http://www.adafruit.com/products/5988"><img src="assets/5988.jpg?raw=true" width="500px"><br/>
|
||||
Click here to purchase one from the Adafruit shop</a>
|
||||
|
||||
PCB files for the Adafruit RS-232 Full Pinout Level-Shifter Breakout.
|
||||
|
||||
Format is EagleCAD schematic and board layout
|
||||
* https://www.adafruit.com/product/5988
|
||||
|
||||
### Description
|
||||
|
||||
If you want to interface with telco, retro, or industrial equipment, you'll probably run into RS-232 interfaces. The Adafruit RS232 Full-Pinout Breakout with 8 Channels of UART to RS-232 Level Shifters is your friend in such cases. It gives you 5 input and 3 output channels of level shifting and takes care of the high/negative voltage generation all in a low-cost breakout board. We use the trusty MAX3243 from TI, a classic chip part of the MAX232 lineage, so you know it will work great for all your RS-232 needs, up to 250Kbps.
|
||||
|
||||
RS-232 is what we had before USB: a 9- or 25-pin D-Sub connector that allowed data plus flow control lines. Many folks may remember these interfaces were used for mice, modems, barcode scanners, teletypes, and more. We still find devices sold with RS-232 ports, although many folks use a USB to RS-232 adapter these days.
|
||||
|
||||
If you want to use a microcontroller or microcomputer to chat with an RS-232, then thankfully, all you need is a serial port / UART (something just about any microcontroller has) and a level shifter. The level shifter is required because while most UARTs are 0-3.3V or 0-5V logic level, RS-232 requires +-6 to +-10V, yep the signal voltage goes negative! That means a specialized shifter is required to generate extra high and low voltages and safely convert the logic levels.
|
||||
|
||||
Sure you could buy a raw MAX232 chip and wire up the necessary capacitors, but this board does it all for you and even comes with a DE-9 connector for plugging in directly into your 'client' device. It can run on 3.3V power and logic, which many older chips can't do. It also can do all 8 data pins, so you can use all of the flow control signals like RTS, CTS, DTR, DSR, DCD and RI.
|
||||
|
||||
We also include two separate lines from the MAX3243: 'Valid' and 'Off'. The Valid line output will have logic level high when the chip detects signal voltages from the device its plugged into. So you can use it as a 'connection made' signal. There's also the Off pin, which when set to logic high by the microcontroller will tri-state all the pins for power reduction.
|
||||
|
||||
This breakout comes fully assembled with a UART side for low-voltage power/logic level, and a DE-9F RS-232 side for high-voltage signals. We also include some header so you can solder to a breadboard in a few minutes.
|
||||
|
||||
### License
|
||||
|
||||
Adafruit invests time and resources providing this open source design, please support Adafruit and open-source hardware by purchasing products from [Adafruit](https://www.adafruit.com)!
|
||||
|
||||
Designed by Limor Fried/Ladyada for Adafruit Industries.
|
||||
|
||||
Creative Commons Attribution/Share-Alike, all text above must be included in any redistribution.
|
||||
See license.txt for additional details.
|
||||
|
Before Width: | Height: | Size: 340 KiB |
@@ -1,753 +0,0 @@
|
||||
Tactile Switch B3F
|
||||
Through-hole-mounting Switches
|
||||
in a Wide Range of Models:
|
||||
6 × 6 mm, 12 × 12 mm, Side-operated
|
||||
Models, Gold-plated Contacts, and
|
||||
Radial Tape
|
||||
• Extended mechanical/electrical durability: 10 x 106
|
||||
operations for 12 x 12 mm type and 1 x 106
|
||||
operations for the 6 x 6 mm type
|
||||
• Taped radial type, vertical type and high force
|
||||
types are available.
|
||||
• Gold plated models available for increased
|
||||
contact reliability, resistance to corrosive gas and
|
||||
insulation failure prevention for ion migration in
|
||||
harsh environments
|
||||
• B32-series Key Tops mount to models with
|
||||
projected plungers.
|
||||
RoHS Compliant
|
||||
|
||||
■ List of Models
|
||||
|
||||
6 × 6 mm Models
|
||||
Type Contact Plunger Height Operating Plunger Bags
|
||||
material force (OF) color Without ground Minimum With ground Minimum
|
||||
terminal packing unit terminal packing unit
|
||||
Standard: Silver Flat type 4.3 mm 0.98 N {100 gf} lvory B3F-1000 B3F-1100
|
||||
B3F-1000 plated 1.47 N {150 gf} Yellow B3F-1002 B3F-1102
|
||||
Series
|
||||
2.55 N {260 gf} Orange B3F-1005 B3F-1105
|
||||
4.9 N {500 gf} Red B3F-1006 ---
|
||||
5.0 mm 0.98 N {100 gf} Black B3F-1020 B3F-1120
|
||||
1.47 N {150 gf} Gray B3F-1022 B3F-1122
|
||||
2.55 N {260 gf} Pink B3F-1025 B3F-1125
|
||||
4.9 N {500 gf} Blue B3F-1026 ---
|
||||
5.0 mm 0.98 N {100 gf} Black --- B3F-1110
|
||||
(7.5-mm pitch)
|
||||
7.0 mm 0.98 N {100 gf} Black B3F-1060 100 pcs --- 100 pcs
|
||||
1.47 N {150 gf} Yellow B3F-1062 ---
|
||||
9.5 mm 0.98 N {100 gf} Black B3F-1070 ---
|
||||
1.47 N {150 gf} Yellow B3F-1072 ---
|
||||
2.55 N {260 gf} Orange B3F-1075 ---
|
||||
7.3 mm 0.98 N {100 gf} lvory B3F-1050 B3F-1150
|
||||
Projected type
|
||||
1.47 N {150 gf} Yellow B3F-1052 B3F-1152
|
||||
|
||||
2.55 N {260 gf} Orange B3F-1055 B3F-1155
|
||||
|
||||
4.9 N {500 gf} Red B3F-1056 ---
|
||||
|
||||
|
||||
|
||||
|
||||
1
|
||||
B3F B3F
|
||||
|
||||
Type Contact Plunger Height Operating Plunger Bags
|
||||
material force (OF) color Without ground Minimum With ground Minimum
|
||||
terminal packing unit terminal packing unit
|
||||
Side- Silver 3.15 mm 0.98 N {100 gf} lvory --- B3F-3100
|
||||
Flat type
|
||||
operated: plated 1.47 N {150 gf} Yellow --- B3F-3102
|
||||
B3F-3000
|
||||
Series 2.55 N {260 gf} Orange --- B3F-3105
|
||||
3.85 mm 0.98 N {100 gf} Black --- B3F-3120
|
||||
1.47 N {150 gf} Gray --- B3F-3122
|
||||
2.55 N {260 gf} Pink --- B3F-3125
|
||||
Projected type 6.15 mm 0.98 N {100 gf} lvory --- B3F-3150
|
||||
|
||||
1.47 N {150 gf} Yellow --- B3F-3152
|
||||
|
||||
2.55 N {260 gf} Orange --- B3F-3155
|
||||
|
||||
High- Gold 4.3 mm 1.76 N {180 gf} Yellow B3F-1002-G B3F-1102-G
|
||||
Flat type
|
||||
reliability plated
|
||||
gold-plated: 5.0 mm Gray B3F-1022-G B3F-1122-G
|
||||
B3F-1000-G 7.0 mm Yellow B3F-1062-G 100 pcs --- 100 pcs
|
||||
Series (see note)
|
||||
9.5 mm Yellow B3F-1072-G ---
|
||||
(see note)
|
||||
Side- Projected type 7.3 mm Yellow B3F-1052-G ---
|
||||
operated
|
||||
with highly
|
||||
reliable
|
||||
gold-plated:
|
||||
B3F-3000-G
|
||||
Series
|
||||
3.85 mm Gray --- B3F-3122-G
|
||||
Flat type
|
||||
(see note)
|
||||
|
||||
|
||||
|
||||
|
||||
Note: Bulk Packaged, 100 Switches per bag. Order in multiples of the package quantity.
|
||||
12 × 12 mm Models
|
||||
Type Contact Plunger Height Operating Plunger Bags
|
||||
material (or LED color) force (OF) color Without ground Minimum With ground Minimum
|
||||
terminal packing unit terminal packing unit
|
||||
Standard: Silver Flat type 4.3 mm 1.27 N {130 gf} lvory B3F-4000 B3F-4100
|
||||
B3F-4000 plated 2.55 N {260 gf} Yellow B3F-4005 B3F-4105
|
||||
Series
|
||||
Projected type 7.3 mm 1.27 N {130 gf} lvory B3F-4050 B3F-4150
|
||||
2.55 N {260 gf} Yellow B3F-4055 B3F-4155
|
||||
Long durability: Silver Flat type 4.3 mm 1.27 N {130 gf} Blue B3F-5000 B3F-5100
|
||||
100 pcs 100 pcs
|
||||
B3F-5000 Se- plated
|
||||
ries Projected type 7.3 mm Blue B3F-5050 B3F-5150
|
||||
High reliability Gold Flat type 4.3 mm 1.27 N {130 gf} Blue B3F-5001 B3F-5101
|
||||
gold-plated: plated
|
||||
B3F-5001 Projected type 7.3 mm Blue B3F-5051 B3F-5151
|
||||
Series
|
||||
Note: Bulk Packaged, 100 switches per bag. Order in multiples of the package quantity.
|
||||
6 × 6 mm Radial Models (Taping Specifications)
|
||||
Type Contact Plunger Height Operating Plunger Taped Radial
|
||||
material force (OF) color Without ground Minimum With ground Minimum
|
||||
terminal packing unit terminal packing unit
|
||||
Taped Silver Flat type 4.3 mm 0.98 N {100 gf} lvory B3F-6000 B3F-6100
|
||||
Radial: plated 1.47 N {150 gf} Yellow B3F-6002 B3F-6102
|
||||
B3F-6000
|
||||
Series 5.0 mm 0.98 N {100 gf} Black B3F-6020 B3F-6120
|
||||
1,000 pcs 1,000 pcs
|
||||
1.47 N {150 gf} Gray B3F-6022 B3F-6122
|
||||
Projected type 7.3 mm 0.98 N {100 gf} lvory B3F-6050 B3F-6150
|
||||
1.47 N {150 gf} Yellow B3F-6052 B3F-6152
|
||||
Note: The switches are tape packaged in units of 1,000 per package. Order in multiples of the package size. Switches are not sold individually.
|
||||
|
||||
|
||||
|
||||
|
||||
2
|
||||
B3F B3F
|
||||
|
||||
■ Ratings/Characteristics
|
||||
|
||||
Rating (resistive load) 1 to 50 mA, 3 to 24 VDC (B3F-G: 100 μA to 50 mA, 3 to 24 VDC)
|
||||
Minimum applicable load (reference value) 10 μA at 1 VDC (resistive load)
|
||||
Ambient operating temperature -25°C to +70°C at 60%RH max. (with no icing or condensation)
|
||||
Ambient operating humidity 35% to 85% (at +5 to +35°C)
|
||||
Contact form SPST-NO
|
||||
Contact resistance (initial value) 100 mΩ max.
|
||||
Insulation resistance 100 MΩ min. (at 250 VDC with insulation tester)
|
||||
Dielectric strength 500 VAC, 50/60 Hz for 1 min
|
||||
Bounce time 5 ms max.
|
||||
Vibration resistance Malfunction: 10 to 55 Hz, 1.5 mm double amplitude
|
||||
Shock resistance Destruction: 1,000 m/s2 {approx. 100G} max.
|
||||
Malfunction: 100 m/s2 {approx. 10G} max.
|
||||
Durability B3F-1000, B3F-3000, B3F-6000:
|
||||
1,000,000 operations min (OF: 0.98 N {100 gf}) (B3F-1070: 500,000 operations min)
|
||||
300,000 operations min (OF: 1.47 N {150 gf})
|
||||
100,000 operations min (OF: 2.55 N {260 gf})
|
||||
50,000 operations min (OF: 4.9 N {500 gf})
|
||||
B3F-4000:
|
||||
3,000,000 operations min (OF: 1.27 N {130 gf})
|
||||
1,000,000 operations min (OF: 2.55 N {260 gf})
|
||||
B3F-5000/5001:
|
||||
10,000,000 operations min.
|
||||
B3F-G:
|
||||
300,000 operations min.
|
||||
Weight 6 × 6 mm models: approx. 0.25 g
|
||||
12 × 12 mm models (standard types): approx. 0.85 g
|
||||
Radial models: approx. 0.25 g
|
||||
Degree of protection IEC IP00
|
||||
Washing Not possible
|
||||
|
||||
|
||||
■ Operating Characteristics
|
||||
|
||||
6 × 6 mm Models
|
||||
B3F-1000, B3F-3000, B3F-6000 B3F-G
|
||||
Operating force (OF) 0.98 N 1.47 N 2.55 N 4.9 N 1.76 N
|
||||
B3F-1@@0 B3F-1@@2 B3F-1@@5 B3F-10@6 B3F-1@@2-G
|
||||
Item B3F-3@@0 B3F-3@@2 B3F-3@@5 B3F-3@@2-G
|
||||
B3F-6@@0 B3F-6@@2
|
||||
Operating force (OF) 0.98±0.29 N 1.47±0.49 N 2.55±0.69 N 4.9±1.47 N 1.76±0.49 N
|
||||
{100±30 gf} {150±50 gf} {260±70 gf} {500±150 gf} {180±50 gf}
|
||||
Releasing force (RF) 0.2 N {20 gf} min. 0.49 N {50 gf}min. 0.49 N {50 gf}min. 0.7 N {70 gf} min. 0.49 N {50 gf}min.
|
||||
Pretravel (PT) 0.25+0.2/–0.1 mm 0.25+0.2/–0.1 mm
|
||||
|
||||
|
||||
12 × 12 mm Models
|
||||
B3F-4000, B3F-5000, B3F-5001
|
||||
Operating force (OF) 1.27 N 2.55 N
|
||||
B3F-4@@0 B3F-4@@5
|
||||
Item B3F-5@@0
|
||||
B3F-5@@1
|
||||
Operating force (OF) 1.27±0.49 N 2.55±0.69 N
|
||||
{130±50 gf} {260±70 gf}
|
||||
Releasing force (RF) 0.29 N {30 gf} min. 0.49 N {50 gf} min.
|
||||
Pretravel (PT) 0.3+0.2/–0.1 mm
|
||||
|
||||
|
||||
|
||||
|
||||
3
|
||||
B3F B3F
|
||||
|
||||
■ Dimensions (Unit: mm)
|
||||
Note: The numbers used for terminals in the following graphics are indicated in the “Bottom View” diagram below. In this 2 1
|
||||
diagram, the Switch is rotated so that the terminals are on the right and left-hand sides, and the OMRON logo ap- 4 3
|
||||
pears the right way up. (Except Side-operated and Radial Models) (Bottom View)
|
||||
|
||||
6 × 6 mm Models
|
||||
|
||||
Standard, Flat Plunger Type Standard, Flat Plunger Type
|
||||
(without Ground Terminal) (with Ground Terminal)
|
||||
B3F-1000, B3F-1002, B3F-1005, B3F-1006 B3F-1100, B3F-1102, B3F-1105
|
||||
B3F-1020 (See note.), B3F-1022 (See note.), B3F-1120 (See note.), B3F-1122 (See note.)
|
||||
B3F-1025 (See note.), B3F-1026 (See note.) B3F-1125 (See note.)
|
||||
B3F-1002-G, B3F-1022-G (See note.) B3F-1102-G, B3F-1122-G (See note.)
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
6±0.2 (PCB thickness, t=1.6) 6±0.2 (PCB thickness, t=1.6)
|
||||
|
||||
|
||||
6±0.2 4.5±0.2 6±0.2 4.5±0.2
|
||||
4.5±0.1 4.5±0.1
|
||||
|
||||
4.1±0.1
|
||||
1.5 3.5
|
||||
6.5±0.1 Four, 1±0.1 dia. dia. Five, 1±0.05 dia.
|
||||
3.5
|
||||
dia. (See note.) 6.5±0.1
|
||||
4.3±0.2 3.4
|
||||
(See note.) Terminal Arrangement/ Terminal Arrangement/Internal
|
||||
4.3±0.2 3.4 Internal Connections (Top View) Connections (Top View)
|
||||
3.5
|
||||
4 3 0.3
|
||||
3.5 4 3
|
||||
0.3 0.7
|
||||
0.7 0.7
|
||||
6.5±0.5 6.5±0.5
|
||||
0.3
|
||||
7.7±0.5 0.7 0.7 2 1 7.7±0.5 2 1
|
||||
Note: The height of B3F-1120, B3F-1122, and 5
|
||||
Note: The height of B3F-1020, B3F-1022, B3F-1025,
|
||||
and B3F-1026 is 5±0.2 mm. B3F-1125 is 5±0.2 mm.
|
||||
|
||||
|
||||
|
||||
Standard, Flat Plunger Type Standard, Flat Plunger Type
|
||||
(with Ground Terminal, Pitch: 7.5 mm) (without Ground Terminal)
|
||||
B3F-1110 B3F-1060, B3F-1062, B3F-1062-G
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
6±0.2
|
||||
6±0.2
|
||||
|
||||
6±0.2 4.5±0.1
|
||||
4.5±0.2 4.5±0.1
|
||||
6±0.2 4.5±0.2
|
||||
4.1±0.1
|
||||
|
||||
Four, 3.5
|
||||
1.5 1.2±0.05 dia. dia.
|
||||
1±0.05 dia. 6.5±0.1
|
||||
Four,
|
||||
3.5 7.5±0.1 0.5 max. 1±0.05 dia.
|
||||
dia.
|
||||
7±0.2
|
||||
Terminal Arrangement/Internal 3.4
|
||||
5±0.2
|
||||
3.4
|
||||
Connections (Top View) Terminal Arrangement/
|
||||
(1.8)
|
||||
3.5
|
||||
4 3 0.3
|
||||
Internal Connections
|
||||
3.5 (Top View)
|
||||
0.3 6.5±0.5
|
||||
7.7±0.5 0.7 0.7
|
||||
4 3
|
||||
0.7 0.7 0.7 2 1
|
||||
7.5±0.5
|
||||
0.3 5
|
||||
9±0.5
|
||||
2 1
|
||||
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions. No terminal numbers are indicated on the Switches.
|
||||
|
||||
|
||||
|
||||
|
||||
4
|
||||
B3F B3F
|
||||
|
||||
|
||||
Standard, Flat Plunger Type Standard, Projected Plunger Type
|
||||
(without Ground Terminal) (without Ground Terminal)
|
||||
B3F-1070, B3F-1072, B3F-1075, B3F-1072-G B3F-1050, B3F-1052
|
||||
B3F-1055, B3F-1056
|
||||
PCB Processing Dimensions B3F-1052-G PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
6±0.2 6±0.2
|
||||
|
||||
|
||||
6±0.2 4.5±0.2 4.5±0.1 6±0.2 4.5±0.2 4.5±0.2
|
||||
|
||||
|
||||
3.5
|
||||
dia. 6.5±0.1 Four,
|
||||
3 6.5±0.1 Four, 2.4 × 2.4±0.1 3.5 dia. 1±0.05 dia.
|
||||
dia. 1±0.05 .dia
|
||||
|
||||
0.5 max. Terminal Arrangement/Internal 1.8±0.1 Terminal Arrangement/Internal
|
||||
9.5 Connections (Top View) Connections (Top View)
|
||||
7.3±0.2
|
||||
3.4 4.3±0.2 3.4
|
||||
4 3 4 3
|
||||
(1.8)
|
||||
3.5 3.5
|
||||
0.3 0.3
|
||||
6.5±0.5 2 1 6.5±0.5 2 1
|
||||
7.7±0.5 0.7 0.7 0.7 0.7
|
||||
7.7±0.5
|
||||
|
||||
|
||||
|
||||
Standard, Projected Plunger Type Side-operated, Flat Plunger Type
|
||||
(with Ground Terminal) B3F-3100, B3F-3102, B3F-3105
|
||||
B3F-1150, B3F-1152, B3F-1155
|
||||
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) 7.3 (PCB thickness, t=1.6)
|
||||
6±0.2
|
||||
Two, 1.5 ±0.05 dia.
|
||||
6.25
|
||||
6±0.2 4.5±0.2 4.5±0.1
|
||||
2.5±0.1
|
||||
4.1±0.1
|
||||
3.5 dia.
|
||||
1.5 Five, 1 ±0.05 dia. 4.5±0.1
|
||||
2.4 × 2.4±0.1 3.5 dia. 7±0.1 Two,
|
||||
6.5±0.1 1±0.05 dia.
|
||||
7.4
|
||||
1.8±0.1 Terminal Arrangement/ Terminal Arrangement/
|
||||
4
|
||||
7.3±0.2 Internal Connections Internal Connections
|
||||
4.3±0.2 3.4 (Top View) (Top View)
|
||||
3.5
|
||||
4 3 4
|
||||
3.5 0.3 3
|
||||
0.7 0.3 1
|
||||
0.3 4.5±0.5 1 2
|
||||
2 1 2.25
|
||||
0.7 0.7 0.7 7±0.5 2.5±0.5
|
||||
6.5±0.5 5 3.15±0.2
|
||||
0.3 8.7
|
||||
7.7±0.5
|
||||
|
||||
|
||||
|
||||
Side-operated, Flat Plunger Type (Height: 3.85 mm) Side-operated, Projected Plunger Type
|
||||
B3F-3120, B3F-3122, B3F-3125, B3F-3122-G B3F-3150, B3F-3152, B3F-3155
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
7.3 7.3 (Reference Only) (Top View)
|
||||
(Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
Two, 1.5 ±0.05 dia. 6.25 Two, 1.5 ±0.05 dia.
|
||||
6.25
|
||||
|
||||
|
||||
2.5±0.1 2.5±0.1
|
||||
|
||||
3.5 dia.
|
||||
4.5±0.1 4.5±0.1
|
||||
3.5 dia.
|
||||
7±0.1 Two, 7±0.1 Two,
|
||||
1±0.05 dia. 1±0.05 dia.
|
||||
7.4 Terminal Arrangement/ 7.4 2.4 × 2.4±0.1 Terminal Arrangement/
|
||||
4 Internal Connections Internal Connections
|
||||
4
|
||||
(Top View) (Top View)
|
||||
3.5
|
||||
4 3.5 4
|
||||
3 3
|
||||
0.3
|
||||
0.7 0.3 1 0.3
|
||||
4.5±0.5 1 2 0.7 0.3 1 1 2
|
||||
4.5±0.5 1.8±0.1
|
||||
2.25
|
||||
7±0.5 2.5±0.5 2.25
|
||||
3.85±0.2 7±0.5 2.5±0.5
|
||||
8.7 6.15±0.2
|
||||
8.7
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions. No terminal numbers are indicated on the Switches.
|
||||
|
||||
|
||||
|
||||
|
||||
5
|
||||
B3F B3F
|
||||
|
||||
12 × 12 mm Models
|
||||
Standard, Long-durability, Standard, Long-durability,
|
||||
and High-reliability Models and High-reliability Models
|
||||
Flat Plunger Type Flat Plunger Type
|
||||
(without Ground Terminal) (with Ground Terminal)
|
||||
B3F-4000, B3F-4005, B3F-4100, B3F-4105,
|
||||
B3F-5000, B3F-5001 B3F-5100, B3F-5101
|
||||
PCB Processing Dimensions PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
Two, 1.8±0.05 dia.
|
||||
Two, 1.8±0.05 dia. (for positioning boss)
|
||||
12±0.2 (for positioning boss) 12±0.2
|
||||
|
||||
|
||||
5±0.1 9±0.1
|
||||
5±0.1 9±0.1
|
||||
12±0.2 5±0.2 12±0.2 5±0.2
|
||||
6.9±0.1
|
||||
|
||||
Five,
|
||||
Four, 1.2±0.05 dia.
|
||||
12.5±0.1 1.2±0.05 dia.
|
||||
1.6 12.5±0.1
|
||||
|
||||
|
||||
7.1 dia.
|
||||
Terminal Arrangement/ Terminal Arrangement/
|
||||
7.1 dia.
|
||||
Internal Connections Internal Connections
|
||||
4.3±0.2
|
||||
(Top View) (Top View)
|
||||
3.5 4.3±0.2 3.5
|
||||
3.5 4 3 4 3
|
||||
3.5
|
||||
1.6 dia. 1.6 dia.
|
||||
0.3 1 1 0.9 0.3 1 1
|
||||
12.5±0.5 9±0.1 2 1 12.5±0.5 6.9 2 1
|
||||
13.8±0.5 13.8±0.5 9±0.1 5
|
||||
|
||||
|
||||
|
||||
|
||||
Standard, Long-durability, Standard, Long-durability,
|
||||
and High-reliability Models and High-reliability Models
|
||||
Projected Plunger Type Projected Plunger Type
|
||||
(without Ground Terminal) (with Ground Terminal)
|
||||
B3F-4050, B3F-4055, B3F-4150, B3F-4155,
|
||||
B3F-5050, B3F-5051 PCB Processing Dimensions B3F-5150, B3F-5151 PCB Processing Dimensions
|
||||
(Reference Only) (Top View) (Reference Only) (Top View)
|
||||
(PCB thickness, t=1.6) (PCB thickness, t=1.6)
|
||||
12±0.2 Two, 1.8±0.05 dia.
|
||||
Two, 1.8±0.05 dia.
|
||||
(for positioning boss) (for positioning boss)
|
||||
12±0.2
|
||||
|
||||
12±0.2 5±0.2
|
||||
5±0.1 9±0.1
|
||||
5±0.1 9±0.1
|
||||
12±0.2 5±0.2
|
||||
6.9±0.1
|
||||
|
||||
1.6 Five,
|
||||
Four, 1.2±0.05 dia.
|
||||
12.5±0.1 1.2±0.05 dia.
|
||||
@3.8±0.1 12.5±0.1
|
||||
@3.8±0.1
|
||||
7.1 dia. 7.1 dia.
|
||||
|
||||
Terminal Arrangement/ 1.8±0.2 Terminal Arrangement/
|
||||
1.8±0.2
|
||||
Internal Connections 7.3±0.2 Internal Connections
|
||||
7.3±0.2
|
||||
4.3±0.2 3.5 (Top View) 4.3±0.2 3.5 (Top View)
|
||||
3.5 4 3 3.5 4 3
|
||||
|
||||
1.6 dia. 1.6 dia.
|
||||
0.3 1 1 0.9 0.3 1 1
|
||||
12.5±0.5 9±0.1 2 1 12.5±0.5 6.9 2 1
|
||||
13.8±0.5 13.8±0.5 9±0.1
|
||||
5
|
||||
|
||||
|
||||
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions.
|
||||
|
||||
|
||||
|
||||
|
||||
6
|
||||
B3F B3F
|
||||
|
||||
Note: The numbers used for terminals in the following graphics are indicated in the “Bottom View” diagram below. In this 1
|
||||
diagram, the Switch is rotated so that the terminals are on the right and left-hand sides, and the OMRON logo appears
|
||||
the right way up.
|
||||
2
|
||||
6 mm × 6 mm Radial Types (Taping Specifications): Sold in Units of 1,000 Switches
|
||||
(Bottom View)
|
||||
|
||||
|
||||
Flat Plunger Type 6±0.3 Surface B
|
||||
(without Ground Terminal)
|
||||
B3F-6000, B3F-6002 6±0.3
|
||||
12.7±1
|
||||
6.35±1
|
||||
2 max.
|
||||
3.5 dia. Surface A
|
||||
|
||||
0.5 max.
|
||||
Support
|
||||
0.9 tape 9
|
||||
(1) 4.3 0.5 max. +1
|
||||
3.4 180
|
||||
|
||||
6
|
||||
0.2 max. 9±0.5
|
||||
0.5
|
||||
+1
|
||||
5-0.2
|
||||
+0.8 0.3 18-0.5
|
||||
|
||||
4 dia. ±0.2
|
||||
Terminal Arrangement
|
||||
PCB Processing Dimensions /Internal Connections
|
||||
12.7±0.3 Carrier tape
|
||||
(Reference Only) (Top View) (Top View)
|
||||
Note: The tape is random
|
||||
(PCB thickness, t=1.6)
|
||||
between surface A
|
||||
and surface B.
|
||||
+0.1 2 1
|
||||
Two, 1 0 dia. 5±0.1
|
||||
|
||||
|
||||
|
||||
|
||||
Flat Plunger Type 6±0.3 12.7±1
|
||||
6.35±1
|
||||
(with Ground Terminal) 2 max.
|
||||
B3F-6100, B3F-6102 6±0.3 2
|
||||
|
||||
|
||||
3.5 dia.
|
||||
Support 11
|
||||
0.5 max. tape 0.5 max.
|
||||
20±0.5
|
||||
0.9
|
||||
(1) 4.3 3.4
|
||||
6
|
||||
9±0.5
|
||||
3.1
|
||||
+1
|
||||
0.2 dia. 18-0.5
|
||||
0.5 5±0.2 0.3
|
||||
+0.8 4±0.2 dia.
|
||||
5-0.2 0.6
|
||||
|
||||
|
||||
12.7±0.3 Carrier tape
|
||||
PCB Processing Dimensions Terminal Arrangement
|
||||
(Reference Only) (Top View) /Internal Connections
|
||||
(PCB thickness, t=1.6) (Top View)
|
||||
|
||||
|
||||
|
||||
+0.1 2 1
|
||||
Three, 1 0 dia. 5±0.1 3
|
||||
5±0.1
|
||||
|
||||
|
||||
|
||||
Flat Plunger Type 6±0.3 Surface B
|
||||
(without Ground Terminal) 12.7±1
|
||||
B3F-6020, B3F-6022 6.35±1
|
||||
2 max.
|
||||
6±0.3
|
||||
|
||||
|
||||
3.5 dia. Surface A
|
||||
Support 9
|
||||
0.5 max. tape 0.5 max. +1
|
||||
180
|
||||
0.9
|
||||
(1) 5
|
||||
3.4 6
|
||||
9±0.5
|
||||
+1
|
||||
0.2 max. 18-0.5
|
||||
0.5
|
||||
+0.8
|
||||
5-0.2 0.3 4±0.2 dia.
|
||||
|
||||
Terminal Arrangement Carrier tape
|
||||
Note: The tape is random PCB Processing Dimensions /Internal Connections
|
||||
12.7±0.3
|
||||
|
||||
between surface A (Reference Only) (Top View) (Top View)
|
||||
and surface B. (PCB thickness, t=1.6)
|
||||
|
||||
|
||||
+0.1 2 1
|
||||
Two, 1 0 dia. 5±0.1
|
||||
|
||||
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions. No terminal numbers are indicated on the Switches.
|
||||
|
||||
|
||||
|
||||
7
|
||||
B3F B3F
|
||||
|
||||
|
||||
Flat Plunger Type 6±0.3
|
||||
(with Ground Terminal)
|
||||
B3F-6120, B3F-6122 6±0.3 2
|
||||
12.7±1
|
||||
6.35±1
|
||||
2 max.
|
||||
|
||||
3.5 dia.
|
||||
0.5 max.
|
||||
|
||||
0.9 Support 11
|
||||
(1) 5
|
||||
3.4 tape 0.5 max.
|
||||
20±0.5
|
||||
3.1
|
||||
0.2 max. 6
|
||||
9±0.5
|
||||
0.5 5±0.2 0.3
|
||||
+1
|
||||
+0.8
|
||||
5-0.2 0.6 18-0.5
|
||||
|
||||
4±0.2 dia.
|
||||
PCB Processing Dimensions Terminal Arrangement
|
||||
(Reference Only) (Top View) /Internal Connections Carrier tape
|
||||
12.7±0.3
|
||||
(PCB thickness, t=1.6) (Top View)
|
||||
|
||||
|
||||
+0.1 2 1
|
||||
Three, 1 0 dia. 5±0.1 3
|
||||
5±0.1
|
||||
|
||||
|
||||
|
||||
|
||||
Projected Plunger Type 6±0.3 Surface B
|
||||
(without Ground Terminal)
|
||||
B3F-6050, B3F-6052
|
||||
6±0.3
|
||||
|
||||
12.7±1
|
||||
3.5 dia. Surface A 6.35±1
|
||||
2.4 2 max.
|
||||
|
||||
|
||||
0.5 max.
|
||||
7.3 0.9
|
||||
(1) 3.4
|
||||
Support 9
|
||||
tape 0.5 dia. +1
|
||||
18 0
|
||||
0.2 max.
|
||||
0.5
|
||||
+0.8 0.3 6
|
||||
5-0.2 9±0.5
|
||||
+1
|
||||
18-0.5
|
||||
PCB Processing Dimensions Terminal Arrang ement
|
||||
(Reference Only) (Top View) /Internal Connections 4±0.2 dia.
|
||||
(PCB thickness, t=1.6) (Top View)
|
||||
12.7±0.3 Carrier tape
|
||||
+0.1
|
||||
Two, 1 0 dia. 5±0.1 2 1
|
||||
|
||||
|
||||
Note: The tape is random between surface A
|
||||
and surface B.
|
||||
|
||||
|
||||
Projected Plunger Type 6±0.3
|
||||
(with Ground Terminal)
|
||||
B3F-6150, B3F-6152
|
||||
6±0.3 2 12.7±1
|
||||
6.35±1
|
||||
2 max.
|
||||
|
||||
3.5 dia. 2.4
|
||||
|
||||
|
||||
0.5 max.
|
||||
7.3 0.9
|
||||
(1) 3.4 Support 11
|
||||
tape 0.5 max.
|
||||
20±0.5
|
||||
3.1
|
||||
0.2 max.
|
||||
6
|
||||
9±0.5
|
||||
0.5 5±0.2 0.3
|
||||
+0.8 +1
|
||||
5 -0.2 0.6 18-0.5
|
||||
|
||||
|
||||
Terminal Arrangement 4±0.2 dia.
|
||||
PCB Processing Dimensions
|
||||
(Reference Only) (Top View) /Internal Connections
|
||||
(PCB thickness, t=1.6) (Top View) 12.7±0.3 Carrier tape
|
||||
|
||||
|
||||
|
||||
+0.1
|
||||
Three, 1 0 dia. 5±0.1 2 1
|
||||
3
|
||||
5±0.1
|
||||
|
||||
|
||||
|
||||
Note: Unless otherwise specified, a tolerance of ±0.4 mm applies to all dimensions. No terminal numbers are indicated on the Switches.
|
||||
|
||||
|
||||
|
||||
8
|
||||
B3F B3F
|
||||
|
||||
■ Key Tops
|
||||
B32-series Key Tops are available for projected plungers. Refer to the Datasheet of B32 for details.
|
||||
|
||||
■ Precautions
|
||||
Be sure to read the safety precautions common to all Tactile Switches for correct use.
|
||||
|
||||
|
||||
|
||||
|
||||
• Application examples provided in this document are for reference only. In actual applications, confirm equipment functions and safety before using the product.
|
||||
• Consult your OMRON representative before using the product under conditions which are not described in the manual or applying the product to nuclear control systems, railroad
|
||||
systems, aviation systems, vehicles, combustion systems, medical equipment, amusement machines, safety equipment, and other systems or equipment that may have a serious
|
||||
influence on lives and property if used improperly. Make sure that the ratings and performance characteristics of the product provide a margin of safety for the system or
|
||||
equipment, and be sure to provide the system or equipment with double safety mechanisms.
|
||||
|
||||
|
||||
Note: Do not use this document to operate the Unit.
|
||||
|
||||
OMRON Corporation
|
||||
Electronic and Mechanical Components Company Contact: www.omron.com/ecb Cat. No. A070-E1-08
|
||||
1014(0207)(O)
|
||||
|
||||
|
||||
|
||||
9
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
Attribution-ShareAlike 3.0 Unported
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
|
||||
DAMAGES RESULTING FROM ITS USE.
|
||||
|
||||
License
|
||||
|
||||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
|
||||
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
|
||||
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
|
||||
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||
|
||||
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
|
||||
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
|
||||
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
|
||||
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
|
||||
CONDITIONS.
|
||||
|
||||
1. Definitions
|
||||
|
||||
a. "Adaptation" means a work based upon the Work, or upon the Work and
|
||||
other pre-existing works, such as a translation, adaptation,
|
||||
derivative work, arrangement of music or other alterations of a
|
||||
literary or artistic work, or phonogram or performance and includes
|
||||
cinematographic adaptations or any other form in which the Work may be
|
||||
recast, transformed, or adapted including in any form recognizably
|
||||
derived from the original, except that a work that constitutes a
|
||||
Collection will not be considered an Adaptation for the purpose of
|
||||
this License. For the avoidance of doubt, where the Work is a musical
|
||||
work, performance or phonogram, the synchronization of the Work in
|
||||
timed-relation with a moving image ("synching") will be considered an
|
||||
Adaptation for the purpose of this License.
|
||||
b. "Collection" means a collection of literary or artistic works, such as
|
||||
encyclopedias and anthologies, or performances, phonograms or
|
||||
broadcasts, or other works or subject matter other than works listed
|
||||
in Section 1(f) below, which, by reason of the selection and
|
||||
arrangement of their contents, constitute intellectual creations, in
|
||||
which the Work is included in its entirety in unmodified form along
|
||||
with one or more other contributions, each constituting separate and
|
||||
independent works in themselves, which together are assembled into a
|
||||
collective whole. A work that constitutes a Collection will not be
|
||||
considered an Adaptation (as defined below) for the purposes of this
|
||||
License.
|
||||
c. "Creative Commons Compatible License" means a license that is listed
|
||||
at https://creativecommons.org/compatiblelicenses that has been
|
||||
approved by Creative Commons as being essentially equivalent to this
|
||||
License, including, at a minimum, because that license: (i) contains
|
||||
terms that have the same purpose, meaning and effect as the License
|
||||
Elements of this License; and, (ii) explicitly permits the relicensing
|
||||
of adaptations of works made available under that license under this
|
||||
License or a Creative Commons jurisdiction license with the same
|
||||
License Elements as this License.
|
||||
d. "Distribute" means to make available to the public the original and
|
||||
copies of the Work or Adaptation, as appropriate, through sale or
|
||||
other transfer of ownership.
|
||||
e. "License Elements" means the following high-level license attributes
|
||||
as selected by Licensor and indicated in the title of this License:
|
||||
Attribution, ShareAlike.
|
||||
f. "Licensor" means the individual, individuals, entity or entities that
|
||||
offer(s) the Work under the terms of this License.
|
||||
g. "Original Author" means, in the case of a literary or artistic work,
|
||||
the individual, individuals, entity or entities who created the Work
|
||||
or if no individual or entity can be identified, the publisher; and in
|
||||
addition (i) in the case of a performance the actors, singers,
|
||||
musicians, dancers, and other persons who act, sing, deliver, declaim,
|
||||
play in, interpret or otherwise perform literary or artistic works or
|
||||
expressions of folklore; (ii) in the case of a phonogram the producer
|
||||
being the person or legal entity who first fixes the sounds of a
|
||||
performance or other sounds; and, (iii) in the case of broadcasts, the
|
||||
organization that transmits the broadcast.
|
||||
h. "Work" means the literary and/or artistic work offered under the terms
|
||||
of this License including without limitation any production in the
|
||||
literary, scientific and artistic domain, whatever may be the mode or
|
||||
form of its expression including digital form, such as a book,
|
||||
pamphlet and other writing; a lecture, address, sermon or other work
|
||||
of the same nature; a dramatic or dramatico-musical work; a
|
||||
choreographic work or entertainment in dumb show; a musical
|
||||
composition with or without words; a cinematographic work to which are
|
||||
assimilated works expressed by a process analogous to cinematography;
|
||||
a work of drawing, painting, architecture, sculpture, engraving or
|
||||
lithography; a photographic work to which are assimilated works
|
||||
expressed by a process analogous to photography; a work of applied
|
||||
art; an illustration, map, plan, sketch or three-dimensional work
|
||||
relative to geography, topography, architecture or science; a
|
||||
performance; a broadcast; a phonogram; a compilation of data to the
|
||||
extent it is protected as a copyrightable work; or a work performed by
|
||||
a variety or circus performer to the extent it is not otherwise
|
||||
considered a literary or artistic work.
|
||||
i. "You" means an individual or entity exercising rights under this
|
||||
License who has not previously violated the terms of this License with
|
||||
respect to the Work, or who has received express permission from the
|
||||
Licensor to exercise rights under this License despite a previous
|
||||
violation.
|
||||
j. "Publicly Perform" means to perform public recitations of the Work and
|
||||
to communicate to the public those public recitations, by any means or
|
||||
process, including by wire or wireless means or public digital
|
||||
performances; to make available to the public Works in such a way that
|
||||
members of the public may access these Works from a place and at a
|
||||
place individually chosen by them; to perform the Work to the public
|
||||
by any means or process and the communication to the public of the
|
||||
performances of the Work, including by public digital performance; to
|
||||
broadcast and rebroadcast the Work by any means including signs,
|
||||
sounds or images.
|
||||
k. "Reproduce" means to make copies of the Work by any means including
|
||||
without limitation by sound or visual recordings and the right of
|
||||
fixation and reproducing fixations of the Work, including storage of a
|
||||
protected performance or phonogram in digital form or other electronic
|
||||
medium.
|
||||
|
||||
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
|
||||
limit, or restrict any uses free from copyright or rights arising from
|
||||
limitations or exceptions that are provided for in connection with the
|
||||
copyright protection under copyright law or other applicable laws.
|
||||
|
||||
3. License Grant. Subject to the terms and conditions of this License,
|
||||
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||
perpetual (for the duration of the applicable copyright) license to
|
||||
exercise the rights in the Work as stated below:
|
||||
|
||||
a. to Reproduce the Work, to incorporate the Work into one or more
|
||||
Collections, and to Reproduce the Work as incorporated in the
|
||||
Collections;
|
||||
b. to create and Reproduce Adaptations provided that any such Adaptation,
|
||||
including any translation in any medium, takes reasonable steps to
|
||||
clearly label, demarcate or otherwise identify that changes were made
|
||||
to the original Work. For example, a translation could be marked "The
|
||||
original work was translated from English to Spanish," or a
|
||||
modification could indicate "The original work has been modified.";
|
||||
c. to Distribute and Publicly Perform the Work including as incorporated
|
||||
in Collections; and,
|
||||
d. to Distribute and Publicly Perform Adaptations.
|
||||
e. For the avoidance of doubt:
|
||||
|
||||
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
|
||||
which the right to collect royalties through any statutory or
|
||||
compulsory licensing scheme cannot be waived, the Licensor
|
||||
reserves the exclusive right to collect such royalties for any
|
||||
exercise by You of the rights granted under this License;
|
||||
ii. Waivable Compulsory License Schemes. In those jurisdictions in
|
||||
which the right to collect royalties through any statutory or
|
||||
compulsory licensing scheme can be waived, the Licensor waives the
|
||||
exclusive right to collect such royalties for any exercise by You
|
||||
of the rights granted under this License; and,
|
||||
iii. Voluntary License Schemes. The Licensor waives the right to
|
||||
collect royalties, whether individually or, in the event that the
|
||||
Licensor is a member of a collecting society that administers
|
||||
voluntary licensing schemes, via that society, from any exercise
|
||||
by You of the rights granted under this License.
|
||||
|
||||
The above rights may be exercised in all media and formats whether now
|
||||
known or hereafter devised. The above rights include the right to make
|
||||
such modifications as are technically necessary to exercise the rights in
|
||||
other media and formats. Subject to Section 8(f), all rights not expressly
|
||||
granted by Licensor are hereby reserved.
|
||||
|
||||
4. Restrictions. The license granted in Section 3 above is expressly made
|
||||
subject to and limited by the following restrictions:
|
||||
|
||||
a. You may Distribute or Publicly Perform the Work only under the terms
|
||||
of this License. You must include a copy of, or the Uniform Resource
|
||||
Identifier (URI) for, this License with every copy of the Work You
|
||||
Distribute or Publicly Perform. You may not offer or impose any terms
|
||||
on the Work that restrict the terms of this License or the ability of
|
||||
the recipient of the Work to exercise the rights granted to that
|
||||
recipient under the terms of the License. You may not sublicense the
|
||||
Work. You must keep intact all notices that refer to this License and
|
||||
to the disclaimer of warranties with every copy of the Work You
|
||||
Distribute or Publicly Perform. When You Distribute or Publicly
|
||||
Perform the Work, You may not impose any effective technological
|
||||
measures on the Work that restrict the ability of a recipient of the
|
||||
Work from You to exercise the rights granted to that recipient under
|
||||
the terms of the License. This Section 4(a) applies to the Work as
|
||||
incorporated in a Collection, but this does not require the Collection
|
||||
apart from the Work itself to be made subject to the terms of this
|
||||
License. If You create a Collection, upon notice from any Licensor You
|
||||
must, to the extent practicable, remove from the Collection any credit
|
||||
as required by Section 4(c), as requested. If You create an
|
||||
Adaptation, upon notice from any Licensor You must, to the extent
|
||||
practicable, remove from the Adaptation any credit as required by
|
||||
Section 4(c), as requested.
|
||||
b. You may Distribute or Publicly Perform an Adaptation only under the
|
||||
terms of: (i) this License; (ii) a later version of this License with
|
||||
the same License Elements as this License; (iii) a Creative Commons
|
||||
jurisdiction license (either this or a later license version) that
|
||||
contains the same License Elements as this License (e.g.,
|
||||
Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
|
||||
License. If you license the Adaptation under one of the licenses
|
||||
mentioned in (iv), you must comply with the terms of that license. If
|
||||
you license the Adaptation under the terms of any of the licenses
|
||||
mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
|
||||
comply with the terms of the Applicable License generally and the
|
||||
following provisions: (I) You must include a copy of, or the URI for,
|
||||
the Applicable License with every copy of each Adaptation You
|
||||
Distribute or Publicly Perform; (II) You may not offer or impose any
|
||||
terms on the Adaptation that restrict the terms of the Applicable
|
||||
License or the ability of the recipient of the Adaptation to exercise
|
||||
the rights granted to that recipient under the terms of the Applicable
|
||||
License; (III) You must keep intact all notices that refer to the
|
||||
Applicable License and to the disclaimer of warranties with every copy
|
||||
of the Work as included in the Adaptation You Distribute or Publicly
|
||||
Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
|
||||
You may not impose any effective technological measures on the
|
||||
Adaptation that restrict the ability of a recipient of the Adaptation
|
||||
from You to exercise the rights granted to that recipient under the
|
||||
terms of the Applicable License. This Section 4(b) applies to the
|
||||
Adaptation as incorporated in a Collection, but this does not require
|
||||
the Collection apart from the Adaptation itself to be made subject to
|
||||
the terms of the Applicable License.
|
||||
c. If You Distribute, or Publicly Perform the Work or any Adaptations or
|
||||
Collections, You must, unless a request has been made pursuant to
|
||||
Section 4(a), keep intact all copyright notices for the Work and
|
||||
provide, reasonable to the medium or means You are utilizing: (i) the
|
||||
name of the Original Author (or pseudonym, if applicable) if supplied,
|
||||
and/or if the Original Author and/or Licensor designate another party
|
||||
or parties (e.g., a sponsor institute, publishing entity, journal) for
|
||||
attribution ("Attribution Parties") in Licensor's copyright notice,
|
||||
terms of service or by other reasonable means, the name of such party
|
||||
or parties; (ii) the title of the Work if supplied; (iii) to the
|
||||
extent reasonably practicable, the URI, if any, that Licensor
|
||||
specifies to be associated with the Work, unless such URI does not
|
||||
refer to the copyright notice or licensing information for the Work;
|
||||
and (iv) , consistent with Ssection 3(b), in the case of an
|
||||
Adaptation, a credit identifying the use of the Work in the Adaptation
|
||||
(e.g., "French translation of the Work by Original Author," or
|
||||
"Screenplay based on original Work by Original Author"). The credit
|
||||
required by this Section 4(c) may be implemented in any reasonable
|
||||
manner; provided, however, that in the case of a Adaptation or
|
||||
Collection, at a minimum such credit will appear, if a credit for all
|
||||
contributing authors of the Adaptation or Collection appears, then as
|
||||
part of these credits and in a manner at least as prominent as the
|
||||
credits for the other contributing authors. For the avoidance of
|
||||
doubt, You may only use the credit required by this Section for the
|
||||
purpose of attribution in the manner set out above and, by exercising
|
||||
Your rights under this License, You may not implicitly or explicitly
|
||||
assert or imply any connection with, sponsorship or endorsement by the
|
||||
Original Author, Licensor and/or Attribution Parties, as appropriate,
|
||||
of You or Your use of the Work, without the separate, express prior
|
||||
written permission of the Original Author, Licensor and/or Attribution
|
||||
Parties.
|
||||
d. Except as otherwise agreed in writing by the Licensor or as may be
|
||||
otherwise permitted by applicable law, if You Reproduce, Distribute or
|
||||
Publicly Perform the Work either by itself or as part of any
|
||||
Adaptations or Collections, You must not distort, mutilate, modify or
|
||||
take other derogatory action in relation to the Work which would be
|
||||
prejudicial to the Original Author's honor or reputation. Licensor
|
||||
agrees that in those jurisdictions (e.g. Japan), in which any exercise
|
||||
of the right granted in Section 3(b) of this License (the right to
|
||||
make Adaptations) would be deemed to be a distortion, mutilation,
|
||||
modification or other derogatory action prejudicial to the Original
|
||||
Author's honor and reputation, the Licensor will waive or not assert,
|
||||
as appropriate, this Section, to the fullest extent permitted by the
|
||||
applicable national law, to enable You to reasonably exercise Your
|
||||
right under Section 3(b) of this License (right to make Adaptations)
|
||||
but not otherwise.
|
||||
|
||||
5. Representations, Warranties and Disclaimer
|
||||
|
||||
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
|
||||
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
|
||||
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
|
||||
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
|
||||
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
|
||||
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
|
||||
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||
|
||||
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
|
||||
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
|
||||
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
|
||||
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
|
||||
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
7. Termination
|
||||
|
||||
a. This License and the rights granted hereunder will terminate
|
||||
automatically upon any breach by You of the terms of this License.
|
||||
Individuals or entities who have received Adaptations or Collections
|
||||
from You under this License, however, will not have their licenses
|
||||
terminated provided such individuals or entities remain in full
|
||||
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
|
||||
survive any termination of this License.
|
||||
b. Subject to the above terms and conditions, the license granted here is
|
||||
perpetual (for the duration of the applicable copyright in the Work).
|
||||
Notwithstanding the above, Licensor reserves the right to release the
|
||||
Work under different license terms or to stop distributing the Work at
|
||||
any time; provided, however that any such election will not serve to
|
||||
withdraw this License (or any other license that has been, or is
|
||||
required to be, granted under the terms of this License), and this
|
||||
License will continue in full force and effect unless terminated as
|
||||
stated above.
|
||||
|
||||
8. Miscellaneous
|
||||
|
||||
a. Each time You Distribute or Publicly Perform the Work or a Collection,
|
||||
the Licensor offers to the recipient a license to the Work on the same
|
||||
terms and conditions as the license granted to You under this License.
|
||||
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
|
||||
offers to the recipient a license to the original Work on the same
|
||||
terms and conditions as the license granted to You under this License.
|
||||
c. If any provision of this License is invalid or unenforceable under
|
||||
applicable law, it shall not affect the validity or enforceability of
|
||||
the remainder of the terms of this License, and without further action
|
||||
by the parties to this agreement, such provision shall be reformed to
|
||||
the minimum extent necessary to make such provision valid and
|
||||
enforceable.
|
||||
d. No term or provision of this License shall be deemed waived and no
|
||||
breach consented to unless such waiver or consent shall be in writing
|
||||
and signed by the party to be charged with such waiver or consent.
|
||||
e. This License constitutes the entire agreement between the parties with
|
||||
respect to the Work licensed here. There are no understandings,
|
||||
agreements or representations with respect to the Work not specified
|
||||
here. Licensor shall not be bound by any additional provisions that
|
||||
may appear in any communication from You. This License may not be
|
||||
modified without the mutual written agreement of the Licensor and You.
|
||||
f. The rights granted under, and the subject matter referenced, in this
|
||||
License were drafted utilizing the terminology of the Berne Convention
|
||||
for the Protection of Literary and Artistic Works (as amended on
|
||||
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
|
||||
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
|
||||
and the Universal Copyright Convention (as revised on July 24, 1971).
|
||||
These rights and subject matter take effect in the relevant
|
||||
jurisdiction in which the License terms are sought to be enforced
|
||||
according to the corresponding provisions of the implementation of
|
||||
those treaty provisions in the applicable national law. If the
|
||||
standard suite of rights granted under applicable copyright law
|
||||
includes additional rights not granted under this License, such
|
||||
additional rights are deemed to be included in the License; this
|
||||
License is not intended to restrict the license of any rights under
|
||||
applicable law.
|
||||
|
||||
|
||||
Creative Commons Notice
|
||||
|
||||
Creative Commons is not a party to this License, and makes no warranty
|
||||
whatsoever in connection with the Work. Creative Commons will not be
|
||||
liable to You or any party on any legal theory for any damages
|
||||
whatsoever, including without limitation any general, special,
|
||||
incidental or consequential damages arising in connection to this
|
||||
license. Notwithstanding the foregoing two (2) sentences, if Creative
|
||||
Commons has expressly identified itself as the Licensor hereunder, it
|
||||
shall have all rights and obligations of Licensor.
|
||||
|
||||
Except for the limited purpose of indicating to the public that the
|
||||
Work is licensed under the CCPL, Creative Commons does not authorize
|
||||
the use by either party of the trademark "Creative Commons" or any
|
||||
related trademark or logo of Creative Commons without the prior
|
||||
written consent of Creative Commons. Any permitted use will be in
|
||||
compliance with Creative Commons' then-current trademark usage
|
||||
guidelines, as may be published on its website or otherwise made
|
||||
available upon request from time to time. For the avoidance of doubt,
|
||||
this trademark restriction does not form part of the License.
|
||||
|
||||
Creative Commons may be contacted at https://creativecommons.org/.
|
||||
@@ -1,365 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
"""Bounded, board-specific two-layer draft router. Requires pcbnew and numpy.
|
||||
|
||||
Dry-run by default. --route --overwrite explicitly permits saving routing;
|
||||
existing copper additionally requires --replace-routes. Never edits the project,
|
||||
schematic, footprint libraries or generator. Review actual KiCad DRC afterwards.
|
||||
This is not a production autorouter or a fabrication qualification.
|
||||
"""
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
import hashlib
|
||||
import heapq
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
import pcbnew as p
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / 'serial-carrier.kicad_pcb'
|
||||
STEP = 0.05
|
||||
NX, NY = int(82 / STEP) + 1, int(80 / STEP) + 1
|
||||
N = NX * NY
|
||||
CLEARANCE = 0.25
|
||||
MARGIN = 0.02
|
||||
LAYERS = (p.F_Cu, p.B_Cu)
|
||||
X = 100 + np.arange(NX)[None, :] * STEP
|
||||
Y = 100 + np.arange(NY)[:, None] * STEP
|
||||
|
||||
|
||||
def mm(v):
|
||||
return p.FromMM(v)
|
||||
|
||||
|
||||
def point(xy):
|
||||
return p.VECTOR2I(mm(xy[0]), mm(xy[1]))
|
||||
|
||||
|
||||
def pos(item):
|
||||
v = item.GetPosition()
|
||||
return (p.ToMM(v.x), p.ToMM(v.y))
|
||||
|
||||
|
||||
def identity(board):
|
||||
"""Immutable geometry/linkage snapshot, independent of serialization order."""
|
||||
footprints = []
|
||||
for f in board.GetFootprints():
|
||||
pads = sorted((a.m_Uuid.AsString(), a.GetNumber(), a.GetNetname(),
|
||||
pos(a), (a.GetSize().x, a.GetSize().y),
|
||||
(a.GetDrillSize().x, a.GetDrillSize().y),
|
||||
a.GetShape(), a.GetAttribute(), a.GetLayerSet().FmtHex(),
|
||||
a.GetOrientationDegrees(), a.GetPinFunction(), a.GetPinType())
|
||||
for a in f.Pads())
|
||||
footprints.append((f.m_Uuid.AsString(), f.GetReference(), f.GetValue(),
|
||||
f.GetFPIDAsString(), f.GetPath().AsString(), pos(f),
|
||||
f.GetOrientationDegrees(), f.GetLayer(), pads))
|
||||
edges = sorted((a.m_Uuid.AsString(), a.GetShape(), a.GetStart().x,
|
||||
a.GetStart().y, a.GetEnd().x, a.GetEnd().y, a.GetWidth())
|
||||
for a in board.GetDrawings() if a.GetLayer() == p.Edge_Cuts)
|
||||
zones = sorted((z.m_Uuid.AsString(), z.GetLayerSet().FmtHex(),
|
||||
z.GetIsRuleArea(), z.GetDoNotAllowTracks(),
|
||||
z.GetDoNotAllowVias(), z.GetDoNotAllowZoneFills(),
|
||||
tuple((z.Outline().COutline(0).CPoint(i).x,
|
||||
z.Outline().COutline(0).CPoint(i).y)
|
||||
for i in range(z.Outline().COutline(0).PointCount())))
|
||||
for z in board.Zones())
|
||||
nets = sorted((n.GetNetCode(), n.GetNetname()) for n in board.GetNetsByNetcode().values())
|
||||
return (board.GetCopperLayerCount(), sorted(footprints), edges, zones, nets)
|
||||
|
||||
|
||||
def window(x0, y0, x1, y1):
|
||||
ix0 = max(0, int(math.floor((x0 - 100) / STEP)))
|
||||
iy0 = max(0, int(math.floor((y0 - 100) / STEP)))
|
||||
ix1 = min(NX, int(math.ceil((x1 - 100) / STEP)) + 1)
|
||||
iy1 = min(NY, int(math.ceil((y1 - 100) / STEP)) + 1)
|
||||
return slice(iy0, iy1), slice(ix0, ix1)
|
||||
|
||||
|
||||
def capsule(mask, a, b, radius):
|
||||
sy, sx = window(min(a[0], b[0]) - radius, min(a[1], b[1]) - radius,
|
||||
max(a[0], b[0]) + radius, max(a[1], b[1]) + radius)
|
||||
xx, yy = X[:, sx], Y[sy, :]
|
||||
dx, dy = b[0] - a[0], b[1] - a[1]
|
||||
length2 = dx * dx + dy * dy
|
||||
t = np.clip(((xx - a[0]) * dx + (yy - a[1]) * dy) / length2, 0, 1) if length2 else 0
|
||||
mask[sy, sx] |= (xx - a[0] - t * dx)**2 + (yy - a[1] - t * dy)**2 <= radius**2
|
||||
|
||||
|
||||
def pad_obstacle(mask, pad, extra, hole_only=False):
|
||||
xy = pos(pad)
|
||||
size = pad.GetDrillSize() if hole_only else pad.GetSize()
|
||||
rx, ry = p.ToMM(size.x) / 2, p.ToMM(size.y) / 2
|
||||
if not rx or not ry:
|
||||
return
|
||||
if abs(rx - ry) < 1e-6 and (hole_only or pad.GetShape() == p.PAD_SHAPE_CIRCLE):
|
||||
capsule(mask, xy, xy, rx + extra)
|
||||
else:
|
||||
# Bounding rectangle deliberately overestimates non-circular pad shapes.
|
||||
box = pad.GetBoundingBox()
|
||||
x0, y0 = p.ToMM(box.GetX()) - extra, p.ToMM(box.GetY()) - extra
|
||||
x1, y1 = p.ToMM(box.GetRight()) + extra, p.ToMM(box.GetBottom()) + extra
|
||||
sy, sx = window(x0, y0, x1, y1)
|
||||
mask[sy, sx] |= (X[:, sx] >= x0) & (X[:, sx] <= x1) & (Y[sy, :] >= y0) & (Y[sy, :] <= y1)
|
||||
|
||||
|
||||
def boundary(radius):
|
||||
edge = 0.5 + radius + MARGIN
|
||||
mask = np.broadcast_to((X < 100 + edge) | (X > 182 - edge) |
|
||||
(Y < 100 + edge) | (Y > 180 - edge), (NY, NX)).copy()
|
||||
mask |= (X < 136 + edge) & (Y > 161 - edge)
|
||||
# Expand the RF reservation by copper radius plus numerical safety margin.
|
||||
r = radius + MARGIN
|
||||
mask |= (X >= 110 - r) & (X <= 131 + r) & (Y <= 113 + r)
|
||||
return mask
|
||||
|
||||
|
||||
def masks(board, pads, net, width):
|
||||
blocked = np.stack([boundary(width / 2)] * 2)
|
||||
via = boundary(0.35)
|
||||
for pad in pads:
|
||||
if pad.GetNetCode() != net or not pad.GetNumber():
|
||||
for z, layer in enumerate(LAYERS):
|
||||
if pad.IsOnLayer(layer):
|
||||
pad_obstacle(blocked[z], pad, CLEARANCE + width / 2 + MARGIN)
|
||||
pad_obstacle(via, pad, CLEARANCE + 0.35 + MARGIN)
|
||||
# Even same-net holes must not receive a drilled via.
|
||||
pad_obstacle(via, pad, CLEARANCE + 0.35 + MARGIN, hole_only=True)
|
||||
for track in board.GetTracks():
|
||||
if track.GetNetCode() == net:
|
||||
if isinstance(track, p.PCB_VIA):
|
||||
capsule(via, pos(track), pos(track), 0.7 + CLEARANCE + MARGIN)
|
||||
continue
|
||||
a = (p.ToMM(track.GetStart().x), p.ToMM(track.GetStart().y))
|
||||
b = (p.ToMM(track.GetEnd().x), p.ToMM(track.GetEnd().y))
|
||||
radius = p.ToMM(track.GetWidth(p.F_Cu) if isinstance(track, p.PCB_VIA) else track.GetWidth()) / 2
|
||||
for z, layer in enumerate(LAYERS):
|
||||
if isinstance(track, p.PCB_VIA) or track.GetLayer() == layer:
|
||||
capsule(blocked[z], a, b, radius + CLEARANCE + width / 2 + MARGIN)
|
||||
capsule(via, a, b, radius + CLEARANCE + 0.35 + MARGIN)
|
||||
return blocked.reshape(-1), via.reshape(-1)
|
||||
|
||||
|
||||
def node(xy, layer=0):
|
||||
x, y = (int(round((v - 100) / STEP)) for v in xy)
|
||||
return layer * N + y * NX + x
|
||||
|
||||
|
||||
def decode(i):
|
||||
z, q = divmod(i, N)
|
||||
y, x = divmod(q, NX)
|
||||
return (100 + x * STEP, 100 + y * STEP), z
|
||||
|
||||
|
||||
def astar(blocked, via, start, target, deadline, max_expansions):
|
||||
tx = int(round((target[0] - 100) / STEP))
|
||||
ty = int(round((target[1] - 100) / STEP))
|
||||
targetq = ty * NX + tx
|
||||
|
||||
def h(q):
|
||||
y, x = divmod(q, NX)
|
||||
dx, dy = abs(x - tx), abs(y - ty)
|
||||
return 10 * max(dx, dy) + 4 * min(dx, dy)
|
||||
|
||||
dist = np.full(2 * N, 2147483647, dtype=np.int32)
|
||||
parent = np.full(2 * N, -1, dtype=np.int32)
|
||||
heap = []
|
||||
for z in (0, 1):
|
||||
i = node(start, z)
|
||||
if not blocked[i]:
|
||||
dist[i] = 0
|
||||
heapq.heappush(heap, (h(i % N), 0, i))
|
||||
expanded = 0
|
||||
moves = ((1, 0, 10), (-1, 0, 10), (0, 1, 10), (0, -1, 10),
|
||||
(1, 1, 14), (1, -1, 14), (-1, 1, 14), (-1, -1, 14))
|
||||
while heap:
|
||||
_, cost, i = heapq.heappop(heap)
|
||||
if cost != dist[i]:
|
||||
continue
|
||||
z, q = divmod(i, N)
|
||||
if q == targetq:
|
||||
path = [i]
|
||||
while parent[path[-1]] >= 0:
|
||||
path.append(int(parent[path[-1]]))
|
||||
return path[::-1], expanded
|
||||
expanded += 1
|
||||
if expanded >= max_expansions or (expanded % 1024 == 0 and time.monotonic() >= deadline):
|
||||
return None, expanded
|
||||
y, x = divmod(q, NX)
|
||||
for dx, dy, stepcost in moves:
|
||||
if not (0 <= x + dx < NX and 0 <= y + dy < NY):
|
||||
continue
|
||||
j = i + dy * NX + dx
|
||||
if blocked[j] or (dx and dy and (blocked[i + dx] or blocked[i + dy * NX])):
|
||||
continue
|
||||
# Mild layer preference prevents needless coincident layer choices.
|
||||
penalty = int((z == 0 and dx == 0) or (z == 1 and dy == 0))
|
||||
nc = cost + stepcost + penalty
|
||||
if nc < dist[j]:
|
||||
dist[j], parent[j] = nc, i
|
||||
heapq.heappush(heap, (nc + h(j % N), nc, j))
|
||||
j = (1 - z) * N + q
|
||||
if not via[q] and not blocked[j]:
|
||||
nc = cost + int(18 / STEP) # 1.8 mm equivalent cost for a layer change.
|
||||
if nc < dist[j]:
|
||||
dist[j], parent[j] = nc, i
|
||||
heapq.heappush(heap, (nc + h(q), nc, j))
|
||||
return None, expanded
|
||||
|
||||
|
||||
def add_path(board, path, start, target, net, width):
|
||||
points = [decode(i) for i in path]
|
||||
points = [(start, points[0][1])] + points + [(target, points[-1][1])]
|
||||
# Only merge exactly collinear grid runs; never shortcut obstacle checks.
|
||||
simple = []
|
||||
for item in points:
|
||||
if simple and item == simple[-1]:
|
||||
continue
|
||||
while len(simple) >= 2 and item[1] == simple[-1][1] == simple[-2][1]:
|
||||
a, b, c = simple[-2][0], simple[-1][0], item[0]
|
||||
cross = (b[0]-a[0])*(c[1]-b[1]) - (b[1]-a[1])*(c[0]-b[0])
|
||||
dot = (b[0]-a[0])*(c[0]-b[0]) + (b[1]-a[1])*(c[1]-b[1])
|
||||
if abs(cross) > 1e-8 or dot < 0:
|
||||
break
|
||||
simple.pop()
|
||||
simple.append(item)
|
||||
for (a, z), (b, zz) in zip(simple, simple[1:]):
|
||||
if z != zz:
|
||||
assert a == b
|
||||
item = p.PCB_VIA(board)
|
||||
item.SetPosition(point(a))
|
||||
item.SetWidth(mm(0.7))
|
||||
item.SetDrill(mm(0.3))
|
||||
item.SetViaType(p.VIATYPE_THROUGH)
|
||||
item.SetLayerPair(p.F_Cu, p.B_Cu)
|
||||
else:
|
||||
if a == b:
|
||||
continue
|
||||
item = p.PCB_TRACK(board)
|
||||
item.SetStart(point(a))
|
||||
item.SetEnd(point(b))
|
||||
item.SetWidth(mm(width))
|
||||
item.SetLayer(LAYERS[z])
|
||||
item.SetNetCode(net)
|
||||
board.Add(item)
|
||||
|
||||
|
||||
def pairs_for_net(pads):
|
||||
# Minimum spanning tree includes duplicate switch contacts explicitly.
|
||||
connected = {0}
|
||||
todo = set(range(1, len(pads)))
|
||||
pairs = []
|
||||
while todo:
|
||||
length, a, b = min((math.dist(pos(pads[a]), pos(pads[b])), a, b)
|
||||
for a in connected for b in todo)
|
||||
pairs.append((length, pads[a], pads[b]))
|
||||
connected.add(b)
|
||||
todo.remove(b)
|
||||
return pairs
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--route', action='store_true')
|
||||
parser.add_argument('--overwrite', action='store_true')
|
||||
parser.add_argument('--replace-routes', action='store_true')
|
||||
parser.add_argument('--seconds', type=float, default=240)
|
||||
parser.add_argument('--max-expansions', type=int, default=1200000)
|
||||
args = parser.parse_args()
|
||||
if args.route and not args.overwrite:
|
||||
parser.error('--route requires --overwrite; back up manual routing first')
|
||||
if not 0 < args.seconds <= 900 or not 0 < args.max_expansions <= 1500000:
|
||||
parser.error('bounds: seconds (0,900], max-expansions (0,1500000]')
|
||||
if ROOT.joinpath('~serial-carrier.kicad_pcb.lck').exists():
|
||||
parser.error('PCB editor lock exists; close the PCB before routing')
|
||||
original_bytes = PCB.read_bytes()
|
||||
board = p.LoadBoard(str(PCB))
|
||||
before = identity(board)
|
||||
if board.GetCopperLayerCount() != 2 or len(board.GetFootprints()) != 13:
|
||||
parser.error('unexpected board structure; review router assumptions')
|
||||
if len(board.Zones()) != 1 or not board.Zones()[0].GetIsRuleArea():
|
||||
parser.error('unexpected zones; router supports only the original RF rule area')
|
||||
outline = [(100, 100), (182, 100), (182, 180), (136, 180), (136, 161), (100, 161)]
|
||||
expected_edges = {frozenset((a, b)) for a, b in zip(outline, outline[1:] + outline[:1])}
|
||||
actual_edges = {frozenset(((p.ToMM(e.GetStart().x), p.ToMM(e.GetStart().y)),
|
||||
(p.ToMM(e.GetEnd().x), p.ToMM(e.GetEnd().y))))
|
||||
for e in board.GetDrawings() if e.GetLayer() == p.Edge_Cuts}
|
||||
if actual_edges != expected_edges:
|
||||
parser.error('outline differs from the board-specific routing envelope')
|
||||
reserve = board.Zones()[0]
|
||||
contour = reserve.Outline().COutline(0)
|
||||
rf_points = {(p.ToMM(contour.CPoint(i).x), p.ToMM(contour.CPoint(i).y))
|
||||
for i in range(contour.PointCount())}
|
||||
if (rf_points != {(110, 100), (131, 100), (131, 113), (110, 113)} or
|
||||
not all(reserve.IsOnLayer(layer) for layer in LAYERS) or
|
||||
not all((reserve.GetDoNotAllowTracks(), reserve.GetDoNotAllowVias(),
|
||||
reserve.GetDoNotAllowZoneFills()))):
|
||||
parser.error('RF rule area differs from the board-specific reservation')
|
||||
if board.GetTracks() and not args.replace_routes:
|
||||
parser.error('existing routing protected; --replace-routes required even for preview')
|
||||
for track in list(board.GetTracks()):
|
||||
board.Delete(track)
|
||||
pads = [a for f in board.GetFootprints() for a in f.Pads()]
|
||||
if any(a.GetOrientationDegrees() % 90 or not all(a.IsOnLayer(l) for l in LAYERS) for a in pads):
|
||||
parser.error('router requires axis-aligned through-hole pads')
|
||||
nets = defaultdict(list)
|
||||
for pad in pads:
|
||||
if pad.GetNumber() and pad.GetNetCode():
|
||||
nets[pad.GetNetCode()].append(pad)
|
||||
work = []
|
||||
for net, netpads in nets.items():
|
||||
if len(netpads) > 1:
|
||||
name = netpads[0].GetNetname()
|
||||
for length, a, b in pairs_for_net(netpads):
|
||||
# Short local links first, then the long signal fanout; ground last.
|
||||
priority = (name == '/GND', length)
|
||||
work.append((priority, net, name, a, b))
|
||||
work.sort(key=lambda w: (w[0], w[2]))
|
||||
start_time = time.monotonic()
|
||||
deadline = start_time + args.seconds
|
||||
results = []
|
||||
for _, net, name, a, b in work:
|
||||
width = 0.5 if name in ('/+3V3', '/GND') else 0.25
|
||||
path, expanded = None, 0
|
||||
if time.monotonic() < deadline:
|
||||
blocked, via = masks(board, pads, net, width)
|
||||
path, expanded = astar(blocked, via, pos(a), pos(b), deadline, args.max_expansions)
|
||||
fallback = False
|
||||
if path is None and width == 0.5 and time.monotonic() < deadline:
|
||||
width, fallback = 0.25, True
|
||||
blocked, via = masks(board, pads, net, width)
|
||||
path, more = astar(blocked, via, pos(a), pos(b), deadline, args.max_expansions)
|
||||
expanded += more
|
||||
label = lambda pad: pad.GetParentFootprint().GetReference() + '.' + pad.GetNumber()
|
||||
result = dict(net=name, start=label(a), end=label(b), start_mm=pos(a), end_mm=pos(b),
|
||||
routed=path is not None, width_mm=width, power_fallback=fallback, expansions=expanded)
|
||||
results.append(result)
|
||||
if path is not None:
|
||||
add_path(board, path, pos(a), pos(b), net, width)
|
||||
print(json.dumps(result), flush=True)
|
||||
assert identity(board) == before, 'immutable board data changed'
|
||||
report = dict(draft_only=True, saved=args.route, grid_mm=STEP, clearance_mm=CLEARANCE,
|
||||
safety_margin_mm=MARGIN, via_diameter_mm=0.7, via_drill_mm=0.3,
|
||||
elapsed_seconds=time.monotonic()-start_time, connections=results,
|
||||
routed_tree_edges=sum(r['routed'] for r in results), total_tree_edges=len(results),
|
||||
tracks=sum(not isinstance(t, p.PCB_VIA) for t in board.GetTracks()),
|
||||
vias=sum(isinstance(t, p.PCB_VIA) for t in board.GetTracks()),
|
||||
immutable_identity_sha256=hashlib.sha256(repr(before).encode()).hexdigest(),
|
||||
input_sha256=hashlib.sha256(original_bytes).hexdigest(),
|
||||
validation='KiCad DRC must be run separately; tree-edge counts are not DRC connectivity')
|
||||
if args.route:
|
||||
if PCB.read_bytes() != original_bytes or ROOT.joinpath('~serial-carrier.kicad_pcb.lck').exists():
|
||||
raise RuntimeError('PCB changed or editor opened during routing; refusing overwrite')
|
||||
# Check a serialized candidate before replacing the user's board.
|
||||
candidate = ROOT / 'validation/pcb-routing-candidate.kicad_pcb'
|
||||
p.SaveBoard(str(candidate), board)
|
||||
assert identity(p.LoadBoard(str(candidate))) == before, 'saved identity mismatch'
|
||||
candidate.replace(PCB)
|
||||
ROOT.joinpath('validation/pcb-routing-run.json').write_text(json.dumps(report, indent=2)+'\n')
|
||||
print(json.dumps({k: v for k, v in report.items() if k != 'connections'}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,117 +0,0 @@
|
||||
# MAX3243 carrier — PROVISIONAL female CAD reference / VERIFY MALE
|
||||
|
||||
Footprint: `Carrier:MAX3243_Reference_Provisional` in
|
||||
`Carrier.pretty/MAX3243_Reference_Provisional.kicad_mod`.
|
||||
Intended only for a <=100 × 100 mm, two-copper-layer carrier PCB draft.
|
||||
No schematic assignment or edits are made. This footprint does not establish
|
||||
whole-board fit, connector access, DRC compliance, or fabrication readiness.
|
||||
|
||||
## Evidence and scope
|
||||
|
||||
The user reports that the male **6253** module has the same dimensions. This is
|
||||
not confirmation of its pad map. Geometry below is taken from published **female
|
||||
5988** Eagle CAD, not verified male-specific CAD. Confirm the purchased module's
|
||||
identity, orientation, header numbering, outline and mounting geometry before
|
||||
release. Never infer header order from DE-9 gender or mating-face numbering.
|
||||
|
||||
Source: [Adafruit PCB repository](https://github.com/adafruit/Adafruit-RS-232-Level-Shifter-Breakout-PCB),
|
||||
commit `63c6200bb3ef17d491089cfa6441b1810d79bb20`.
|
||||
Local original: `reference/Adafruit RS-232 Full Pinout Level-Shifter Breakout.brd`;
|
||||
SHA-256 `6a7b35ef909f5a2d243c326c34db3957aaa01275bf2aac8e0c749ff18162e870`
|
||||
was rechecked against the file. See `adafruit-research.md` for provenance and
|
||||
female/male distinctions.
|
||||
|
||||
CAD-derived geometry adapted from the design by **Limor Fried/Ladyada for Adafruit
|
||||
Industries**, under **CC BY-SA 3.0 Unported**:
|
||||
https://creativecommons.org/licenses/by-sa/3.0/ . The CAD-derived footprint and
|
||||
these CAD-derived notes use that license. Preserve the full upstream notices in
|
||||
`reference/README.md` and `reference/license.txt` when redistributing this adaptation.
|
||||
Changes: coordinate transformation, carrier pad convention, NPTH carrier holes,
|
||||
and provisional annotations; no module circuitry or connector package copied.
|
||||
|
||||
## View, origin and exact nominal geometry
|
||||
|
||||
Front/component top view, module facing up, DE-9 toward the top (**negative Y**).
|
||||
Origin is header JP2 pin 1, not a PCB corner. X increases rightward; Y downward.
|
||||
Transform Eagle board coordinates in mm with:
|
||||
|
||||
`x = X - 1.905; y = 2.667 - Y`.
|
||||
|
||||
- Pads 1…12: `(2.54 × (N - 1), 0)`, first-to-last span 27.94 mm.
|
||||
- Full bare-module outline is on `F.Fab`, **not carrier `Edge.Cuts`**.
|
||||
Bounds: X = -1.905…29.845, Y = -26.670…2.667 (31.750 × 29.337 mm).
|
||||
Top corners are square. Bottom corners retain the source 2.540 mm radius arcs.
|
||||
- Left arc: (-1.905, 0.127) to (0.635, 2.667), centre (0.635, 0.127).
|
||||
Right arc: (27.305, 2.667) to (29.845, 0.127), centre (27.305, 0.127).
|
||||
Midpoints use radius/sqrt(2), rounded to nine decimal places in the file;
|
||||
the arcs are not chamfers or a bounding-box approximation.
|
||||
- Two unnumbered **3.00 mm NPTH carrier holes**: (1.905, -8.763) and
|
||||
(26.035, -8.763), spacing 24.130 mm. These transfer module-hole centres and
|
||||
drill diameter only. The source module holes are actually plated, 3.60 mm
|
||||
copper / 3.00 mm drill, unconnected in the source signal map. Carrier NPTH
|
||||
deliberately does not copy that plating or assign electrical connections.
|
||||
No screw, washer, standoff or tool-access clearance is implied.
|
||||
|
||||
## Provisional carrier pads and female reference map
|
||||
|
||||
Same convention documented in `footprint-notes.md` for the other carrier modules:
|
||||
**1.70 × 1.70 mm PTH pads / 1.00 mm drills**, rectangular pad 1, circular pads
|
||||
2…12, `*.Cu` and `*.Mask`, unused copper layers retained. That convention derives
|
||||
from the installed KiCad `PinHeader_1x04_P2.54mm_Vertical`; it is not a selected
|
||||
header/socket qualification. The source module itself uses 1.9304 mm round pads.
|
||||
Confirm actual pins/sockets, finished-hole tolerances, annular rings and assembly
|
||||
height before fabrication. Square pad 1 is a carrier marker, not source evidence.
|
||||
|
||||
| Pad | X (mm), Y=0 | Female label | Female CAD net |
|
||||
|---:|---:|---|---|
|
||||
| 1 | 0.00 | Vin | VCC |
|
||||
| 2 | 2.54 | GND | GND |
|
||||
| 3 | 5.08 | DCD | DCD |
|
||||
| 4 | 7.62 | RX | RXD |
|
||||
| 5 | 10.16 | TX | TXD |
|
||||
| 6 | 12.70 | DTR | DTR |
|
||||
| 7 | 15.24 | DSR | DSR |
|
||||
| 8 | 17.78 | RTS | RTS |
|
||||
| 9 | 20.32 | CTS | CTS |
|
||||
| 10 | 22.86 | RI | RI |
|
||||
| 11 | 25.40 | VLD | !INVALID |
|
||||
| 12 | 27.94 | OFF | !FORCEOFF |
|
||||
|
||||
**This is the female reference map, NOT a confirmed male map.** Footprint pads
|
||||
have numbers only, no assigned nets. OFF is active low; VLD indicates valid input
|
||||
when high. This project's logic interface requires 3.3 V Vin.
|
||||
|
||||
## Intentionally omitted / release gates
|
||||
|
||||
**No verified DE-9 mechanical envelope is available, so none is drawn.** No
|
||||
male overhang, shell, mating plug, cable bend, enclosure cutout, height, underside
|
||||
keepout, mounting-hardware clearance, 3D model or courtyard is invented. Female
|
||||
library silkscreen and product bounding dimensions are not male clearance data.
|
||||
The F.Fab outline describes only the bare module PCB, not the assembled envelope.
|
||||
There is **no fake courtyard**; courtyard checks cannot establish assembly fit.
|
||||
Short silkscreen labels identify PROVISIONAL / FEMALE CAD REF / VERIFY MALE.
|
||||
|
||||
Before layout release: verify male pad map and orientation against the actual
|
||||
module or authoritative male CAD; perform a 1:1 fit check; select headers and
|
||||
mounting hardware; verify assembled and mating envelopes; then establish real
|
||||
clearances/courtyard and check the complete <=100 × 100 mm two-layer layout.
|
||||
|
||||
## Validation performed
|
||||
|
||||
KiCad **10.0.6** `pcbnew.FootprintLoad` successfully loaded the footprint using
|
||||
system Python (`/usr/bin/python3 -B`) in a read-only inline check. Assertions passed
|
||||
for all 12 numbered pad positions against source JP2 package/element coordinates,
|
||||
female signal-contactref order, pad sizes/drills/shapes and copper/mask presence,
|
||||
no assigned header nets, both NPTH centres/sizes/drills, and all six transformed
|
||||
source outline segments including both 90-degree, 2.540 mm radius arcs and centres.
|
||||
Also checked absence of courtyard, Edge.Cuts, zones and 3D models.
|
||||
|
||||
The first check assumed arc endpoint order was preserved; KiCad normalizes arc
|
||||
orientation. The corrected check matches endpoint pairs independent of order and
|
||||
verifies radius, angle and centre; it passed without changing geometry. The
|
||||
binding emitted the same three startup `PROPERTY_ENUM` assertions documented for
|
||||
other module validation; these did not prevent loading or successful checks.
|
||||
No hardware fit test, male electrical verification, board DRC or firmware build
|
||||
was performed. Existing `validate_footprints.py` was not changed and does not
|
||||
cover this new footprint. Only this notes file and the new footprint were written;
|
||||
schematic, project and lock files were left untouched.
|
||||
@@ -1,352 +0,0 @@
|
||||
{
|
||||
"board": {
|
||||
"3dviewports": [],
|
||||
"design_settings": {
|
||||
"defaults": {
|
||||
"apply_defaults_to_fp_barcodes": false,
|
||||
"apply_defaults_to_fp_dimensions": false,
|
||||
"apply_defaults_to_fp_fields": false,
|
||||
"apply_defaults_to_fp_shapes": false,
|
||||
"apply_defaults_to_fp_text": false,
|
||||
"board_outline_line_width": 0.05,
|
||||
"copper_line_width": 0.2,
|
||||
"copper_text_italic": false,
|
||||
"copper_text_size_h": 1.5,
|
||||
"copper_text_size_v": 1.5,
|
||||
"copper_text_thickness": 0.3,
|
||||
"copper_text_upright": false,
|
||||
"courtyard_line_width": 0.05,
|
||||
"dimension_precision": 4,
|
||||
"dimension_units": 3,
|
||||
"dimensions": {
|
||||
"arrow_length": 1270000,
|
||||
"extension_offset": 500000,
|
||||
"keep_text_aligned": true,
|
||||
"suppress_zeroes": true,
|
||||
"text_position": 0,
|
||||
"units_format": 0
|
||||
},
|
||||
"fab_line_width": 0.1,
|
||||
"fab_text_italic": false,
|
||||
"fab_text_size_h": 1.0,
|
||||
"fab_text_size_v": 1.0,
|
||||
"fab_text_thickness": 0.15,
|
||||
"fab_text_upright": false,
|
||||
"other_line_width": 0.1,
|
||||
"other_text_italic": false,
|
||||
"other_text_size_h": 1.0,
|
||||
"other_text_size_v": 1.0,
|
||||
"other_text_thickness": 0.15,
|
||||
"other_text_upright": false,
|
||||
"pads": {
|
||||
"drill": 0.8,
|
||||
"height": 1.27,
|
||||
"width": 1.27
|
||||
},
|
||||
"silk_line_width": 0.1,
|
||||
"silk_text_italic": false,
|
||||
"silk_text_size_h": 1.0,
|
||||
"silk_text_size_v": 1.0,
|
||||
"silk_text_thickness": 0.1,
|
||||
"silk_text_upright": false,
|
||||
"zones": {
|
||||
"border_display_style": 2,
|
||||
"border_hatch_pitch": 0.5,
|
||||
"corner_radius": 0.0,
|
||||
"corner_smoothing": 0,
|
||||
"fill_mode": 0,
|
||||
"hatch_gap": 1.5,
|
||||
"hatch_orientation": 0.0,
|
||||
"hatch_smoothing_level": 0,
|
||||
"hatch_smoothing_value": 0.1,
|
||||
"hatch_thickness": 1.0,
|
||||
"min_clearance": 0.5,
|
||||
"min_island_area": 10.0,
|
||||
"min_thickness": 0.25,
|
||||
"pad_connection": 1,
|
||||
"remove_islands": 0,
|
||||
"thermal_relief_gap": 0.5,
|
||||
"thermal_relief_spoke_width": 0.5
|
||||
}
|
||||
},
|
||||
"diff_pair_dimensions": [],
|
||||
"drc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 2
|
||||
},
|
||||
"rule_severities": {
|
||||
"annular_width": "error",
|
||||
"clearance": "error",
|
||||
"connection_width": "warning",
|
||||
"copper_edge_clearance": "error",
|
||||
"copper_sliver": "warning",
|
||||
"courtyards_overlap": "error",
|
||||
"creepage": "error",
|
||||
"diff_pair_gap_out_of_range": "error",
|
||||
"diff_pair_uncoupled_length_too_long": "error",
|
||||
"drill_out_of_range": "error",
|
||||
"duplicate_footprints": "warning",
|
||||
"extra_footprint": "warning",
|
||||
"footprint": "error",
|
||||
"footprint_filters_mismatch": "ignore",
|
||||
"footprint_symbol_field_mismatch": "warning",
|
||||
"footprint_symbol_mismatch": "warning",
|
||||
"footprint_type_mismatch": "ignore",
|
||||
"hole_clearance": "error",
|
||||
"hole_to_hole": "warning",
|
||||
"holes_co_located": "warning",
|
||||
"invalid_outline": "error",
|
||||
"isolated_copper": "warning",
|
||||
"item_on_disabled_layer": "error",
|
||||
"items_not_allowed": "error",
|
||||
"length_out_of_range": "error",
|
||||
"lib_footprint_issues": "warning",
|
||||
"lib_footprint_mismatch": "warning",
|
||||
"malformed_courtyard": "error",
|
||||
"microvia_drill_out_of_range": "error",
|
||||
"mirrored_text_on_front_layer": "warning",
|
||||
"missing_courtyard": "ignore",
|
||||
"missing_footprint": "warning",
|
||||
"missing_tuning_profile": "warning",
|
||||
"net_conflict": "warning",
|
||||
"nonmirrored_text_on_back_layer": "warning",
|
||||
"npth_inside_courtyard": "error",
|
||||
"padstack": "warning",
|
||||
"pth_inside_courtyard": "error",
|
||||
"shorting_items": "error",
|
||||
"silk_edge_clearance": "warning",
|
||||
"silk_over_copper": "warning",
|
||||
"silk_overlap": "warning",
|
||||
"skew_out_of_range": "error",
|
||||
"solder_mask_bridge": "error",
|
||||
"starved_thermal": "error",
|
||||
"text_height": "warning",
|
||||
"text_on_edge_cuts": "error",
|
||||
"text_thickness": "warning",
|
||||
"through_hole_pad_without_hole": "error",
|
||||
"too_many_vias": "error",
|
||||
"track_angle": "error",
|
||||
"track_dangling": "warning",
|
||||
"track_not_centered_on_via": "ignore",
|
||||
"track_on_post_machined_layer": "error",
|
||||
"track_segment_length": "error",
|
||||
"track_width": "error",
|
||||
"tracks_crossing": "error",
|
||||
"tuning_profile_track_geometries": "ignore",
|
||||
"unconnected_items": "error",
|
||||
"unresolved_variable": "error",
|
||||
"via_dangling": "warning",
|
||||
"zones_intersect": "error"
|
||||
},
|
||||
"rules": {
|
||||
"max_error": 0.005,
|
||||
"min_clearance": 0.25,
|
||||
"min_connection": 0.0,
|
||||
"min_copper_edge_clearance": 0.5,
|
||||
"min_groove_width": 0.0,
|
||||
"min_hole_clearance": 0.25,
|
||||
"min_hole_to_hole": 0.25,
|
||||
"min_microvia_diameter": 0.2,
|
||||
"min_microvia_drill": 0.1,
|
||||
"min_resolved_spokes": 2,
|
||||
"min_silk_clearance": 0.0,
|
||||
"min_text_height": 0.8,
|
||||
"min_text_thickness": 0.08,
|
||||
"min_through_hole_diameter": 0.3,
|
||||
"min_track_width": 0.25,
|
||||
"min_via_annular_width": 0.1,
|
||||
"min_via_diameter": 0.7,
|
||||
"solder_mask_to_copper_clearance": 0.0,
|
||||
"use_height_for_length_calcs": true
|
||||
},
|
||||
"teardrop_options": [
|
||||
{
|
||||
"td_onpthpad": true,
|
||||
"td_onroundshapesonly": false,
|
||||
"td_onsmdpad": true,
|
||||
"td_ontrackend": false,
|
||||
"td_onvia": true
|
||||
}
|
||||
],
|
||||
"teardrop_parameters": [
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_round_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_rect_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_track_end",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
}
|
||||
],
|
||||
"track_widths": [],
|
||||
"tuning_pattern_settings": {
|
||||
"diff_pair_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 1.0
|
||||
},
|
||||
"diff_pair_skew_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
},
|
||||
"single_track_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
}
|
||||
},
|
||||
"via_dimensions": [],
|
||||
"zones_allow_external_fillets": false
|
||||
},
|
||||
"ipc2581": {
|
||||
"bom_rev": "",
|
||||
"dist": "",
|
||||
"distpn": "",
|
||||
"internal_id": "",
|
||||
"mfg": "",
|
||||
"mpn": "",
|
||||
"sch_revision": ""
|
||||
},
|
||||
"layer_pairs": [],
|
||||
"layer_presets": [],
|
||||
"viewports": []
|
||||
},
|
||||
"boards": [],
|
||||
"component_class_settings": {
|
||||
"assignments": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"sheet_component_classes": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"cvpcb": {
|
||||
"equivalence_files": []
|
||||
},
|
||||
"libraries": {
|
||||
"pinned_footprint_libs": [],
|
||||
"pinned_symbol_libs": []
|
||||
},
|
||||
"meta": {
|
||||
"filename": "serial-carrier.kicad_pro",
|
||||
"version": 3
|
||||
},
|
||||
"net_settings": {
|
||||
"classes": [
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.25,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Default",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 2147483647,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.25,
|
||||
"tuning_profile": "",
|
||||
"via_diameter": 0.7,
|
||||
"via_drill": 0.3,
|
||||
"wire_width": 6
|
||||
},
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.25,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.25,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Power",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 0,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.5,
|
||||
"tuning_profile": "",
|
||||
"via_diameter": 0.7,
|
||||
"via_drill": 0.3,
|
||||
"wire_width": 6
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"version": 5
|
||||
},
|
||||
"net_colors": null,
|
||||
"netclass_assignments": null,
|
||||
"netclass_patterns": [
|
||||
{ "netclass": "Power", "pattern": "/+3V3" },
|
||||
{ "netclass": "Power", "pattern": "/GND" }
|
||||
]
|
||||
},
|
||||
"pcbnew": {
|
||||
"last_paths": {
|
||||
"idf": "",
|
||||
"netlist": "",
|
||||
"plot": "",
|
||||
"specctra_dsn": "",
|
||||
"step": "",
|
||||
"vrml": ""
|
||||
},
|
||||
"page_layout_descr_file": ""
|
||||
},
|
||||
"schematic": {
|
||||
"bus_aliases": {},
|
||||
"legacy_lib_dir": "",
|
||||
"legacy_lib_list": [],
|
||||
"top_level_sheets": [
|
||||
{
|
||||
"filename": "serial-carrier.kicad_sch",
|
||||
"name": "serial-carrier",
|
||||
"uuid": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sheets": [],
|
||||
"text_variables": {},
|
||||
"tuning_profiles": {
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"tuning_profiles_impedance_geometric": []
|
||||
}
|
||||
}
|
||||
@@ -1,400 +0,0 @@
|
||||
(kicad_sch (version 20250114) (generator "eeschema") (uuid "c86e0d56-4290-5f2b-b790-14926c8cdeea") (paper "A3")
|
||||
(title_block (title "ESP32 Serial Swiss Army Knife - Module Carrier") (date "2026-09-20") (rev "A0 DRAFT") (comment 1 "Not fabrication-ready: provisional module footprints and male RS-232 mapping"))
|
||||
(lib_symbols (symbol "Carrier:HW678_N16R8" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "U" (at 0 34.29 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "HW678_N16R8" (at 0 31.75 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:HW678_2x22_Provisional" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "HW678_N16R8_0_1" (rectangle (start -17.78 29.21) (end 17.78 -29.21) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "HW678_N16R8_1_1"
|
||||
(pin power_out line (at -22.86 26.67 0) (length 5.08) (name "3V3" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at -22.86 24.13 0) (length 5.08) (name "3V3" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 21.59 0) (length 5.08) (name "EN" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 19.05 0) (length 5.08) (name "GPIO4" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 16.51 0) (length 5.08) (name "GPIO5" (effects (font (size 1.016 1.016)) )) (number "5" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 13.97 0) (length 5.08) (name "GPIO6" (effects (font (size 1.016 1.016)) )) (number "6" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 11.43 0) (length 5.08) (name "GPIO7" (effects (font (size 1.016 1.016)) )) (number "7" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 8.89 0) (length 5.08) (name "GPIO15" (effects (font (size 1.016 1.016)) )) (number "8" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 6.35 0) (length 5.08) (name "GPIO16" (effects (font (size 1.016 1.016)) )) (number "9" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 3.81 0) (length 5.08) (name "GPIO17" (effects (font (size 1.016 1.016)) )) (number "10" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 1.27 0) (length 5.08) (name "GPIO18" (effects (font (size 1.016 1.016)) )) (number "11" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -1.27 0) (length 5.08) (name "GPIO8" (effects (font (size 1.016 1.016)) )) (number "12" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -3.81 0) (length 5.08) (name "GPIO3" (effects (font (size 1.016 1.016)) )) (number "13" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -6.35 0) (length 5.08) (name "GPIO46" (effects (font (size 1.016 1.016)) )) (number "14" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -8.89 0) (length 5.08) (name "GPIO9" (effects (font (size 1.016 1.016)) )) (number "15" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -11.43 0) (length 5.08) (name "GPIO10" (effects (font (size 1.016 1.016)) )) (number "16" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -22.86 -13.97 0) (length 5.08) (name "GPIO11" (effects (font (size 1.016 1.016)) )) (number "17" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -22.86 -16.51 0) (length 5.08) (name "GPIO12" (effects (font (size 1.016 1.016)) )) (number "18" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -19.05 0) (length 5.08) (name "GPIO13" (effects (font (size 1.016 1.016)) )) (number "19" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -22.86 -21.59 0) (length 5.08) (name "GPIO14" (effects (font (size 1.016 1.016)) )) (number "20" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at -22.86 -24.13 0) (length 5.08) (name "5V" (effects (font (size 1.016 1.016)) )) (number "21" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_out line (at -22.86 -26.67 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "22" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 26.67 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "23" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 24.13 180) (length 5.08) (name "GPIO43" (effects (font (size 1.016 1.016)) )) (number "24" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 21.59 180) (length 5.08) (name "GPIO44" (effects (font (size 1.016 1.016)) )) (number "25" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 19.05 180) (length 5.08) (name "GPIO1" (effects (font (size 1.016 1.016)) )) (number "26" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 16.51 180) (length 5.08) (name "GPIO2" (effects (font (size 1.016 1.016)) )) (number "27" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 13.97 180) (length 5.08) (name "GPIO42" (effects (font (size 1.016 1.016)) )) (number "28" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 11.43 180) (length 5.08) (name "GPIO41" (effects (font (size 1.016 1.016)) )) (number "29" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 8.89 180) (length 5.08) (name "GPIO40" (effects (font (size 1.016 1.016)) )) (number "30" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 6.35 180) (length 5.08) (name "GPIO39" (effects (font (size 1.016 1.016)) )) (number "31" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 3.81 180) (length 5.08) (name "GPIO38" (effects (font (size 1.016 1.016)) )) (number "32" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 1.27 180) (length 5.08) (name "GPIO37" (effects (font (size 1.016 1.016)) )) (number "33" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -1.27 180) (length 5.08) (name "GPIO36" (effects (font (size 1.016 1.016)) )) (number "34" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -3.81 180) (length 5.08) (name "GPIO35" (effects (font (size 1.016 1.016)) )) (number "35" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -6.35 180) (length 5.08) (name "GPIO0" (effects (font (size 1.016 1.016)) )) (number "36" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -8.89 180) (length 5.08) (name "GPIO45" (effects (font (size 1.016 1.016)) )) (number "37" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -11.43 180) (length 5.08) (name "GPIO48" (effects (font (size 1.016 1.016)) )) (number "38" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -13.97 180) (length 5.08) (name "GPIO47" (effects (font (size 1.016 1.016)) )) (number "39" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -16.51 180) (length 5.08) (name "GPIO21" (effects (font (size 1.016 1.016)) )) (number "40" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -19.05 180) (length 5.08) (name "GPIO20" (effects (font (size 1.016 1.016)) )) (number "41" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at 22.86 -21.59 180) (length 5.08) (name "GPIO19" (effects (font (size 1.016 1.016)) )) (number "42" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 -24.13 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "43" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 22.86 -26.67 180) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "44" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "Carrier:MAX3243_Module_Reference" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "U" (at 0 22.86 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "MAX3243_Module_Reference" (at 0 20.32 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "MAX3243_Module_Reference_0_1" (rectangle (start -15.24 17.78) (end 15.24 -17.78) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "MAX3243_Module_Reference_1_1"
|
||||
(pin power_in line (at -20.32 13.97 0) (length 5.08) (name "Vin" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_in line (at -20.32 11.43 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 8.89 0) (length 5.08) (name "DCD" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 6.35 0) (length 5.08) (name "RX" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 3.81 0) (length 5.08) (name "TX" (effects (font (size 1.016 1.016)) )) (number "5" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 1.27 0) (length 5.08) (name "DTR" (effects (font (size 1.016 1.016)) )) (number "6" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -1.27 0) (length 5.08) (name "DSR" (effects (font (size 1.016 1.016)) )) (number "7" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 -3.81 0) (length 5.08) (name "RTS" (effects (font (size 1.016 1.016)) )) (number "8" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -6.35 0) (length 5.08) (name "CTS" (effects (font (size 1.016 1.016)) )) (number "9" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -8.89 0) (length 5.08) (name "RI" (effects (font (size 1.016 1.016)) )) (number "10" (effects (font (size 1.016 1.016)) )))
|
||||
(pin output line (at -20.32 -11.43 0) (length 5.08) (name "VLD" (effects (font (size 1.016 1.016)) )) (number "11" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -20.32 -13.97 0) (length 5.08) (name "OFF_N" (effects (font (size 1.016 1.016)) )) (number "12" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "Carrier:OLED_I2C_26mm" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "DS" (at 0 12.7 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "OLED_I2C_26mm" (at 0 10.16 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:OLED_26mm_I2C_Provisional" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "OLED_I2C_26mm_0_1" (rectangle (start -10.16 7.62) (end 10.16 -7.62) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "OLED_I2C_26mm_1_1"
|
||||
(pin power_in line (at -15.24 3.81 0) (length 5.08) (name "GND" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin power_in line (at -15.24 1.27 0) (length 5.08) (name "VCC" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
(pin input line (at -15.24 -1.27 0) (length 5.08) (name "SCL" (effects (font (size 1.016 1.016)) )) (number "3" (effects (font (size 1.016 1.016)) )))
|
||||
(pin bidirectional line (at -15.24 -3.81 0) (length 5.08) (name "SDA" (effects (font (size 1.016 1.016)) )) (number "4" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "Carrier:SW_NO" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "SW" (at 0 7.62 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "SW_NO" (at 0 5.08 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "SW_NO_0_1" (polyline (pts (xy -2.54 0) (xy 2.54 1.524)) (stroke (width 0.254) (type default)) (fill (type none)))(circle (center -2.54 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none)))(circle (center 2.54 0) (radius 0.35) (stroke (width 0.1524) (type default)) (fill (type none))))
|
||||
(symbol "SW_NO_1_1"
|
||||
(pin passive line (at -5.08 0 0) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 5.08 0 180) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
)
|
||||
(symbol "Carrier:R" (pin_names (offset 0.762)) (in_bom yes) (on_board yes)
|
||||
(property "Reference" "R" (at 0 6.096 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "R" (at 0 3.556 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Description" "Resistor, axial DIN0207 footprint; exact MPN pending" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(symbol "R_0_1" (rectangle (start -2.54 1.016) (end 2.54 -1.016) (stroke (width 0.254) (type default)) (fill (type background))))
|
||||
(symbol "R_1_1"
|
||||
(pin passive line (at -5.08 0 0) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "1" (effects (font (size 1.016 1.016)) )))
|
||||
(pin passive line (at 5.08 0 180) (length 2.54) (name "~" (effects (font (size 1.016 1.016)) )) (number "2" (effects (font (size 1.016 1.016)) )))
|
||||
)
|
||||
))
|
||||
(symbol (lib_id "Carrier:HW678_N16R8") (at 95.25 109.22 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "3ab265d0-7fd3-5891-94eb-d39426c6a8a3")
|
||||
(property "Reference" "U1" (at 95.25 74.93 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "HW678 / S3-N16R8" (at 95.25 77.47 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:HW678_2x22_Provisional" (at 95.25 109.22 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 95.25 109.22 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Status" "Header geometry nominal; outline/antenna clearance pending" (at 95.25 109.22 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Pin_numbering" "Left 1-22; right 23-44; both top-to-bottom, antenna up" (at 95.25 109.22 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "4a464e7d-e18b-557c-8fae-641c5fc5419f"))
|
||||
(pin "2" (uuid "8224a9d5-21e3-5814-9dda-20f82d0dff57"))
|
||||
(pin "3" (uuid "c8362eee-41d1-5268-ba40-ab780577836d"))
|
||||
(pin "4" (uuid "c38294c1-5e07-56bc-8afd-b92e622ee4c8"))
|
||||
(pin "5" (uuid "e9137085-cd7e-5481-8165-89ead64f6f0d"))
|
||||
(pin "6" (uuid "7400e635-9d97-5e1b-8bdb-cef9cc1eaaf9"))
|
||||
(pin "7" (uuid "21108d2d-b874-58a0-b220-e7ed30048e86"))
|
||||
(pin "8" (uuid "2d607a2c-f0d7-53db-9f0f-ba99c277bebf"))
|
||||
(pin "9" (uuid "f0feb47f-2080-575a-adaa-6086d2b111ce"))
|
||||
(pin "10" (uuid "f7e3c60e-a23d-5f8f-a0b4-e2259b587906"))
|
||||
(pin "11" (uuid "751f386b-e0e0-5fd7-b93b-5006c04bf57e"))
|
||||
(pin "12" (uuid "35b6d88b-5907-5977-aadc-eeb22f38e80a"))
|
||||
(pin "13" (uuid "351de5e5-c93c-5146-ab20-04e74cf929e8"))
|
||||
(pin "14" (uuid "6ecd8a0c-5a4e-5e23-82e6-4591738b1e23"))
|
||||
(pin "15" (uuid "6a21011c-83bd-5d62-8359-1c2d77b4b8d8"))
|
||||
(pin "16" (uuid "11810c32-a16c-5508-9766-59b4bace344a"))
|
||||
(pin "17" (uuid "44b4c7dd-10ab-59cb-bf10-6e996820493b"))
|
||||
(pin "18" (uuid "6d2d2610-f346-5f8b-924a-7b7a7b5dec7d"))
|
||||
(pin "19" (uuid "bde6865b-0c4c-51d5-b38d-38fd9c0b2ba2"))
|
||||
(pin "20" (uuid "dd0e2dad-30a0-5212-b7d5-873b048010e4"))
|
||||
(pin "21" (uuid "234394ae-14d9-5f7c-86fc-96d485769c7d"))
|
||||
(pin "22" (uuid "bfbc25e0-b266-5930-9094-3930cb4640e3"))
|
||||
(pin "23" (uuid "18243b22-b464-570f-8adf-2ccce1be2e2d"))
|
||||
(pin "24" (uuid "7a33ec03-7756-557b-8890-fddda9d64305"))
|
||||
(pin "25" (uuid "98b98ba2-2019-5fd3-b268-37af497dda99"))
|
||||
(pin "26" (uuid "c7309c87-2402-506d-8b55-d67799b08001"))
|
||||
(pin "27" (uuid "2744eff7-a534-579f-9cac-da2eb007b47e"))
|
||||
(pin "28" (uuid "50f8137f-f265-5cdb-844e-c4025996014c"))
|
||||
(pin "29" (uuid "03a45791-629b-5973-b58b-de8e7260c3d9"))
|
||||
(pin "30" (uuid "51687701-e717-570b-858f-d0037fde62d1"))
|
||||
(pin "31" (uuid "36822cf0-a680-5e1f-9105-38331f2639e6"))
|
||||
(pin "32" (uuid "2b4d7757-c4ba-5eff-9b68-ef9fbff349de"))
|
||||
(pin "33" (uuid "0864a47a-88c4-5564-be5c-54cede1eb4ff"))
|
||||
(pin "34" (uuid "87ae74c4-9ef5-520f-b909-5eef264c51cb"))
|
||||
(pin "35" (uuid "1359325a-5bab-59f5-97c1-0ef02ad26e24"))
|
||||
(pin "36" (uuid "d2ffd2b2-a6ed-55dc-b6f6-e23ab2ee6823"))
|
||||
(pin "37" (uuid "fdaaa89f-154c-565f-a9d1-6946c6b7a59a"))
|
||||
(pin "38" (uuid "5db889a0-00f5-55dc-bd75-68665bc1d769"))
|
||||
(pin "39" (uuid "9a24d8b0-bd02-5752-ac4d-5003fce71762"))
|
||||
(pin "40" (uuid "06937bd2-e8de-5f19-81fb-5d225f94caa6"))
|
||||
(pin "41" (uuid "7079189c-8336-52a4-a74b-10823730406d"))
|
||||
(pin "42" (uuid "08e6fb4c-12a9-5cdd-a96e-5a66b544c321"))
|
||||
(pin "43" (uuid "c0a0436c-8a72-5462-83b4-5aed704aaf5d"))
|
||||
(pin "44" (uuid "cf42b784-faa2-51f5-8b6e-9cbe009fc3ac"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "U1") (unit 1)))))
|
||||
(wire (pts (xy 72.39 82.55) (xy 59.69 82.55)) (stroke (width 0) (type default)) (uuid "6a5c3165-28b8-521e-b59d-937eb48ebc86"))
|
||||
(label "+3V3" (at 59.69 82.55 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8e9c5cf1-acd7-5bd0-b378-16feb2193986"))
|
||||
(wire (pts (xy 72.39 85.09) (xy 59.69 85.09)) (stroke (width 0) (type default)) (uuid "eadb072f-1c0c-5867-bf06-c526efaa7ffa"))
|
||||
(label "+3V3" (at 59.69 85.09 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "e35c02e0-4ded-52af-ae01-eebe07485e0a"))
|
||||
(no_connect (at 72.39 87.63) (uuid "14e4ee36-cb86-5b36-8d06-52b3a1804732"))
|
||||
(wire (pts (xy 72.39 90.17) (xy 59.69 90.17)) (stroke (width 0) (type default)) (uuid "d9ad45bb-26c2-586f-8ab9-30ddeba5fc31"))
|
||||
(label "RS_DCD" (at 59.69 90.17 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "af29ef10-6f48-5a58-801f-c006fcf9b755"))
|
||||
(wire (pts (xy 72.39 92.71) (xy 59.69 92.71)) (stroke (width 0) (type default)) (uuid "ca28ce58-fdf8-5a5f-89da-488eab1b4bfd"))
|
||||
(label "RS_DSR" (at 59.69 92.71 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "ac3c4cd0-0cce-5cdb-a61d-0b588434404d"))
|
||||
(wire (pts (xy 72.39 95.25) (xy 59.69 95.25)) (stroke (width 0) (type default)) (uuid "78f6e2c3-e080-56f6-9960-3033cc78e51a"))
|
||||
(label "RS_RI" (at 59.69 95.25 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8de0b0a5-4244-5078-a0da-3ee5745e5680"))
|
||||
(wire (pts (xy 72.39 97.79) (xy 59.69 97.79)) (stroke (width 0) (type default)) (uuid "04298e7e-1e1a-5b86-92e8-c5785182688b"))
|
||||
(label "RS_DTR" (at 59.69 97.79 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "d9d01c92-5403-5947-8a87-fae7e62d0d8e"))
|
||||
(wire (pts (xy 72.39 100.33) (xy 59.69 100.33)) (stroke (width 0) (type default)) (uuid "9456c018-93ae-5ed8-9c1b-32a325d8b379"))
|
||||
(label "RS_RTS" (at 59.69 100.33 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "4d8c3f06-455e-55db-8fff-396fa0b7e11a"))
|
||||
(wire (pts (xy 72.39 102.87) (xy 59.69 102.87)) (stroke (width 0) (type default)) (uuid "d2ec21ba-7af9-5da0-9e08-1661dd30374a"))
|
||||
(label "RS_CTS" (at 59.69 102.87 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8c1a0ed8-16a1-5fc1-ba90-0548b73dc83b"))
|
||||
(wire (pts (xy 72.39 105.41) (xy 59.69 105.41)) (stroke (width 0) (type default)) (uuid "037b448f-7b47-5741-87e1-1b6f49b9b9f0"))
|
||||
(label "RS_TX" (at 59.69 105.41 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "072eac8c-bd61-5168-a15c-94d4da50a863"))
|
||||
(wire (pts (xy 72.39 107.95) (xy 59.69 107.95)) (stroke (width 0) (type default)) (uuid "56d395e8-2d19-5606-9ca3-743ff07722be"))
|
||||
(label "RS_RX" (at 59.69 107.95 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "e024b664-2aef-5efc-ba59-2a443f66599f"))
|
||||
(wire (pts (xy 72.39 110.49) (xy 59.69 110.49)) (stroke (width 0) (type default)) (uuid "77cbac5d-1230-55d8-8e74-1e41c2cd8da4"))
|
||||
(label "RS_VALID" (at 59.69 110.49 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "35c2e93d-3962-566b-8683-cac12c0fa27a"))
|
||||
(no_connect (at 72.39 113.03) (uuid "4aefdb9e-0fca-5fcc-875b-f873870fdebe"))
|
||||
(no_connect (at 72.39 115.57) (uuid "2948fac0-bacc-54c4-a568-6ee3d49ff31d"))
|
||||
(wire (pts (xy 72.39 118.11) (xy 59.69 118.11)) (stroke (width 0) (type default)) (uuid "1aef75f4-ccce-555e-b023-5194df0779c2"))
|
||||
(label "RS_OFF_N" (at 59.69 118.11 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "b709828f-6e83-5536-afa3-68fa465f6c7c"))
|
||||
(wire (pts (xy 72.39 120.65) (xy 59.69 120.65)) (stroke (width 0) (type default)) (uuid "c7d046c1-c102-5045-a0d5-88b122a73c46"))
|
||||
(label "BTN_PREVIOUS" (at 59.69 120.65 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "1482502c-6db5-5ee4-a35b-e191cbdf59c0"))
|
||||
(wire (pts (xy 72.39 123.19) (xy 59.69 123.19)) (stroke (width 0) (type default)) (uuid "fda79afa-ecf6-5090-8178-a3d646b2a38b"))
|
||||
(label "OLED_SDA" (at 59.69 123.19 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "ce3c03fe-db6d-5427-98cb-b65007bbe53a"))
|
||||
(wire (pts (xy 72.39 125.73) (xy 59.69 125.73)) (stroke (width 0) (type default)) (uuid "9da4032e-2f54-5d2e-93f9-49b135df292b"))
|
||||
(label "OLED_SCL" (at 59.69 125.73 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "eca1f64d-3fe1-5d99-b1b8-acb054f69bb6"))
|
||||
(wire (pts (xy 72.39 128.27) (xy 59.69 128.27)) (stroke (width 0) (type default)) (uuid "c109b035-7762-5c19-b891-fa46ddfcdec5"))
|
||||
(label "BTN_SELECT" (at 59.69 128.27 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "b5214ec2-aedd-52d0-8a85-45d5c7d79446"))
|
||||
(wire (pts (xy 72.39 130.81) (xy 59.69 130.81)) (stroke (width 0) (type default)) (uuid "9d3c585c-1f7e-5a44-bcad-7339d79884d3"))
|
||||
(label "BTN_NEXT" (at 59.69 130.81 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "0d34aeea-5456-5b2f-8971-ad47f4c23529"))
|
||||
(no_connect (at 72.39 133.35) (uuid "bcd294b1-7855-539a-88ec-8de20fec460c"))
|
||||
(wire (pts (xy 72.39 135.89) (xy 59.69 135.89)) (stroke (width 0) (type default)) (uuid "8294090c-9506-522a-b079-913fdf29a6ad"))
|
||||
(label "GND" (at 59.69 135.89 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8614b2df-be43-533a-b997-e83d9f4c10f9"))
|
||||
(wire (pts (xy 118.11 82.55) (xy 130.81 82.55)) (stroke (width 0) (type default)) (uuid "cde93e84-4bb2-5344-b18d-5f88f9a1f470"))
|
||||
(label "GND" (at 130.81 82.55 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "1d57f5aa-46b1-5519-ab0d-26076787e212"))
|
||||
(no_connect (at 118.11 85.09) (uuid "b0127af0-5316-5fdd-a572-e50e49541d94"))
|
||||
(no_connect (at 118.11 87.63) (uuid "8171ff7a-3067-5768-aba5-6df8c9bb0001"))
|
||||
(no_connect (at 118.11 90.17) (uuid "4e3e062e-eb44-5b93-ad09-fb7e0c85f42f"))
|
||||
(no_connect (at 118.11 92.71) (uuid "d3db7568-87aa-5f7a-809f-c72a30f1d126"))
|
||||
(no_connect (at 118.11 95.25) (uuid "16df0850-16e0-5696-8909-523afebe0b32"))
|
||||
(no_connect (at 118.11 97.79) (uuid "0a438c8b-add8-5a25-b26a-242898998c02"))
|
||||
(no_connect (at 118.11 100.33) (uuid "13b2680e-cbda-5e35-87d8-7342c24bee65"))
|
||||
(no_connect (at 118.11 102.87) (uuid "071dfd7b-7397-52ad-9228-900c74ee3cd6"))
|
||||
(no_connect (at 118.11 105.41) (uuid "0f2099f9-3f16-5cdd-9e67-692d8c63d801"))
|
||||
(no_connect (at 118.11 107.95) (uuid "1b7e29af-3042-5688-b8e3-b9072f2d45bc"))
|
||||
(no_connect (at 118.11 110.49) (uuid "da9f9ee2-d65e-5e7c-b541-066369051c98"))
|
||||
(no_connect (at 118.11 113.03) (uuid "c09c2fc0-4f3a-5a00-a922-dbf5240160d0"))
|
||||
(no_connect (at 118.11 115.57) (uuid "45c970fe-f750-5241-b68f-a3210b95f132"))
|
||||
(no_connect (at 118.11 118.11) (uuid "706fb938-caad-5342-90a4-6aa8d26a6983"))
|
||||
(no_connect (at 118.11 120.65) (uuid "c9aaa7a4-9c4c-5e1e-b271-2c56582d90c1"))
|
||||
(no_connect (at 118.11 123.19) (uuid "b205a9f5-8146-532d-ad52-36338d4e5a24"))
|
||||
(no_connect (at 118.11 125.73) (uuid "30825573-28ae-50d6-8a7d-0b77a1808f1f"))
|
||||
(no_connect (at 118.11 128.27) (uuid "9f2dc584-46f4-512b-90a1-38635223684a"))
|
||||
(no_connect (at 118.11 130.81) (uuid "ab0f10b3-46d5-5781-a1e1-7d23622f3c22"))
|
||||
(wire (pts (xy 118.11 133.35) (xy 130.81 133.35)) (stroke (width 0) (type default)) (uuid "bb4a0a06-459f-57bb-a130-ac9bfacba61e"))
|
||||
(label "GND" (at 130.81 133.35 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "bb66c329-fbef-5925-99b1-cc08edbbe010"))
|
||||
(wire (pts (xy 118.11 135.89) (xy 130.81 135.89)) (stroke (width 0) (type default)) (uuid "4571a4ff-b89e-51b0-adc4-45846a4d8d8c"))
|
||||
(label "GND" (at 130.81 135.89 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "9c4d50e5-720c-5ef0-8c5e-a0a59725f0a8"))
|
||||
(symbol (lib_id "Carrier:MAX3243_Module_Reference") (at 228.6 96.52 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "1127195e-e393-5d1d-9119-b25d10556144")
|
||||
(property "Reference" "U2" (at 228.6 73.66 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "Adafruit MAX3243 / male 6253" (at 228.6 76.2 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "" (at 228.6 96.52 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 228.6 96.52 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Status" "VERIFY male header numbering; footprint intentionally unassigned" (at 228.6 96.52 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet_source" "adafruit-research.md; reference female 5988 CAD" (at 228.6 96.52 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "14d1ea7b-f53a-592f-9305-545890b94099"))
|
||||
(pin "2" (uuid "b2027da1-66f1-5c08-b29b-b63e30a160ff"))
|
||||
(pin "3" (uuid "5d477802-5ff5-5dda-b518-af959915061c"))
|
||||
(pin "4" (uuid "86c4ca1b-d379-5b5c-93d3-383e356ea671"))
|
||||
(pin "5" (uuid "280aecb0-4e1c-5a83-b678-4ef080970881"))
|
||||
(pin "6" (uuid "339f561b-dd28-56b7-bd1b-1a8b222b3494"))
|
||||
(pin "7" (uuid "1960dc0c-b0fa-5dd9-9623-258d944e85d7"))
|
||||
(pin "8" (uuid "97cacb1f-982d-5831-b852-2ced708e5b19"))
|
||||
(pin "9" (uuid "40aa2c26-8837-51c7-89f3-c91fdfa456de"))
|
||||
(pin "10" (uuid "a890a4e8-65ab-5a8b-a491-6b1d4531d079"))
|
||||
(pin "11" (uuid "ec2dfe5e-4072-560e-880e-ab8f8a85cbe9"))
|
||||
(pin "12" (uuid "7e4f35fe-028a-5516-8170-04eadc2b1c28"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "U2") (unit 1)))))
|
||||
(wire (pts (xy 208.28 82.55) (xy 195.58 82.55)) (stroke (width 0) (type default)) (uuid "e93d1edd-22c3-5eae-800d-3921e5221a7c"))
|
||||
(label "+3V3" (at 195.58 82.55 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "ac84abc3-1072-5498-9c83-e40d54a3bea6"))
|
||||
(wire (pts (xy 208.28 85.09) (xy 195.58 85.09)) (stroke (width 0) (type default)) (uuid "82783bca-950c-5c43-996b-9408064b2edf"))
|
||||
(label "GND" (at 195.58 85.09 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "75ec2808-cc79-570e-9845-9fb0a98d3231"))
|
||||
(wire (pts (xy 208.28 87.63) (xy 195.58 87.63)) (stroke (width 0) (type default)) (uuid "c0de1ff8-0c7f-5ae8-ba62-2b3302eadc93"))
|
||||
(label "RS_DCD" (at 195.58 87.63 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "9904c95d-f8a0-5894-afa6-445f8a3cfa00"))
|
||||
(wire (pts (xy 208.28 90.17) (xy 195.58 90.17)) (stroke (width 0) (type default)) (uuid "8d826d42-7bf4-55d8-b1f8-8346d7a75102"))
|
||||
(label "RS_RX" (at 195.58 90.17 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "4edfb969-0023-5b12-842b-d010d64b6c9f"))
|
||||
(wire (pts (xy 208.28 92.71) (xy 195.58 92.71)) (stroke (width 0) (type default)) (uuid "1de1a809-0c2b-5c66-93fa-b6572d03b40f"))
|
||||
(label "RS_TX" (at 195.58 92.71 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "8a97d238-7df7-5201-a7c2-215b084d0edd"))
|
||||
(wire (pts (xy 208.28 95.25) (xy 195.58 95.25)) (stroke (width 0) (type default)) (uuid "6df22e5c-3f66-557d-b3d0-d2ed031525c5"))
|
||||
(label "RS_DTR" (at 195.58 95.25 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "7c80f558-16d4-5ba6-b673-c2b2cbff09e1"))
|
||||
(wire (pts (xy 208.28 97.79) (xy 195.58 97.79)) (stroke (width 0) (type default)) (uuid "886620b9-42d6-52e1-8483-23facfd02f19"))
|
||||
(label "RS_DSR" (at 195.58 97.79 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "a0a94ae8-b47e-5b05-846f-0efd8f397f55"))
|
||||
(wire (pts (xy 208.28 100.33) (xy 195.58 100.33)) (stroke (width 0) (type default)) (uuid "70dbba76-347e-5f98-8da2-e037f8f592f9"))
|
||||
(label "RS_RTS" (at 195.58 100.33 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "09b654e4-afd9-5168-9822-b62724f90a67"))
|
||||
(wire (pts (xy 208.28 102.87) (xy 195.58 102.87)) (stroke (width 0) (type default)) (uuid "1894bdb0-a2a3-5854-a12b-6a69d76e862b"))
|
||||
(label "RS_CTS" (at 195.58 102.87 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "cf747b92-81c2-5cca-8e92-d01f0789fe3f"))
|
||||
(wire (pts (xy 208.28 105.41) (xy 195.58 105.41)) (stroke (width 0) (type default)) (uuid "3e973901-b924-5ee4-a21b-264b3911c50c"))
|
||||
(label "RS_RI" (at 195.58 105.41 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "cda14f57-0d83-5d8d-a65d-3fd304c5cef9"))
|
||||
(wire (pts (xy 208.28 107.95) (xy 195.58 107.95)) (stroke (width 0) (type default)) (uuid "1fa8fc2c-f25a-5d81-8a72-ab7b3168abd2"))
|
||||
(label "RS_VALID" (at 195.58 107.95 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "7e7957b3-d8ff-51ef-996c-19b68b0254a0"))
|
||||
(wire (pts (xy 208.28 110.49) (xy 195.58 110.49)) (stroke (width 0) (type default)) (uuid "ae100e76-5158-5372-b653-0e3d739e4d8d"))
|
||||
(label "RS_OFF_N" (at 195.58 110.49 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "734376cf-c18c-512e-a0a4-10246dd2fa26"))
|
||||
(symbol (lib_id "Carrier:OLED_I2C_26mm") (at 228.6 152.4 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "0ae411c1-94cc-5551-9037-1d59c01a8ee5")
|
||||
(property "Reference" "DS1" (at 228.6 139.7 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "128x64 I2C OLED / 0x3C" (at 228.6 142.24 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Carrier:OLED_26mm_I2C_Provisional" (at 228.6 152.4 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 228.6 152.4 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Status" "Module outline/header/display user fit-verified; pad/drill fit provisional" (at 228.6 152.4 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "0d23b99f-a7aa-5862-b319-434604b281fc"))
|
||||
(pin "2" (uuid "5697134b-5fea-552c-a972-f5008c22b049"))
|
||||
(pin "3" (uuid "8b10ead6-0242-5709-97f0-2f4290180cdd"))
|
||||
(pin "4" (uuid "3908fc82-c607-5bec-ab4c-90efe237abf6"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "DS1") (unit 1)))))
|
||||
(wire (pts (xy 213.36 148.59) (xy 200.66 148.59)) (stroke (width 0) (type default)) (uuid "41bf2da6-78f1-52d1-9f16-4b8edb2221fe"))
|
||||
(label "GND" (at 200.66 148.59 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "7942dcf6-75b1-533a-b9d5-c9a5c9032399"))
|
||||
(wire (pts (xy 213.36 151.13) (xy 200.66 151.13)) (stroke (width 0) (type default)) (uuid "8da97684-8a90-54ff-b1ca-8bc93e05eacc"))
|
||||
(label "+3V3" (at 200.66 151.13 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "5388601d-60c8-5d05-b5b4-c0abd390b4b2"))
|
||||
(wire (pts (xy 213.36 153.67) (xy 200.66 153.67)) (stroke (width 0) (type default)) (uuid "459e4f2f-bf9d-5e2f-a3c6-35fbef8ce0cf"))
|
||||
(label "OLED_SCL" (at 200.66 153.67 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "abe6b20c-25f7-555c-b995-c60fe96c0db5"))
|
||||
(wire (pts (xy 213.36 156.21) (xy 200.66 156.21)) (stroke (width 0) (type default)) (uuid "9a563337-ee5e-5679-a957-fac4fe55933a"))
|
||||
(label "OLED_SDA" (at 200.66 156.21 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "dc86b9ed-3904-50d0-8423-c02004518d88"))
|
||||
(symbol (lib_id "Carrier:R") (at 327.66 88.9 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "defb78eb-632f-5f9d-a59d-dffbb1d31dec")
|
||||
(property "Reference" "R1" (at 327.66 82.804 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "2.2k 1%" (at 327.66 85.344 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 327.66 88.9 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 88.9 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Rating" "At least 0.125 W; exact resistor MPN pending" (at 327.66 88.9 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "79a88116-1a52-5fcb-855e-da54defa9400"))
|
||||
(pin "2" (uuid "b56a5773-68f5-5c6d-936a-8289c11714de"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "R1") (unit 1)))))
|
||||
(wire (pts (xy 322.58 88.9) (xy 309.88 88.9)) (stroke (width 0) (type default)) (uuid "72f62c22-6c21-5ce4-95b5-97fe6afaea38"))
|
||||
(label "+3V3" (at 309.88 88.9 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "da597fd4-3753-56ba-8a8c-b7ae3d6d6c20"))
|
||||
(wire (pts (xy 332.74 88.9) (xy 345.44 88.9)) (stroke (width 0) (type default)) (uuid "a965fd28-156e-51a9-a567-a09ee3288b26"))
|
||||
(label "BTN_PREVIOUS" (at 345.44 88.9 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "b0cdb46e-c123-5a1b-9450-8d6d573bf376"))
|
||||
(symbol (lib_id "Carrier:SW_NO") (at 327.66 104.14 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "dd5f1f75-0093-55fc-9814-437ed26871eb")
|
||||
(property "Reference" "SW1" (at 327.66 96.52 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "B3F-1000" (at 327.66 99.06 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Manufacturer" "Omron" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "MPN" "B3F-1000" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Function" "Previous / back" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Procurement" "Live stock not verified" (at 327.66 104.14 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "0e490c78-3450-5a5b-b7d3-d55c32de988b"))
|
||||
(pin "2" (uuid "6bbc82d1-8cfb-5fd5-832c-8df2775a89c9"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "SW1") (unit 1)))))
|
||||
(wire (pts (xy 322.58 104.14) (xy 309.88 104.14)) (stroke (width 0) (type default)) (uuid "5b028b05-8a36-55db-aaf9-9b382e614203"))
|
||||
(label "BTN_PREVIOUS" (at 309.88 104.14 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "5e45c195-3cf8-5a7b-93e1-84b53b154824"))
|
||||
(wire (pts (xy 332.74 104.14) (xy 345.44 104.14)) (stroke (width 0) (type default)) (uuid "4036fecd-dbe2-5786-a9b9-dce5b14fbf7f"))
|
||||
(label "GND" (at 345.44 104.14 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "d3b7a7cc-4c33-5080-b795-7892d393bb53"))
|
||||
(text "Previous / back" (at 302.26 110.49 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "d84df288-be52-5de4-899d-bf29ba78f4f2"))
|
||||
(symbol (lib_id "Carrier:R") (at 327.66 124.46 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "405055c0-35ab-5334-ad4d-30d45c5ef6f2")
|
||||
(property "Reference" "R2" (at 327.66 118.364 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "2.2k 1%" (at 327.66 120.904 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 327.66 124.46 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 124.46 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Rating" "At least 0.125 W; exact resistor MPN pending" (at 327.66 124.46 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "c23dbef2-d61d-56fa-a158-bb9912ef48ac"))
|
||||
(pin "2" (uuid "f60eb205-60d1-5d8f-ac84-ca04f66e7106"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "R2") (unit 1)))))
|
||||
(wire (pts (xy 322.58 124.46) (xy 309.88 124.46)) (stroke (width 0) (type default)) (uuid "eb68b8eb-dbe6-5133-8089-e9de01165396"))
|
||||
(label "+3V3" (at 309.88 124.46 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "5bef1a07-0985-5bb8-9b36-ed0c9e2c82ab"))
|
||||
(wire (pts (xy 332.74 124.46) (xy 345.44 124.46)) (stroke (width 0) (type default)) (uuid "b3b13ef4-4203-52d8-889b-70534a4a1b28"))
|
||||
(label "BTN_SELECT" (at 345.44 124.46 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "e8d69aad-ffba-5ace-a414-b44ff7528289"))
|
||||
(symbol (lib_id "Carrier:SW_NO") (at 327.66 139.7 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "c522a716-55a4-584d-a12e-ab135b4c08eb")
|
||||
(property "Reference" "SW2" (at 327.66 132.08 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "B3F-1000" (at 327.66 134.62 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Manufacturer" "Omron" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "MPN" "B3F-1000" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Function" "Select / confirm" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Procurement" "Live stock not verified" (at 327.66 139.7 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "19bafbc6-666f-5e53-a4ef-265057c6dc8a"))
|
||||
(pin "2" (uuid "9e3f47ba-92ac-5503-9d87-6b5590a70ccb"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "SW2") (unit 1)))))
|
||||
(wire (pts (xy 322.58 139.7) (xy 309.88 139.7)) (stroke (width 0) (type default)) (uuid "05f5a256-5e68-53ae-a051-81eab3a14d4d"))
|
||||
(label "BTN_SELECT" (at 309.88 139.7 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "a90ee2dc-ed10-5bb9-9082-99cdbfeac4c9"))
|
||||
(wire (pts (xy 332.74 139.7) (xy 345.44 139.7)) (stroke (width 0) (type default)) (uuid "04211708-439f-5d65-a5c6-21cf89318109"))
|
||||
(label "GND" (at 345.44 139.7 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "7f92da60-87bd-5c50-afc0-4046d0c67767"))
|
||||
(text "Select / confirm" (at 302.26 146.05 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "c28f2639-f392-552b-858e-b031aabc8c0a"))
|
||||
(symbol (lib_id "Carrier:R") (at 327.66 160.02 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "78f9a74f-5d91-5687-b50e-68176333df23")
|
||||
(property "Reference" "R3" (at 327.66 153.924 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "2.2k 1%" (at 327.66 156.464 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" (at 327.66 160.02 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 160.02 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Rating" "At least 0.125 W; exact resistor MPN pending" (at 327.66 160.02 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "d871ee2e-bc82-5f23-a436-7b7ab74b73a9"))
|
||||
(pin "2" (uuid "96528b84-ba49-5f38-8445-b6ca2cadc93b"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "R3") (unit 1)))))
|
||||
(wire (pts (xy 322.58 160.02) (xy 309.88 160.02)) (stroke (width 0) (type default)) (uuid "ad7355d6-b196-5b55-b518-28095d4cd010"))
|
||||
(label "+3V3" (at 309.88 160.02 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "08ba6112-d16a-5597-b3f3-8794ed1ea849"))
|
||||
(wire (pts (xy 332.74 160.02) (xy 345.44 160.02)) (stroke (width 0) (type default)) (uuid "7a2af359-d23d-5777-9193-ff203ac538d8"))
|
||||
(label "BTN_NEXT" (at 345.44 160.02 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "5011a121-e49f-5298-8cc1-2998dfa40f7d"))
|
||||
(symbol (lib_id "Carrier:SW_NO") (at 327.66 175.26 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "35caa6bb-42a6-512c-848c-f1e4457aa97f")
|
||||
(property "Reference" "SW3" (at 327.66 167.64 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Value" "B3F-1000" (at 327.66 170.18 0) (effects (font (size 1.27 1.27)) ))
|
||||
(property "Footprint" "Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Datasheet" "" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Manufacturer" "Omron" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "MPN" "B3F-1000" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Function" "Next" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(property "Procurement" "Live stock not verified" (at 327.66 175.26 0) (effects (font (size 1.27 1.27)) (hide yes)))
|
||||
(pin "1" (uuid "85beb3b7-e16f-5450-ba94-5711245a7b25"))
|
||||
(pin "2" (uuid "300c4196-eb91-5e58-a9aa-c84b8f7c4219"))
|
||||
(instances (project "serial-carrier" (path "/c86e0d56-4290-5f2b-b790-14926c8cdeea" (reference "SW3") (unit 1)))))
|
||||
(wire (pts (xy 322.58 175.26) (xy 309.88 175.26)) (stroke (width 0) (type default)) (uuid "899495f9-e27d-5c7f-9961-bda42dd5b5b5"))
|
||||
(label "BTN_NEXT" (at 309.88 175.26 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "4917cd5a-7d37-54f4-8139-7ae4739c44c4"))
|
||||
(wire (pts (xy 332.74 175.26) (xy 345.44 175.26)) (stroke (width 0) (type default)) (uuid "f48c99dc-9974-5205-8a87-3b1278a4fdfd"))
|
||||
(label "GND" (at 345.44 175.26 0) (effects (font (size 1.016 1.016)) (justify left bottom)) (uuid "3c7713ca-d8f2-53fd-8e98-6fd376e4570c"))
|
||||
(text "Next" (at 302.26 181.61 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "0db41e9e-455f-5007-82ee-b485aacda4c4"))
|
||||
(text "MODULE CARRIER - INITIAL SCHEMATIC" (at 15.24 15.24 0) (effects (font (size 2.54 2.54)) (justify left top)) (uuid "81374533-4a9c-521b-8834-9fa30bb907b5"))
|
||||
(text "Native KiCad 10 schematic. Named wire labels join matching nets on this sheet.\nRead hardware/PCB/README.md before layout or fabrication." (at 15.24 24.13 0) (effects (font (size 1.27 1.27)) (justify left top)) (uuid "d9d89d85-2a9c-5e76-bd60-48c81ac7f524"))
|
||||
(text "U1: complete development board" (at 53.34 57.15 0) (effects (font (size 1.778 1.778)) (justify left top)) (uuid "320a8f4e-ff8b-5100-845b-dfe249b39a2e"))
|
||||
(text "Antenna up / USB down: left row 1-22, right row 23-44.\nNC marks mean unused on carrier, not unused inside module." (at 53.34 64.77 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "649df1fa-48c0-580d-aeb8-d019ffd0b61d"))
|
||||
(text "U2: complete RS-232 breakout" (at 177.8 57.15 0) (effects (font (size 1.778 1.778)) (justify left top)) (uuid "5226007c-1904-547d-8ddb-008d14b83d51"))
|
||||
(text "Male 6253 selected. Numbers from published 5988 female CAD.\nVERIFY male header before routing; no footprint assigned.\nDE-9 and charge pump are already on the module." (at 177.8 64.77 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "9b62e7ca-695a-51af-bbbe-e7ee4ee45c08"))
|
||||
(text "Controls: active-low, firmware-debounced" (at 292.1 57.15 0) (effects (font (size 1.778 1.778)) (justify left top)) (uuid "9d91985c-7375-5177-9a5e-8280e3bcaff6"))
|
||||
(text "External 2.2k pull-ups: ~1.5 mA pressed at 3.3 V.\nChosen for B3F-1000 rated contact load; internal\npull-ups may remain enabled. Exact resistor MPN pending." (at 292.1 64.77 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "b2dd72f5-86fc-5ce1-b09a-67a6fe9404d4"))
|
||||
(text "DS1: front view GND / VCC / SCL / SDA" (at 177.8 128.27 0) (effects (font (size 1.524 1.524)) (justify left top)) (uuid "7b57748d-38ca-5712-97f9-bf6dab6dafaf"))
|
||||
(text "Power at 3.3 V only. Verify on-module I2C pull-ups;\nexternal values intentionally not guessed." (at 177.8 165.1 0) (effects (font (size 1.016 1.016)) (justify left top)) (uuid "fb23ec11-40f8-56c7-b78b-0da2d40e4b72"))
|
||||
(text "POWER / RECOVERY\nPower only through U1's existing USB ports. No carrier 5 V input.\nU1 regulator supplies +3V3; GND pins are common on the module.\nPin 1 +3V3 and pin 22 GND model the onboard supply for ERC.\nGPIO19/20 native USB and GPIO43/44 UART0 remain onboard.\nVerify USB power isolation before attaching two powered hosts.\nKeep both USB connectors and reset/boot buttons accessible." (at 15.24 185.42 0) (effects (font (size 1.27 1.27)) (justify left top)) (uuid "cadf6b77-e888-5f04-b4f6-3873114844ca"))
|
||||
(text "RS-232 / ELECTRICAL LIMITS\nTX/RX and modem labels are at the ESP32 logic side.\nOFF_N is active low; GPIO9 must remain open-drain.\nBreakout provides its OFF pull-up. No galvanic isolation.\nNo carrier connection to raw RS-232 voltages.\nCarrier GND, USB GND and RS-232 signal ground are common.\nNever power OLED or MAX3243 logic from 5 V." (at 152.4 185.42 0) (effects (font (size 1.27 1.27)) (justify left top)) (uuid "0f7da388-3edb-5cc3-9937-13492397a221"))
|
||||
(text "BEFORE PCB RELEASE\nVerify male breakout pin order/footprint.\nConfirm header/socket finished-hole requirements.\nComplete module courtyards and antenna clearance.\nVerify 3.3 V budget, OLED pull-ups and USB power path.\nChoose mounting hardware and confirm button stock.\nERC is not hardware or fabrication approval." (at 292.1 185.42 0) (effects (font (size 1.27 1.27)) (justify left top)) (uuid "1df2bd9e-fbff-5047-92a9-09f33d7cd7ac"))
|
||||
)
|
||||
@@ -1,3 +0,0 @@
|
||||
(sym_lib_table (version 7)
|
||||
(lib (name "Carrier")(type "KiCad")(uri "${KIPRJMOD}/Carrier.kicad_sym")(options "")(descr "Project-local module interfaces"))
|
||||
)
|
||||
@@ -1,129 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only regression checks for the two provisional module footprints.
|
||||
|
||||
Run with the system Python that provides KiCad's installed pcbnew binding:
|
||||
/usr/bin/python3 -B hardware/PCB/validate_footprints.py
|
||||
|
||||
Paths resolve relative to this script, independent of the working directory.
|
||||
No board, reports, or footprint files are written. Passing checks do not establish
|
||||
physical connector fit, electrical correctness, DRC, or fabrication readiness.
|
||||
"""
|
||||
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
try:
|
||||
import pcbnew as k
|
||||
except ImportError as exc:
|
||||
raise SystemExit(
|
||||
"KiCad pcbnew binding unavailable. Use the system Python associated with "
|
||||
"your KiCad installation (on this machine: /usr/bin/python3)."
|
||||
) from exc
|
||||
|
||||
|
||||
LIBRARY = Path(__file__).resolve().parent / "Carrier.pretty"
|
||||
|
||||
|
||||
def xy(vector):
|
||||
"""Keep native integer coordinates so comparisons do not hide deviations."""
|
||||
return vector.x, vector.y
|
||||
|
||||
|
||||
def mm(x, y):
|
||||
return k.FromMM(x), k.FromMM(y)
|
||||
|
||||
|
||||
class FootprintRegression(unittest.TestCase):
|
||||
def load(self, name):
|
||||
self.assertTrue((LIBRARY / (name + ".kicad_mod")).is_file(), name)
|
||||
footprint = k.FootprintLoad(str(LIBRARY), name)
|
||||
self.assertIsNotNone(footprint, f"KiCad could not parse {name}")
|
||||
self.assertEqual(footprint.GetValue(), name)
|
||||
self.assertEqual(footprint.GetLayer(), k.F_Cu)
|
||||
self.assertIn("PROVISIONAL connector fit", footprint.GetLibDescription())
|
||||
self.assertEqual(len(list(footprint.Zones())), 0, "Unexpected zone/keepout")
|
||||
self.assertEqual(len(list(footprint.Models())), 0, "Unexpected 3D envelope")
|
||||
graphics = list(footprint.GraphicalItems())
|
||||
for item in graphics:
|
||||
self.assertNotIn(item.GetLayer(), (k.F_CrtYd, k.B_CrtYd, k.Edge_Cuts))
|
||||
self.assertIsInstance(item, (k.PCB_TEXT, k.PCB_SHAPE),
|
||||
"Unexpected graphic/envelope type")
|
||||
self.assertTrue(any(isinstance(item, k.PCB_TEXT)
|
||||
and item.GetText() == "PROVISIONAL CONNECTOR FIT"
|
||||
for item in graphics), "Missing visible provisional label")
|
||||
return footprint
|
||||
|
||||
def check_header(self, footprint, positions, hole_count=0):
|
||||
pads = list(footprint.Pads())
|
||||
self.assertEqual(len(pads), len(positions) + hole_count)
|
||||
numbered = [pad for pad in pads if pad.GetNumber()]
|
||||
# Compare multiplicities so duplicate numbers cannot hide missing pads.
|
||||
self.assertEqual(Counter(p.GetNumber() for p in numbered),
|
||||
Counter({str(n): 1 for n in positions}))
|
||||
for pad in numbered:
|
||||
number = int(pad.GetNumber())
|
||||
with self.subTest(pad=number):
|
||||
self.assertEqual(xy(pad.GetPosition()), mm(*positions[number]))
|
||||
self.assertEqual(xy(pad.GetSize()), mm(1.7, 1.7))
|
||||
self.assertEqual(xy(pad.GetDrillSize()), mm(1, 1))
|
||||
self.assertEqual(pad.GetDrillShape(), k.PAD_DRILL_SHAPE_CIRCLE)
|
||||
self.assertEqual(pad.GetAttribute(), k.PAD_ATTRIB_PTH)
|
||||
self.assertEqual(pad.GetShape(),
|
||||
k.PAD_SHAPE_RECT if number == 1 else k.PAD_SHAPE_CIRCLE)
|
||||
self.assertEqual(pad.GetNetCode(), 0, "Unexpected assigned net")
|
||||
for layer in (k.F_Cu, k.B_Cu, k.F_Mask, k.B_Mask):
|
||||
self.assertTrue(pad.IsOnLayer(layer), "Missing copper/mask layer")
|
||||
for layer in (k.F_Paste, k.B_Paste):
|
||||
self.assertFalse(pad.IsOnLayer(layer), "Unexpected paste layer")
|
||||
return [pad for pad in pads if not pad.GetNumber()]
|
||||
|
||||
def test_oled(self):
|
||||
footprint = self.load("OLED_26mm_I2C_Provisional")
|
||||
positions = {n: (x, 1.5) for n, x in
|
||||
enumerate((9.19, 11.73, 14.27, 16.81), 1)}
|
||||
holes = self.check_header(footprint, positions, hole_count=4)
|
||||
self.assertEqual(Counter(xy(p.GetPosition()) for p in holes),
|
||||
Counter(mm(x, y) for x, y in (
|
||||
(1.75, 1.75), (24.25, 1.75),
|
||||
(1.75, 24.25), (24.25, 24.25))))
|
||||
for pad in holes:
|
||||
with self.subTest(hole=xy(pad.GetPosition())):
|
||||
self.assertEqual(pad.GetAttribute(), k.PAD_ATTRIB_NPTH)
|
||||
self.assertEqual(pad.GetShape(), k.PAD_SHAPE_CIRCLE)
|
||||
self.assertEqual(pad.GetDrillShape(), k.PAD_DRILL_SHAPE_CIRCLE)
|
||||
self.assertEqual(xy(pad.GetSize()), mm(2, 2))
|
||||
self.assertEqual(xy(pad.GetDrillSize()), mm(2, 2))
|
||||
self.assertEqual(pad.GetNetCode(), 0)
|
||||
shapes = [g for g in footprint.GraphicalItems() if isinstance(g, k.PCB_SHAPE)]
|
||||
self.assertEqual(len(shapes), 2, "Only board/display rectangles are verified")
|
||||
self.assertEqual(Counter((xy(g.GetStart()), xy(g.GetEnd())) for g in shapes),
|
||||
Counter([(mm(0, 0), mm(26, 26)),
|
||||
(mm(1, 4.5), mm(25.5, 21))]))
|
||||
for shape in shapes:
|
||||
self.assertEqual(shape.GetShape(), k.SHAPE_T_RECT)
|
||||
self.assertEqual(shape.GetLayer(), k.F_Fab)
|
||||
self.assertEqual(shape.GetWidth(), k.FromMM(0.1))
|
||||
self.assertFalse(shape.IsAnyFill())
|
||||
# Stroke styles are not checked: this binding returns opaque LINE_STYLE
|
||||
# pointers, so comparing GetLineStyle() results would compare identities,
|
||||
# not the solid/dash values, and also emits SWIG leak warnings.
|
||||
|
||||
def test_hw678(self):
|
||||
footprint = self.load("HW678_2x22_Provisional")
|
||||
positions = {
|
||||
row * 22 + index + 1: (x, round(index * 2.54, 2))
|
||||
for row, x in enumerate((0, 25.4)) for index in range(22)
|
||||
}
|
||||
self.check_header(footprint, positions)
|
||||
self.assertFalse(any(isinstance(g, k.PCB_SHAPE)
|
||||
for g in footprint.GraphicalItems()),
|
||||
"HW678 must not acquire guessed outlines or envelopes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"KiCad {k.GetBuildVersion()}; library: {LIBRARY}", flush=True)
|
||||
suite = unittest.defaultTestLoader.loadTestsFromTestCase(FootprintRegression)
|
||||
result = unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
sys.exit(0 if result.wasSuccessful() else 1)
|
||||
@@ -1,94 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only PCB checks and fresh KiCad reports; does not regenerate or save PCB.
|
||||
|
||||
Requires KiCad's pcbnew binding in system Python. The known U2 footprint parity
|
||||
warning is reported explicitly, not hidden or added to KiCad's exclusions.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
import pcbnew as p
|
||||
|
||||
ROOT=Path(__file__).resolve().parent
|
||||
PCB=ROOT/"serial-carrier.kicad_pcb"
|
||||
OUT=ROOT/"validation"
|
||||
|
||||
def run(*args):
|
||||
subprocess.run(args,check=True,cwd=ROOT)
|
||||
|
||||
run("kicad-cli","sch","export","netlist","--format","kicadxml","-o",str(OUT/"pcb-source-netlist.xml"),str(ROOT/"serial-carrier.kicad_sch"))
|
||||
run("kicad-cli","pcb","drc","--format","json","--all-track-errors","--severity-all","--schematic-parity","-o",str(OUT/"pcb-parity-drc.json"),str(PCB))
|
||||
board=p.LoadBoard(str(PCB))
|
||||
source=ET.parse(OUT/"pcb-source-netlist.xml").getroot()
|
||||
comps={c.get("ref"):c for c in source.findall("components/comp")}
|
||||
footprints={f.GetReference():f for f in board.GetFootprints()}
|
||||
assert set(footprints)==set(comps)|{"H1","H2","H3","H4"}
|
||||
assert board.GetCopperLayerCount()==2
|
||||
assert p.ToMM(board.GetDesignSettings().GetBoardThickness())==1.6
|
||||
outline=[d for d in board.GetDrawings() if d.GetLayer()==p.Edge_Cuts]
|
||||
assert len(outline)==6
|
||||
points={(round(p.ToMM(v.x)-100,5),round(p.ToMM(v.y)-100,5)) for d in outline for v in (d.GetStart(),d.GetEnd())}
|
||||
assert points=={(0,0),(82,0),(82,80),(36,80),(36,61),(0,61)}
|
||||
assert max(x for x,y in points)-min(x for x,y in points)==82
|
||||
assert max(y for x,y in points)-min(y for x,y in points)==80
|
||||
padnets={}
|
||||
for net in source.findall("nets/net"):
|
||||
for node in net.findall("node"):
|
||||
padnets[(node.get("ref"),node.get("pin"))]=net.get("name")
|
||||
seen=set()
|
||||
for ref,comp in comps.items():
|
||||
fp=footprints[ref]
|
||||
assert fp.GetValue()==comp.findtext("value"),ref
|
||||
assert fp.GetPath().AsString().endswith("/"+comp.findtext("tstamps")),ref
|
||||
wanted=comp.findtext("footprint")
|
||||
if ref=="U2":
|
||||
assert not wanted
|
||||
assert fp.GetFPIDAsString()=="Carrier:MAX3243_Reference_Provisional"
|
||||
else:
|
||||
assert fp.GetFPIDAsString()==wanted,ref
|
||||
for pad in fp.Pads():
|
||||
if not pad.GetNumber():
|
||||
continue
|
||||
key=ref,pad.GetNumber()
|
||||
assert pad.GetNetname()==padnets[key],(key,pad.GetNetname(),padnets[key])
|
||||
seen.add(key)
|
||||
assert seen==set(padnets),"Missing or extra electrical pads"
|
||||
for ref in ("H1","H2","H3","H4"):
|
||||
assert footprints[ref].IsBoardOnly()
|
||||
pads=list(footprints[ref].Pads())
|
||||
assert len(pads)==1 and pads[0].GetDrillSize().x==p.FromMM(3.2)
|
||||
assert pads[0].GetAttribute()==p.PAD_ATTRIB_NPTH
|
||||
tracks=list(board.GetTracks())
|
||||
assert tracks,"Draft unexpectedly unrouted"
|
||||
for track in tracks:
|
||||
if isinstance(track,p.PCB_VIA):
|
||||
assert track.GetDrillValue()==p.FromMM(0.3)
|
||||
assert track.GetWidth(p.F_Cu)==p.FromMM(0.7)
|
||||
else:
|
||||
assert track.GetLayer() in (p.F_Cu,p.B_Cu)
|
||||
expected=0.5 if track.GetNetname() in ("/+3V3","/GND") else 0.25
|
||||
assert track.GetWidth()==p.FromMM(expected)
|
||||
report=json.loads((OUT/"pcb-parity-drc.json").read_text())
|
||||
assert not report["violations"],report["violations"]
|
||||
assert not report["unconnected_items"],report["unconnected_items"]
|
||||
parity=report["schematic_parity"]
|
||||
assert len(parity)==1 and parity[0]["type"]=="footprint_symbol_mismatch",parity
|
||||
assert len(parity[0]["items"])==1 and parity[0]["items"][0]["uuid"]==footprints["U2"].m_Uuid.AsString()
|
||||
summary={
|
||||
"carrier_bounds_mm":[82,80],
|
||||
"copper_layers":2,
|
||||
"draft_thickness_mm":1.6,
|
||||
"electrical_footprints":len(comps),
|
||||
"proposed_M3_mounts":4,
|
||||
"physical_DRC_violations":0,
|
||||
"unconnected_items":0,
|
||||
"schematic_pad_nets_match":True,
|
||||
"known_parity_warning":"U2 provisional female-reference footprint assigned on PCB only; schematic footprint intentionally blank",
|
||||
"ignored_DRC_checks":report["ignored_checks"],
|
||||
"not_validated":["male module pin map/fit", "assembly envelope and heights", "header/socket fit", "RF performance and antenna clearance", "power/return-path integrity", "USB power path", "OLED pull-ups", "fabrication process", "hardware operation"],
|
||||
}
|
||||
(OUT/"pcb-validation-summary.json").write_text(json.dumps(summary,indent=2)+"\n")
|
||||
print("PCB draft verified: 82 x 80 mm; 2 copper layers; 0 physical DRC violations; 0 unrouted.")
|
||||
print("All schematic pad nets match. One expected U2 footprint parity warning remains visible.")
|
||||
print("Not fabrication-ready; see pcb-draft-notes.md and ignored checks in the DRC report.")
|
||||
@@ -1,94 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export and validate the checked-in schematic; never regenerates the design."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
OUT = ROOT / "validation"
|
||||
SCHEMATIC = ROOT / "serial-carrier.kicad_sch"
|
||||
|
||||
def run(*args):
|
||||
subprocess.run(args, check=True, cwd=ROOT)
|
||||
|
||||
run("kicad-cli", "sch", "export", "netlist", "--format", "kicadxml", "-o", str(OUT / "serial-carrier.xml"), str(SCHEMATIC))
|
||||
run("kicad-cli", "sch", "erc", "--format", "json", "--exit-code-violations", "-o", str(OUT / "erc.json"), str(SCHEMATIC))
|
||||
root = ET.parse(OUT / "serial-carrier.xml").getroot()
|
||||
nets = {net.get("name").removeprefix("/"): net.findall("node") for net in root.findall("nets/net")}
|
||||
members = {name: sorted((node.get("ref"), node.get("pin")) for node in nodes) for name, nodes in nets.items()}
|
||||
components = {comp.get("ref"): comp for comp in root.findall("components/comp")}
|
||||
assert set(components) == {"U1", "U2", "DS1", "SW1", "SW2", "SW3", "R1", "R2", "R3"}
|
||||
|
||||
# Independent firmware-to-module contract, not imported from the schematic generator.
|
||||
contracts = [
|
||||
("RS_TX", "RS232_TX_GPIO", "TX", "5"),
|
||||
("RS_RX", "RS232_RX_GPIO", "RX", "4"),
|
||||
("RS_RTS", "RS232_RTS_GPIO", "RTS", "8"),
|
||||
("RS_CTS", "RS232_CTS_GPIO", "CTS", "9"),
|
||||
("RS_DTR", "RS232_DTR_GPIO", "DTR", "6"),
|
||||
("RS_DSR", "RS232_DSR_GPIO", "DSR", "7"),
|
||||
("RS_DCD", "RS232_DCD_GPIO", "DCD", "3"),
|
||||
("RS_RI", "RS232_RI_GPIO", "RI", "10"),
|
||||
("RS_VALID", "RS232_VALID_GPIO", "VLD", "11"),
|
||||
("RS_OFF_N", "RS232_FORCE_OFF_N_GPIO", "OFF_N", "12"),
|
||||
("OLED_SDA", "LOCAL_UI_DISPLAY_SDA_GPIO", "SDA", "4"),
|
||||
("OLED_SCL", "LOCAL_UI_DISPLAY_SCL_GPIO", "SCL", "3"),
|
||||
("BTN_PREVIOUS", "LOCAL_UI_BUTTON_PREVIOUS_GPIO", None, None),
|
||||
("BTN_SELECT", "LOCAL_UI_BUTTON_SELECT_GPIO", None, None),
|
||||
("BTN_NEXT", "LOCAL_UI_BUTTON_NEXT_GPIO", None, None),
|
||||
]
|
||||
firmware = ROOT.parents[1].joinpath("src/board_pins.h").read_text()
|
||||
for net, macro, peripheral_name, peripheral_pin in contracts:
|
||||
match = re.search(r"^#define\s+" + re.escape(macro) + r"\s+GPIO_NUM_(\d+)\s*$", firmware, re.MULTILINE)
|
||||
assert match, macro
|
||||
gpio = "GPIO" + match.group(1)
|
||||
mcu = [node for node in nets[net] if node.get("ref") == "U1"]
|
||||
assert len(mcu) == 1 and mcu[0].get("pinfunction") == gpio + "_" + mcu[0].get("pin"), (net, gpio)
|
||||
if peripheral_name:
|
||||
ref = "DS1" if net.startswith("OLED_") else "U2"
|
||||
node = [node for node in nets[net] if node.get("ref") == ref]
|
||||
assert len(node) == 1 and node[0].get("pin") == peripheral_pin
|
||||
assert node[0].get("pinfunction") == peripheral_name + "_" + peripheral_pin
|
||||
assert len(nets[net]) == 2, (net, members[net])
|
||||
|
||||
for i, net in enumerate(("BTN_PREVIOUS", "BTN_SELECT", "BTN_NEXT"), 1):
|
||||
assert len(nets[net]) == 3
|
||||
assert (f"SW{i}", "1") in members[net] and (f"R{i}", "2") in members[net]
|
||||
assert components[f"SW{i}"].findtext("value") == "B3F-1000"
|
||||
assert components[f"R{i}"].findtext("value") == "2.2k 1%"
|
||||
assert members["+3V3"] == sorted([("U1","1"),("U1","2"),("U2","1"),("DS1","2"),("R1","1"),("R2","1"),("R3","1")])
|
||||
assert members["GND"] == sorted([("U1","22"),("U1","23"),("U1","43"),("U1","44"),("U2","2"),("DS1","1"),("SW1","2"),("SW2","2"),("SW3","2")])
|
||||
active = {name for name in nets if not name.startswith("unconnected-")}
|
||||
assert active == {"+3V3", "GND"} | {row[0] for row in contracts}
|
||||
for name, nodes in nets.items():
|
||||
if name.startswith("unconnected-"):
|
||||
assert len(nodes) == 1 and nodes[0].get("ref") == "U1"
|
||||
# All 44 MCU header pins must occur exactly once, including deliberate no-connects.
|
||||
assert sorted(int(node.get("pin")) for nodes in nets.values() for node in nodes if node.get("ref") == "U1") == list(range(1,45))
|
||||
# USB/UART0, 5 V and PSRAM-conflicting pins must never be carrier nets.
|
||||
for name in active:
|
||||
assert not any(node.get("ref") == "U1" and node.get("pin") in ("21","24","25","33","34","35","41","42") for node in nets[name])
|
||||
# U2's physical male footprint must stay explicitly unresolved in this draft.
|
||||
assert not components["U2"].findtext("footprint")
|
||||
expected = json.loads((OUT / "expected-nets.json").read_text())
|
||||
assert set(expected) == active
|
||||
for net, nodes in expected.items():
|
||||
assert members[net] == sorted(tuple(node) for node in nodes), net
|
||||
report = json.loads((OUT / "erc.json").read_text())
|
||||
assert not any(sheet["violations"] for sheet in report["sheets"])
|
||||
run("kicad-cli", "sch", "export", "pdf", "-o", str(ROOT / "serial-carrier.pdf"), str(SCHEMATIC))
|
||||
run("kicad-cli", "sch", "export", "svg", "-o", str(OUT) + "/", str(SCHEMATIC))
|
||||
summary = "\n".join([
|
||||
"Native schematic validation passed.",
|
||||
"KiCad ERC: zero reported errors/warnings; no project exclusions added.",
|
||||
"17 connected nets, 9 components; exact net memberships verified.",
|
||||
"15 firmware GPIO definitions independently checked against exported pin functions.",
|
||||
"All 44 MCU header pins accounted for; USB/UART0/5V/PSRAM pins unused on carrier.",
|
||||
"PDF and SVG exported successfully.",
|
||||
"Not validated: male-module physical mapping, power budget/path, OLED pull-ups,",
|
||||
"selected connector fit, PCB DRC/layout, procurement, or assembled hardware.",
|
||||
]) + "\n"
|
||||
(OUT / "summary.txt").write_text(summary)
|
||||
print(summary)
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/erc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T20:14:47",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Global label only appears once in the schematic",
|
||||
"key": "single_global_label"
|
||||
},
|
||||
{
|
||||
"description": "Four connection points are joined together",
|
||||
"key": "four_way_junction"
|
||||
},
|
||||
{
|
||||
"description": "SPICE model issue",
|
||||
"key": "simulation_model_issue"
|
||||
},
|
||||
{
|
||||
"description": "Assigned footprint doesn't match footprint filters",
|
||||
"key": "footprint_filter"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"sheets": [
|
||||
{
|
||||
"path": "/",
|
||||
"uuid_path": "/c86e0d56-4290-5f2b-b790-14926c8cdeea",
|
||||
"violations": []
|
||||
}
|
||||
],
|
||||
"source": "serial-carrier.kicad_sch"
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
{
|
||||
"+3V3": [
|
||||
[
|
||||
"U1",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"U1",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"DS1",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"R1",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"R2",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"R3",
|
||||
"1"
|
||||
]
|
||||
],
|
||||
"BTN_NEXT": [
|
||||
[
|
||||
"U1",
|
||||
"20"
|
||||
],
|
||||
[
|
||||
"R3",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW3",
|
||||
"1"
|
||||
]
|
||||
],
|
||||
"BTN_PREVIOUS": [
|
||||
[
|
||||
"U1",
|
||||
"16"
|
||||
],
|
||||
[
|
||||
"R1",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW1",
|
||||
"1"
|
||||
]
|
||||
],
|
||||
"BTN_SELECT": [
|
||||
[
|
||||
"U1",
|
||||
"19"
|
||||
],
|
||||
[
|
||||
"R2",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW2",
|
||||
"1"
|
||||
]
|
||||
],
|
||||
"GND": [
|
||||
[
|
||||
"U1",
|
||||
"22"
|
||||
],
|
||||
[
|
||||
"U1",
|
||||
"23"
|
||||
],
|
||||
[
|
||||
"U1",
|
||||
"43"
|
||||
],
|
||||
[
|
||||
"U1",
|
||||
"44"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"DS1",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"SW1",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW2",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
"SW3",
|
||||
"2"
|
||||
]
|
||||
],
|
||||
"OLED_SCL": [
|
||||
[
|
||||
"U1",
|
||||
"18"
|
||||
],
|
||||
[
|
||||
"DS1",
|
||||
"3"
|
||||
]
|
||||
],
|
||||
"OLED_SDA": [
|
||||
[
|
||||
"U1",
|
||||
"17"
|
||||
],
|
||||
[
|
||||
"DS1",
|
||||
"4"
|
||||
]
|
||||
],
|
||||
"RS_CTS": [
|
||||
[
|
||||
"U1",
|
||||
"9"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"9"
|
||||
]
|
||||
],
|
||||
"RS_DCD": [
|
||||
[
|
||||
"U1",
|
||||
"4"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"3"
|
||||
]
|
||||
],
|
||||
"RS_DSR": [
|
||||
[
|
||||
"U1",
|
||||
"5"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"7"
|
||||
]
|
||||
],
|
||||
"RS_DTR": [
|
||||
[
|
||||
"U1",
|
||||
"7"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"6"
|
||||
]
|
||||
],
|
||||
"RS_OFF_N": [
|
||||
[
|
||||
"U1",
|
||||
"15"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"12"
|
||||
]
|
||||
],
|
||||
"RS_RI": [
|
||||
[
|
||||
"U1",
|
||||
"6"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"10"
|
||||
]
|
||||
],
|
||||
"RS_RTS": [
|
||||
[
|
||||
"U1",
|
||||
"8"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"8"
|
||||
]
|
||||
],
|
||||
"RS_RX": [
|
||||
[
|
||||
"U1",
|
||||
"11"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"4"
|
||||
]
|
||||
],
|
||||
"RS_TX": [
|
||||
[
|
||||
"U1",
|
||||
"10"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"5"
|
||||
]
|
||||
],
|
||||
"RS_VALID": [
|
||||
[
|
||||
"U1",
|
||||
"12"
|
||||
],
|
||||
[
|
||||
"U2",
|
||||
"11"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 152 KiB |
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T23:07:29",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning",
|
||||
"exclusion"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [
|
||||
{
|
||||
"description": "Carrier:MAX3243_Reference_Provisional stimmt nicht mit dem Footprint des Symbols () überein.",
|
||||
"items": [
|
||||
{
|
||||
"description": "Footprint U2",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "812387f6-6524-459e-8102-fe9c55122668"
|
||||
}
|
||||
],
|
||||
"severity": "warning",
|
||||
"type": "footprint_symbol_mismatch"
|
||||
}
|
||||
],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [],
|
||||
"violations": []
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T22:50:33",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning",
|
||||
"exclusion"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [],
|
||||
"violations": []
|
||||
}
|
||||
@@ -1,911 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T22:39:18",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 19 [/BTN_SELECT] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 149.72
|
||||
},
|
||||
"uuid": "f7b9badd-1db5-4575-9da8-979a7c8f3946"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_SELECT] von SW2",
|
||||
"pos": {
|
||||
"x": 155.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "574d93e4-d3fc-4c28-b2d5-4e7d8682c22f"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_SELECT] von SW2",
|
||||
"pos": {
|
||||
"x": 155.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "574d93e4-d3fc-4c28-b2d5-4e7d8682c22f"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_SELECT] von SW2",
|
||||
"pos": {
|
||||
"x": 161.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "0398530b-79dc-45b1-886c-51343182e1d3"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_SELECT] von SW2",
|
||||
"pos": {
|
||||
"x": 161.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "0398530b-79dc-45b1-886c-51343182e1d3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/BTN_SELECT] von R2",
|
||||
"pos": {
|
||||
"x": 161.62,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "c118f27f-8ad4-4abe-b9c0-a30a22fe60c9"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 22 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 157.34
|
||||
},
|
||||
"uuid": "9d509ec2-7588-4c77-9146-8d03c8646e37"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 44 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 157.34
|
||||
},
|
||||
"uuid": "91b9e8e9-7e2c-43ab-b2ef-ec8ba16a25f1"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 23 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 104.0
|
||||
},
|
||||
"uuid": "b66490f6-5d60-415d-babb-215c70511b77"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von U2",
|
||||
"pos": {
|
||||
"x": 145.54,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "7744910d-b37b-4fd5-a46a-95ee262d2a08"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 43 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 154.8
|
||||
},
|
||||
"uuid": "23b353d7-7ede-43a0-a655-57496d4f8aee"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/GND] von DS1",
|
||||
"pos": {
|
||||
"x": 152.19,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "0d95ad6d-f278-4240-9618-08ad8fcb5e1c"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 44 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 157.34
|
||||
},
|
||||
"uuid": "91b9e8e9-7e2c-43ab-b2ef-ec8ba16a25f1"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 43 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 154.8
|
||||
},
|
||||
"uuid": "23b353d7-7ede-43a0-a655-57496d4f8aee"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "cc9a376c-9410-4f75-a7a6-3a849cc384b3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW1",
|
||||
"pos": {
|
||||
"x": 149.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "24c82a41-4f9f-4c94-aaaf-2a3df71bbd08"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "cc9a376c-9410-4f75-a7a6-3a849cc384b3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 44 [/GND] von U1",
|
||||
"pos": {
|
||||
"x": 133.4,
|
||||
"y": 157.34
|
||||
},
|
||||
"uuid": "91b9e8e9-7e2c-43ab-b2ef-ec8ba16a25f1"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW1",
|
||||
"pos": {
|
||||
"x": 149.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "24c82a41-4f9f-4c94-aaaf-2a3df71bbd08"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW2",
|
||||
"pos": {
|
||||
"x": 155.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "970e56ae-cc93-46cd-8807-ccc9cf082aa1"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/GND] von DS1",
|
||||
"pos": {
|
||||
"x": 152.19,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "0d95ad6d-f278-4240-9618-08ad8fcb5e1c"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von U2",
|
||||
"pos": {
|
||||
"x": 145.54,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "7744910d-b37b-4fd5-a46a-95ee262d2a08"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW2",
|
||||
"pos": {
|
||||
"x": 155.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "970e56ae-cc93-46cd-8807-ccc9cf082aa1"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW2",
|
||||
"pos": {
|
||||
"x": 161.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "2f44f580-c7e4-4f03-ba46-49605735875c"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW2",
|
||||
"pos": {
|
||||
"x": 161.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "2f44f580-c7e4-4f03-ba46-49605735875c"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW3",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "3cdbd911-6e07-4f86-8887-5953fa3d2a01"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW3",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "3cdbd911-6e07-4f86-8887-5953fa3d2a01"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/GND] von SW3",
|
||||
"pos": {
|
||||
"x": 173.5,
|
||||
"y": 172.5
|
||||
},
|
||||
"uuid": "8e780381-cbfb-4f62-ae3b-8b0b8fa2a6a8"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 104.0
|
||||
},
|
||||
"uuid": "9aaad355-039f-439b-9cd7-308d07ace02e"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/+3V3] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 106.54
|
||||
},
|
||||
"uuid": "2c8cc7c5-7b73-4bc2-a384-b3a5ac459c92"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von U2",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "f8aafd85-e635-47cd-ba22-ddf4a036d1cd"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "56ff90bc-b894-4f81-8274-7a34384229e7"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von U2",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "f8aafd85-e635-47cd-ba22-ddf4a036d1cd"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/+3V3] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 106.54
|
||||
},
|
||||
"uuid": "2c8cc7c5-7b73-4bc2-a384-b3a5ac459c92"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R2",
|
||||
"pos": {
|
||||
"x": 154.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "73a249ed-c3b1-46fb-afd5-f8bfe0e3fa65"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "56ff90bc-b894-4f81-8274-7a34384229e7"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/+3V3] von DS1",
|
||||
"pos": {
|
||||
"x": 154.73,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "62a98206-7a36-4b20-a201-d4ca10b404df"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R2",
|
||||
"pos": {
|
||||
"x": 154.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "73a249ed-c3b1-46fb-afd5-f8bfe0e3fa65"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/+3V3] von DS1",
|
||||
"pos": {
|
||||
"x": 154.73,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "62a98206-7a36-4b20-a201-d4ca10b404df"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/+3V3] von R3",
|
||||
"pos": {
|
||||
"x": 165.0,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "72b8a1ea-1fa7-480b-8122-aceb08f4bb4b"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/RS_DCD] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 111.62
|
||||
},
|
||||
"uuid": "d9f0d2af-a701-43e9-9d1e-58a6c7cb5250"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 3 [/RS_DCD] von U2",
|
||||
"pos": {
|
||||
"x": 148.08,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "d589a6a4-0d53-4c2a-8f68-fb4407d9f10e"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_DSR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 114.16
|
||||
},
|
||||
"uuid": "0ceca603-53a8-4a53-85cc-f7f8e7be90e4"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DSR] von U2",
|
||||
"pos": {
|
||||
"x": 158.24,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e2d6b2cb-6025-490c-8371-d7cc34ac37f6"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_RI] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 116.7
|
||||
},
|
||||
"uuid": "f35f1696-b778-437b-8cce-67b8c4d5f5d0"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_RI] von U2",
|
||||
"pos": {
|
||||
"x": 165.86,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e28de6f2-fb85-4849-ba1a-e98e2a231492"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DTR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 119.24
|
||||
},
|
||||
"uuid": "354ab122-85b1-4c9c-9385-d5091fe12303"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_DTR] von U2",
|
||||
"pos": {
|
||||
"x": 155.7,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "81e8ad67-4bdc-4f57-90b1-68cff6a95261"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 121.78
|
||||
},
|
||||
"uuid": "55162730-46fd-465d-a1c5-20aed09ed534"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U2",
|
||||
"pos": {
|
||||
"x": 160.78,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "5c304724-2bc6-409d-9c59-2e40adc557b9"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 124.32
|
||||
},
|
||||
"uuid": "f75c20f8-d3b5-4c3b-9919-cb044b2da5c3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U2",
|
||||
"pos": {
|
||||
"x": 163.32,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a8411036-6859-4041-84b8-2c80c326c58e"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_TX] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 126.86
|
||||
},
|
||||
"uuid": "22c323db-7146-44af-951b-04a1d7dbb2fe"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_TX] von U2",
|
||||
"pos": {
|
||||
"x": 153.16,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "f919098b-4ad7-4600-9e8b-3375a1cff050"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_RX] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 129.4
|
||||
},
|
||||
"uuid": "6573852d-5714-4c8d-8793-0be87f5a052a"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/RS_RX] von U2",
|
||||
"pos": {
|
||||
"x": 150.62,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "3d90cce4-14d8-46a0-b560-09524f11698f"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_VALID] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 131.94
|
||||
},
|
||||
"uuid": "2fbc596b-c88f-4fc6-aa94-5dad4fcac9cc"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_VALID] von U2",
|
||||
"pos": {
|
||||
"x": 168.4,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "9ad42f44-160f-4aa5-bcf0-a4466c090438"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 15 [/RS_OFF_N] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 139.56
|
||||
},
|
||||
"uuid": "34e1afe9-b3fc-4d3a-9e33-38b32244f19e"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_OFF_N] von U2",
|
||||
"pos": {
|
||||
"x": 170.94,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a43ba2f2-477d-4850-87de-e1591119d5ae"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_PREVIOUS] von SW1",
|
||||
"pos": {
|
||||
"x": 143.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "5214c011-545b-4ae7-9cdb-852ade815805"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_PREVIOUS] von SW1",
|
||||
"pos": {
|
||||
"x": 149.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "84202d30-b2fe-4170-8146-69b68d3b82a1"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_PREVIOUS] von SW1",
|
||||
"pos": {
|
||||
"x": 149.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "84202d30-b2fe-4170-8146-69b68d3b82a1"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/BTN_PREVIOUS] von R1",
|
||||
"pos": {
|
||||
"x": 150.62,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "8237f507-f5c7-4eb6-9aa1-f46af2981e7c"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/BTN_PREVIOUS] von R1",
|
||||
"pos": {
|
||||
"x": 150.62,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "8237f507-f5c7-4eb6-9aa1-f46af2981e7c"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 16 [/BTN_PREVIOUS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 142.1
|
||||
},
|
||||
"uuid": "fc705960-92fa-4d8f-bb0e-ce2b82d1e7fc"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 17 [/OLED_SDA] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 144.64
|
||||
},
|
||||
"uuid": "1f91549c-5cb2-465f-86df-b2f1b0c16ed8"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/OLED_SDA] von DS1",
|
||||
"pos": {
|
||||
"x": 159.81,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "91a4dcef-1b36-4e3d-a15c-c4578077c27b"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 18 [/OLED_SCL] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 147.18
|
||||
},
|
||||
"uuid": "1c55fd03-0b3e-494f-a37b-6e0b3496cfc9"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 3 [/OLED_SCL] von DS1",
|
||||
"pos": {
|
||||
"x": 157.27,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "704668d0-88bf-4b51-840a-60a35804fafd"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 20 [/BTN_NEXT] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 152.26
|
||||
},
|
||||
"uuid": "e7159c90-4e8d-4b21-8e84-a7f59f793440"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_NEXT] von SW3",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "c67d0b07-c47f-48b3-9e79-7aed82edbbfc"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_NEXT] von SW3",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "c67d0b07-c47f-48b3-9e79-7aed82edbbfc"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_NEXT] von SW3",
|
||||
"pos": {
|
||||
"x": 173.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "62f3da3b-8297-4232-92ee-88f3ce4a7d36"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 1 [/BTN_NEXT] von SW3",
|
||||
"pos": {
|
||||
"x": 173.5,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "62f3da3b-8297-4232-92ee-88f3ce4a7d36"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 2 [/BTN_NEXT] von R3",
|
||||
"pos": {
|
||||
"x": 172.62,
|
||||
"y": 134.0
|
||||
},
|
||||
"uuid": "9e9dfe49-4b68-472b-a86c-bfcee913a81b"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
}
|
||||
],
|
||||
"violations": []
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"immutable_geometry_nets_pads_linkage_preserved": true,
|
||||
"board_sha256": "03b69c4084dba328b11e2373c219e8fe462bcb3add934cf9f527214b21bd9cb6",
|
||||
"independent_min_different_net_or_npth_clearance_mm": 0.2749999999999886,
|
||||
"closest_items": [
|
||||
"R2.2:c118f27f-8ad4-4abe-b9c0-a30a22fe60c9",
|
||||
"99fb402a-445b-4eba-aedf-76b7f0cad042"
|
||||
],
|
||||
"min_copper_or_npth_edge_gap_mm": 0.5250000000000057,
|
||||
"min_rf_gap_mm": 0.025000000000005684,
|
||||
"min_via_to_other_drilled_hole_gap_mm": 0.8401754250991242,
|
||||
"track_width_counts": {
|
||||
"0.25": 425,
|
||||
"0.5": 144
|
||||
},
|
||||
"routed_length_mm_by_net": {
|
||||
"/BTN_SELECT": 95.16152954766495,
|
||||
"/GND": 152.2615364335217,
|
||||
"/+3V3": 106.94219089103528,
|
||||
"/RS_DCD": 47.76152984055992,
|
||||
"/RS_DSR": 56.835284377834164,
|
||||
"/RS_RI": 66.02000635033798,
|
||||
"/RS_DTR": 52.162796381301995,
|
||||
"/RS_RTS": 56.23655121147421,
|
||||
"/RS_CTS": 58.073809511675655,
|
||||
"/RS_TX": 47.51030692030297,
|
||||
"/RS_RX": 43.90406233625865,
|
||||
"/RS_VALID": 64.36573922778403,
|
||||
"/RS_OFF_N": 67.05000417516253,
|
||||
"/BTN_PREVIOUS": 86.95076506672746,
|
||||
"/OLED_SDA": 54.781626385188304,
|
||||
"/OLED_SCL": 53.30787155502609,
|
||||
"/BTN_NEXT": 106.4266552277327
|
||||
},
|
||||
"tracks": 569,
|
||||
"vias": 20,
|
||||
"copper_zones": 0,
|
||||
"drc_freshly_run": true,
|
||||
"drc_violations": 0,
|
||||
"unconnected_items": 0,
|
||||
"drc_ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"overwrite_guard_tests": [
|
||||
{
|
||||
"args": [
|
||||
"--route"
|
||||
],
|
||||
"exit_code": 2,
|
||||
"reason": "route_pcb_draft.py: error: --route requires --overwrite; back up manual routing first"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"--route",
|
||||
"--overwrite"
|
||||
],
|
||||
"exit_code": 2,
|
||||
"reason": "route_pcb_draft.py: error: existing routing protected; --replace-routes required even for preview"
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"exit_code": 2,
|
||||
"reason": "route_pcb_draft.py: error: existing routing protected; --replace-routes required even for preview"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
"""Read-only draft audit; needs pcbnew, numpy, shapely and kicad-cli.
|
||||
Writes fresh DRC and audit JSON reports, never modifies the board.
|
||||
|
||||
Independent continuous centreline/radius checks complement KiCad DRC. Restricted
|
||||
here to the existing circle/axis-aligned rectangular THT pads and straight tracks.
|
||||
"""
|
||||
from collections import Counter
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
import pcbnew as p
|
||||
from shapely.geometry import Point, LineString, Polygon, box
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
spec = importlib.util.spec_from_file_location('router', ROOT / 'route_pcb_draft.py')
|
||||
r = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(r)
|
||||
board = p.LoadBoard(str(r.PCB))
|
||||
original = p.LoadBoard(str(ROOT / 'validation/pcb-routing-input.kicad_pcb'))
|
||||
assert r.identity(board) == r.identity(original), 'immutable source geometry/linkage changed'
|
||||
items, holes = [], []
|
||||
for f in board.GetFootprints():
|
||||
for pad in f.Pads():
|
||||
xy = r.pos(pad)
|
||||
assert pad.GetOrientationDegrees() % 90 == 0
|
||||
assert pad.GetShape() in (p.PAD_SHAPE_CIRCLE, p.PAD_SHAPE_RECT)
|
||||
if pad.GetShape() == p.PAD_SHAPE_CIRCLE:
|
||||
assert pad.GetSize().x == pad.GetSize().y
|
||||
geom, radius = Point(xy), p.ToMM(pad.GetSize().x) / 2
|
||||
else:
|
||||
bb = pad.GetBoundingBox()
|
||||
geom = box(p.ToMM(bb.GetX()), p.ToMM(bb.GetY()),
|
||||
p.ToMM(bb.GetRight()), p.ToMM(bb.GetBottom()))
|
||||
radius = 0
|
||||
label = f.GetReference() + '.' + pad.GetNumber() + ':' + pad.m_Uuid.AsString()
|
||||
layers = {l for l in r.LAYERS if pad.IsOnLayer(l)}
|
||||
items.append((label, pad.GetNetCode(), layers, geom, radius))
|
||||
if pad.GetDrillSize().x:
|
||||
assert pad.GetDrillSize().x == pad.GetDrillSize().y
|
||||
holes.append((label, Point(xy), p.ToMM(pad.GetDrillSize().x) / 2, False))
|
||||
track_lengths = Counter()
|
||||
widths = Counter()
|
||||
for t in board.GetTracks():
|
||||
is_via = isinstance(t, p.PCB_VIA)
|
||||
if is_via:
|
||||
geom, radius, layers = Point(r.pos(t)), p.ToMM(t.GetWidth(p.F_Cu)) / 2, set(r.LAYERS)
|
||||
assert abs(radius - 0.35) < 1e-8 and t.GetDrillValue() == r.mm(0.3)
|
||||
holes.append((t.m_Uuid.AsString(), geom, 0.15, True))
|
||||
else:
|
||||
geom = LineString([(p.ToMM(t.GetStart().x), p.ToMM(t.GetStart().y)),
|
||||
(p.ToMM(t.GetEnd().x), p.ToMM(t.GetEnd().y))])
|
||||
radius, layers = p.ToMM(t.GetWidth()) / 2, {t.GetLayer()}
|
||||
expected = 0.5 if t.GetNetname() in ('/+3V3', '/GND') else 0.25
|
||||
assert abs(2 * radius - expected) < 1e-8
|
||||
widths[str(2 * radius)] += 1
|
||||
track_lengths[t.GetNetname()] += geom.length
|
||||
items.append((t.m_Uuid.AsString(), t.GetNetCode(), layers, geom, radius))
|
||||
|
||||
min_clearance = float('inf')
|
||||
closest = None
|
||||
for i, a in enumerate(items):
|
||||
for b in items[i + 1:]:
|
||||
if not a[2].intersection(b[2]) or (a[1] and a[1] == b[1]):
|
||||
continue
|
||||
distance = a[3].distance(b[3]) - a[4] - b[4]
|
||||
if distance < min_clearance:
|
||||
min_clearance, closest = distance, (a[0], b[0])
|
||||
assert min_clearance >= 0.25 - 1e-6, (min_clearance, closest)
|
||||
outline = Polygon([(100,100),(182,100),(182,180),(136,180),(136,161),(100,161)])
|
||||
rf = box(110, 100, 131, 113)
|
||||
min_edge, min_rf = float('inf'), float('inf')
|
||||
for label, net, layers, geom, radius in items:
|
||||
assert outline.covers(geom), label
|
||||
edge_gap = outline.boundary.distance(geom) - radius
|
||||
rf_gap = rf.distance(geom) - radius
|
||||
min_edge, min_rf = min(min_edge, edge_gap), min(min_rf, rf_gap)
|
||||
assert edge_gap >= 0.5 - 1e-6, (label, edge_gap)
|
||||
assert rf_gap > 0, (label, rf_gap)
|
||||
min_via_hole_gap = float('inf')
|
||||
for i, a in enumerate(holes):
|
||||
for b in holes[i + 1:]:
|
||||
if not (a[3] or b[3]):
|
||||
continue
|
||||
gap = a[1].distance(b[1]) - a[2] - b[2]
|
||||
min_via_hole_gap = min(min_via_hole_gap, gap)
|
||||
assert gap >= 0.25 - 1e-6, (a[0], b[0], gap)
|
||||
|
||||
# Refusal paths must leave the routed board byte-for-byte unchanged.
|
||||
before = r.PCB.read_bytes()
|
||||
guards = []
|
||||
for args in (['--route'], ['--route', '--overwrite'], []):
|
||||
result = subprocess.run(['/usr/bin/python3', '-B', str(ROOT/'route_pcb_draft.py'), *args],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
assert result.returncode == 2, (args, result.stdout, result.stderr)
|
||||
assert r.PCB.read_bytes() == before
|
||||
guards.append({'args': args, 'exit_code': result.returncode,
|
||||
'reason': result.stderr.splitlines()[-1]})
|
||||
subprocess.run(['kicad-cli', 'pcb', 'drc', '--format', 'json', '--all-track-errors',
|
||||
'--severity-all', '--exit-code-violations', '-o',
|
||||
str(ROOT/'validation/pcb-routing-drc.json'), str(r.PCB)],
|
||||
check=True, timeout=60)
|
||||
assert r.PCB.read_bytes() == before, 'board changed during audit'
|
||||
drc = json.loads(ROOT.joinpath('validation/pcb-routing-drc.json').read_text())
|
||||
assert not drc['violations'] and not drc['unconnected_items']
|
||||
report = dict(immutable_geometry_nets_pads_linkage_preserved=True,
|
||||
board_sha256=hashlib.sha256(before).hexdigest(),
|
||||
independent_min_different_net_or_npth_clearance_mm=min_clearance,
|
||||
closest_items=closest, min_copper_or_npth_edge_gap_mm=min_edge,
|
||||
min_rf_gap_mm=min_rf, min_via_to_other_drilled_hole_gap_mm=min_via_hole_gap,
|
||||
track_width_counts=widths, routed_length_mm_by_net=track_lengths,
|
||||
tracks=sum(not isinstance(t,p.PCB_VIA) for t in board.GetTracks()),
|
||||
vias=sum(isinstance(t,p.PCB_VIA) for t in board.GetTracks()),
|
||||
copper_zones=sum(not z.GetIsRuleArea() for z in board.Zones()),
|
||||
drc_freshly_run=True,
|
||||
drc_violations=len(drc['violations']), unconnected_items=len(drc['unconnected_items']),
|
||||
drc_ignored_checks=drc['ignored_checks'], overwrite_guard_tests=guards)
|
||||
ROOT.joinpath('validation/pcb-routing-check.json').write_text(json.dumps(report, indent=2)+'\n')
|
||||
print(json.dumps(report, indent=2))
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T23:07:33",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning",
|
||||
"exclusion"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [],
|
||||
"violations": []
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
{"net": "/+3V3", "start": "U1.1", "end": "U1.2", "start_mm": [108.0, 104.0], "end_mm": [108.0, 106.54], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/+3V3", "start": "R2.1", "end": "DS1.2", "start_mm": [154.0, 134.0], "end_mm": [154.73, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 911}
|
||||
{"net": "/+3V3", "start": "U2.1", "end": "R1.1", "start_mm": [143.0, 130.0], "end_mm": [143.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 81}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "SW3.1", "start_mm": [167.0, 168.0], "end_mm": [173.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "SW1.1", "start_mm": [149.5, 168.0], "end_mm": [143.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "SW2.1", "start_mm": [155.0, 168.0], "end_mm": [161.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/+3V3", "start": "DS1.2", "end": "R3.1", "start_mm": [154.73, 137.5], "end_mm": [165.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 5139}
|
||||
{"net": "/+3V3", "start": "R1.1", "end": "R2.1", "start_mm": [143.0, 134.0], "end_mm": [154.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 14153}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "R2.2", "start_mm": [161.5, 168.0], "end_mm": [161.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 2039}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "R3.2", "start_mm": [173.5, 168.0], "end_mm": [172.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 12615}
|
||||
{"net": "/BTN_PREVIOUS", "start": "R1.2", "end": "SW1.1", "start_mm": [150.62, 134.0], "end_mm": [149.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 15179}
|
||||
{"net": "/+3V3", "start": "U1.2", "end": "U2.1", "start_mm": [108.0, 106.54], "end_mm": [143.0, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 894511}
|
||||
{"net": "/RS_RX", "start": "U1.11", "end": "U2.4", "start_mm": [108.0, 129.4], "end_mm": [150.62, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 51516}
|
||||
{"net": "/BTN_PREVIOUS", "start": "U1.16", "end": "R1.2", "start_mm": [108.0, 142.1], "end_mm": [150.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 94479}
|
||||
{"net": "/RS_DCD", "start": "U1.4", "end": "U2.3", "start_mm": [108.0, 111.62], "end_mm": [148.08, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 266031}
|
||||
{"net": "/RS_TX", "start": "U1.10", "end": "U2.5", "start_mm": [108.0, 126.86], "end_mm": [153.16, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 175689}
|
||||
{"net": "/RS_DTR", "start": "U1.7", "end": "U2.6", "start_mm": [108.0, 119.24], "end_mm": [155.7, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 326823}
|
||||
{"net": "/OLED_SCL", "start": "U1.18", "end": "DS1.3", "start_mm": [108.0, 147.18], "end_mm": [157.27, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 82557}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "U1.19", "start_mm": [155.0, 168.0], "end_mm": [108.0, 149.72], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 147997}
|
||||
{"net": "/OLED_SDA", "start": "U1.17", "end": "DS1.4", "start_mm": [108.0, 144.64], "end_mm": [159.81, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 266428}
|
||||
{"net": "/RS_DSR", "start": "U1.5", "end": "U2.7", "start_mm": [108.0, 114.16], "end_mm": [158.24, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 372014}
|
||||
{"net": "/RS_RTS", "start": "U1.8", "end": "U2.8", "start_mm": [108.0, 121.78], "end_mm": [160.78, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 298514}
|
||||
{"net": "/RS_CTS", "start": "U1.9", "end": "U2.9", "start_mm": [108.0, 124.32], "end_mm": [163.32, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 265272}
|
||||
{"net": "/RS_RI", "start": "U1.6", "end": "U2.10", "start_mm": [108.0, 116.7], "end_mm": [165.86, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 332506}
|
||||
{"net": "/RS_VALID", "start": "U1.12", "end": "U2.11", "start_mm": [108.0, 131.94], "end_mm": [168.4, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 348384}
|
||||
{"net": "/BTN_NEXT", "start": "U1.20", "end": "SW3.1", "start_mm": [108.0, 152.26], "end_mm": [167.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 272795}
|
||||
{"net": "/RS_OFF_N", "start": "U1.15", "end": "U2.12", "start_mm": [108.0, 139.56], "end_mm": [170.94, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 351630}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.43", "start_mm": [133.4, 157.34], "end_mm": [133.4, 154.8], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW1.2", "start_mm": [155.0, 172.5], "end_mm": [149.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW3.2", "start_mm": [161.5, 172.5], "end_mm": [167.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW2.2", "start_mm": [155.0, 172.5], "end_mm": [161.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW3.2", "end": "SW3.2", "start_mm": [167.0, 172.5], "end_mm": [173.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "SW1.2", "start_mm": [149.5, 172.5], "end_mm": [143.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "DS1.1", "end": "U2.2", "start_mm": [152.19, 137.5], "end_mm": [145.54, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 17468}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "U1.44", "start_mm": [143.0, 172.5], "end_mm": [133.4, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 27488}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.22", "start_mm": [133.4, 157.34], "end_mm": [108.0, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 73753}
|
||||
{"net": "/GND", "start": "U1.43", "end": "DS1.1", "start_mm": [133.4, 154.8], "end_mm": [152.19, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 54021}
|
||||
{"net": "/GND", "start": "U2.2", "end": "U1.23", "start_mm": [145.54, 130.0], "end_mm": [133.4, 104.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 98250}
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": false,
|
||||
"grid_mm": 0.05,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.02,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 27.992941715005145,
|
||||
"routed_tree_edges": 38,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 569,
|
||||
"vias": 20,
|
||||
"immutable_identity_sha256": "81def52e6c1d574b4d3ab1a5859a5266b893df0ea6c5b9aa7d7b688cc4a98519",
|
||||
"input_sha256": "bf94b6c9b00c43a97f933054758a2c9d6541981ae09f2f285dcdb567bd73d9b1",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T22:43:07",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_DSR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 114.16
|
||||
},
|
||||
"uuid": "0ceca603-53a8-4a53-85cc-f7f8e7be90e4"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DSR] von U2",
|
||||
"pos": {
|
||||
"x": 158.24,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e2d6b2cb-6025-490c-8371-d7cc34ac37f6"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_RI] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 116.7
|
||||
},
|
||||
"uuid": "f35f1696-b778-437b-8cce-67b8c4d5f5d0"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_RI] von U2",
|
||||
"pos": {
|
||||
"x": 165.86,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e28de6f2-fb85-4849-ba1a-e98e2a231492"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DTR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 119.24
|
||||
},
|
||||
"uuid": "354ab122-85b1-4c9c-9385-d5091fe12303"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_DTR] von U2",
|
||||
"pos": {
|
||||
"x": 155.7,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "81e8ad67-4bdc-4f57-90b1-68cff6a95261"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 121.78
|
||||
},
|
||||
"uuid": "55162730-46fd-465d-a1c5-20aed09ed534"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U2",
|
||||
"pos": {
|
||||
"x": 160.78,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "5c304724-2bc6-409d-9c59-2e40adc557b9"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 124.32
|
||||
},
|
||||
"uuid": "f75c20f8-d3b5-4c3b-9919-cb044b2da5c3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U2",
|
||||
"pos": {
|
||||
"x": 163.32,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a8411036-6859-4041-84b8-2c80c326c58e"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_TX] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 126.86
|
||||
},
|
||||
"uuid": "22c323db-7146-44af-951b-04a1d7dbb2fe"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_TX] von U2",
|
||||
"pos": {
|
||||
"x": 153.16,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "f919098b-4ad7-4600-9e8b-3375a1cff050"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_RX] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 129.4
|
||||
},
|
||||
"uuid": "6573852d-5714-4c8d-8793-0be87f5a052a"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/RS_RX] von U2",
|
||||
"pos": {
|
||||
"x": 150.62,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "3d90cce4-14d8-46a0-b560-09524f11698f"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_VALID] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 131.94
|
||||
},
|
||||
"uuid": "2fbc596b-c88f-4fc6-aa94-5dad4fcac9cc"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_VALID] von U2",
|
||||
"pos": {
|
||||
"x": 168.4,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "9ad42f44-160f-4aa5-bcf0-a4466c090438"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 15 [/RS_OFF_N] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 139.56
|
||||
},
|
||||
"uuid": "34e1afe9-b3fc-4d3a-9e33-38b32244f19e"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_OFF_N] von U2",
|
||||
"pos": {
|
||||
"x": 170.94,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a43ba2f2-477d-4850-87de-e1591119d5ae"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
}
|
||||
],
|
||||
"violations": []
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schemas.kicad.org/drc.v1.json",
|
||||
"coordinate_units": "mm",
|
||||
"date": "2026-09-20T22:46:03",
|
||||
"ignored_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"included_severities": [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
"kicad_version": "10.0.6",
|
||||
"schematic_parity": [],
|
||||
"source": "serial-carrier.kicad_pcb",
|
||||
"unconnected_items": [
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 5 [/RS_DSR] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 114.16
|
||||
},
|
||||
"uuid": "0ceca603-53a8-4a53-85cc-f7f8e7be90e4"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 7 [/RS_DSR] von U2",
|
||||
"pos": {
|
||||
"x": 158.24,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e2d6b2cb-6025-490c-8371-d7cc34ac37f6"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 6 [/RS_RI] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 116.7
|
||||
},
|
||||
"uuid": "f35f1696-b778-437b-8cce-67b8c4d5f5d0"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 10 [/RS_RI] von U2",
|
||||
"pos": {
|
||||
"x": 165.86,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "e28de6f2-fb85-4849-ba1a-e98e2a231492"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 121.78
|
||||
},
|
||||
"uuid": "55162730-46fd-465d-a1c5-20aed09ed534"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 8 [/RS_RTS] von U2",
|
||||
"pos": {
|
||||
"x": 160.78,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "5c304724-2bc6-409d-9c59-2e40adc557b9"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 124.32
|
||||
},
|
||||
"uuid": "f75c20f8-d3b5-4c3b-9919-cb044b2da5c3"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 9 [/RS_CTS] von U2",
|
||||
"pos": {
|
||||
"x": 163.32,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a8411036-6859-4041-84b8-2c80c326c58e"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_VALID] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 131.94
|
||||
},
|
||||
"uuid": "2fbc596b-c88f-4fc6-aa94-5dad4fcac9cc"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 11 [/RS_VALID] von U2",
|
||||
"pos": {
|
||||
"x": 168.4,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "9ad42f44-160f-4aa5-bcf0-a4466c090438"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 15 [/RS_OFF_N] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 139.56
|
||||
},
|
||||
"uuid": "34e1afe9-b3fc-4d3a-9e33-38b32244f19e"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 12 [/RS_OFF_N] von U2",
|
||||
"pos": {
|
||||
"x": 170.94,
|
||||
"y": 130.0
|
||||
},
|
||||
"uuid": "a43ba2f2-477d-4850-87de-e1591119d5ae"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 17 [/OLED_SDA] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 144.64
|
||||
},
|
||||
"uuid": "1f91549c-5cb2-465f-86df-b2f1b0c16ed8"
|
||||
},
|
||||
{
|
||||
"description": "Durchsteckpad 4 [/OLED_SDA] von DS1",
|
||||
"pos": {
|
||||
"x": 159.81,
|
||||
"y": 137.5
|
||||
},
|
||||
"uuid": "91a4dcef-1b36-4e3d-a15c-c4578077c27b"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
},
|
||||
{
|
||||
"description": "Missing connection between items",
|
||||
"items": [
|
||||
{
|
||||
"description": "Durchsteckpad 20 [/BTN_NEXT] von U1",
|
||||
"pos": {
|
||||
"x": 108.0,
|
||||
"y": 152.26
|
||||
},
|
||||
"uuid": "e7159c90-4e8d-4b21-8e84-a7f59f793440"
|
||||
},
|
||||
{
|
||||
"description": "Leiterbahn [/BTN_NEXT] auf F.Cu, Länge 6,5000 mm",
|
||||
"pos": {
|
||||
"x": 167.0,
|
||||
"y": 168.0
|
||||
},
|
||||
"uuid": "97cfdf4d-f068-4e4a-8684-aae638a809ad"
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"type": "unconnected_items"
|
||||
}
|
||||
],
|
||||
"violations": []
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
{"net": "/+3V3", "start": "U1.1", "end": "U1.2", "start_mm": [108.0, 104.0], "end_mm": [108.0, 106.54], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 26}
|
||||
{"net": "/+3V3", "start": "R2.1", "end": "DS1.2", "start_mm": [154.0, 134.0], "end_mm": [154.73, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 239}
|
||||
{"net": "/+3V3", "start": "U2.1", "end": "R1.1", "start_mm": [143.0, 130.0], "end_mm": [143.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 41}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "SW3.1", "start_mm": [167.0, 168.0], "end_mm": [173.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "SW1.1", "start_mm": [149.5, 168.0], "end_mm": [143.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "SW2.1", "start_mm": [155.0, 168.0], "end_mm": [161.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/+3V3", "start": "DS1.2", "end": "R3.1", "start_mm": [154.73, 137.5], "end_mm": [165.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 1334}
|
||||
{"net": "/+3V3", "start": "R1.1", "end": "R2.1", "start_mm": [143.0, 134.0], "end_mm": [154.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 3711}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "R2.2", "start_mm": [161.5, 168.0], "end_mm": [161.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 681}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "R3.2", "start_mm": [173.5, 168.0], "end_mm": [172.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 3329}
|
||||
{"net": "/BTN_PREVIOUS", "start": "R1.2", "end": "SW1.1", "start_mm": [150.62, 134.0], "end_mm": [149.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 3971}
|
||||
{"net": "/+3V3", "start": "U1.2", "end": "U2.1", "start_mm": [108.0, 106.54], "end_mm": [143.0, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 224476}
|
||||
{"net": "/RS_RX", "start": "U1.11", "end": "U2.4", "start_mm": [108.0, 129.4], "end_mm": [150.62, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 274662}
|
||||
{"net": "/BTN_PREVIOUS", "start": "U1.16", "end": "R1.2", "start_mm": [108.0, 142.1], "end_mm": [150.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 227867}
|
||||
{"net": "/RS_DCD", "start": "U1.4", "end": "U2.3", "start_mm": [108.0, 111.62], "end_mm": [148.08, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 201896}
|
||||
{"net": "/RS_TX", "start": "U1.10", "end": "U2.5", "start_mm": [108.0, 126.86], "end_mm": [153.16, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 271117}
|
||||
{"net": "/RS_DTR", "start": "U1.7", "end": "U2.6", "start_mm": [108.0, 119.24], "end_mm": [155.7, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 307013}
|
||||
{"net": "/OLED_SCL", "start": "U1.18", "end": "DS1.3", "start_mm": [108.0, 147.18], "end_mm": [157.27, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 127246}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "U1.19", "start_mm": [155.0, 168.0], "end_mm": [108.0, 149.72], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 73189}
|
||||
{"net": "/OLED_SDA", "start": "U1.17", "end": "DS1.4", "start_mm": [108.0, 144.64], "end_mm": [159.81, 137.5], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272010}
|
||||
{"net": "/RS_DSR", "start": "U1.5", "end": "U2.7", "start_mm": [108.0, 114.16], "end_mm": [158.24, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272046}
|
||||
{"net": "/RS_RTS", "start": "U1.8", "end": "U2.8", "start_mm": [108.0, 121.78], "end_mm": [160.78, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272010}
|
||||
{"net": "/RS_CTS", "start": "U1.9", "end": "U2.9", "start_mm": [108.0, 124.32], "end_mm": [163.32, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272000}
|
||||
{"net": "/RS_RI", "start": "U1.6", "end": "U2.10", "start_mm": [108.0, 116.7], "end_mm": [165.86, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272008}
|
||||
{"net": "/RS_VALID", "start": "U1.12", "end": "U2.11", "start_mm": [108.0, 131.94], "end_mm": [168.4, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272015}
|
||||
{"net": "/BTN_NEXT", "start": "U1.20", "end": "SW3.1", "start_mm": [108.0, 152.26], "end_mm": [167.0, 168.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272010}
|
||||
{"net": "/RS_OFF_N", "start": "U1.15", "end": "U2.12", "start_mm": [108.0, 139.56], "end_mm": [170.94, 130.0], "routed": false, "width_mm": 0.25, "power_fallback": false, "expansions": 272010}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.43", "start_mm": [133.4, 157.34], "end_mm": [133.4, 154.8], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 26}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW1.2", "start_mm": [155.0, 172.5], "end_mm": [149.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 56}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW3.2", "start_mm": [161.5, 172.5], "end_mm": [167.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 56}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW2.2", "start_mm": [155.0, 172.5], "end_mm": [161.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/GND", "start": "SW3.2", "end": "SW3.2", "start_mm": [167.0, 172.5], "end_mm": [173.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "SW1.2", "start_mm": [149.5, 172.5], "end_mm": [143.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 66}
|
||||
{"net": "/GND", "start": "DS1.1", "end": "U2.2", "start_mm": [152.19, 137.5], "end_mm": [145.54, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 3234}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "U1.44", "start_mm": [143.0, 172.5], "end_mm": [133.4, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 29110}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.22", "start_mm": [133.4, 157.34], "end_mm": [108.0, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 16915}
|
||||
{"net": "/GND", "start": "U1.43", "end": "DS1.1", "start_mm": [133.4, 154.8], "end_mm": [152.19, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 57470}
|
||||
{"net": "/GND", "start": "U2.2", "end": "U1.23", "start_mm": [145.54, 130.0], "end_mm": [133.4, 104.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 34158}
|
||||
22:45:50: Debug: Adding duplicate image handler for 'PNG file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'JPEG file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'TIFF file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'GIF file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'PNM file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'PCX file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'IFF file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'Windows icon file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'Windows cursor file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'Windows animated cursor file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'TGA file'
|
||||
22:45:50: Debug: Adding duplicate image handler for 'XPM file'
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": true,
|
||||
"grid_mm": 0.1,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.04,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 20.799824999005068,
|
||||
"routed_tree_edges": 30,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 248,
|
||||
"vias": 13,
|
||||
"immutable_identity_sha256": "81def52e6c1d574b4d3ab1a5859a5266b893df0ea6c5b9aa7d7b688cc4a98519",
|
||||
"input_sha256": "ab3f0775dee575a11d06f80996169f5097dfff672b69152ad9b559707f70009b",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
/builddir/build/BUILD/kicad-10.0.6-build/kicad-10.0.6/include/properties/property.h(607): assert "m_choices.GetCount() > 0" failed in PROPERTY_ENUM(): No enum choices defined
|
||||
{"net": "/+3V3", "start": "U1.1", "end": "U1.2", "start_mm": [108.0, 104.0], "end_mm": [108.0, 106.54], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/+3V3", "start": "R2.1", "end": "DS1.2", "start_mm": [154.0, 134.0], "end_mm": [154.73, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 911}
|
||||
{"net": "/+3V3", "start": "U2.1", "end": "R1.1", "start_mm": [143.0, 130.0], "end_mm": [143.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 81}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "SW3.1", "start_mm": [167.0, 168.0], "end_mm": [173.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_PREVIOUS", "start": "SW1.1", "end": "SW1.1", "start_mm": [149.5, 168.0], "end_mm": [143.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "SW2.1", "start_mm": [155.0, 168.0], "end_mm": [161.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/+3V3", "start": "DS1.2", "end": "R3.1", "start_mm": [154.73, 137.5], "end_mm": [165.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 5139}
|
||||
{"net": "/+3V3", "start": "R1.1", "end": "R2.1", "start_mm": [143.0, 134.0], "end_mm": [154.0, 134.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 14153}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "R2.2", "start_mm": [161.5, 168.0], "end_mm": [161.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 2039}
|
||||
{"net": "/BTN_NEXT", "start": "SW3.1", "end": "R3.2", "start_mm": [173.5, 168.0], "end_mm": [172.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 12615}
|
||||
{"net": "/BTN_PREVIOUS", "start": "R1.2", "end": "SW1.1", "start_mm": [150.62, 134.0], "end_mm": [149.5, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 15179}
|
||||
{"net": "/+3V3", "start": "U1.2", "end": "U2.1", "start_mm": [108.0, 106.54], "end_mm": [143.0, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 894511}
|
||||
{"net": "/RS_RX", "start": "U1.11", "end": "U2.4", "start_mm": [108.0, 129.4], "end_mm": [150.62, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 51516}
|
||||
{"net": "/BTN_PREVIOUS", "start": "U1.16", "end": "R1.2", "start_mm": [108.0, 142.1], "end_mm": [150.62, 134.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 94479}
|
||||
{"net": "/RS_DCD", "start": "U1.4", "end": "U2.3", "start_mm": [108.0, 111.62], "end_mm": [148.08, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 266031}
|
||||
{"net": "/RS_TX", "start": "U1.10", "end": "U2.5", "start_mm": [108.0, 126.86], "end_mm": [153.16, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 175689}
|
||||
{"net": "/RS_DTR", "start": "U1.7", "end": "U2.6", "start_mm": [108.0, 119.24], "end_mm": [155.7, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 326823}
|
||||
{"net": "/OLED_SCL", "start": "U1.18", "end": "DS1.3", "start_mm": [108.0, 147.18], "end_mm": [157.27, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 82557}
|
||||
{"net": "/BTN_SELECT", "start": "SW2.1", "end": "U1.19", "start_mm": [155.0, 168.0], "end_mm": [108.0, 149.72], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 147997}
|
||||
{"net": "/OLED_SDA", "start": "U1.17", "end": "DS1.4", "start_mm": [108.0, 144.64], "end_mm": [159.81, 137.5], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 266428}
|
||||
{"net": "/RS_DSR", "start": "U1.5", "end": "U2.7", "start_mm": [108.0, 114.16], "end_mm": [158.24, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 372014}
|
||||
{"net": "/RS_RTS", "start": "U1.8", "end": "U2.8", "start_mm": [108.0, 121.78], "end_mm": [160.78, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 298514}
|
||||
{"net": "/RS_CTS", "start": "U1.9", "end": "U2.9", "start_mm": [108.0, 124.32], "end_mm": [163.32, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 265272}
|
||||
{"net": "/RS_RI", "start": "U1.6", "end": "U2.10", "start_mm": [108.0, 116.7], "end_mm": [165.86, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 332506}
|
||||
{"net": "/RS_VALID", "start": "U1.12", "end": "U2.11", "start_mm": [108.0, 131.94], "end_mm": [168.4, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 348384}
|
||||
{"net": "/BTN_NEXT", "start": "U1.20", "end": "SW3.1", "start_mm": [108.0, 152.26], "end_mm": [167.0, 168.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 272795}
|
||||
{"net": "/RS_OFF_N", "start": "U1.15", "end": "U2.12", "start_mm": [108.0, 139.56], "end_mm": [170.94, 130.0], "routed": true, "width_mm": 0.25, "power_fallback": false, "expansions": 351630}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.43", "start_mm": [133.4, 157.34], "end_mm": [133.4, 154.8], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 52}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW1.2", "start_mm": [155.0, 172.5], "end_mm": [149.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW3.2", "start_mm": [161.5, 172.5], "end_mm": [167.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 111}
|
||||
{"net": "/GND", "start": "SW2.2", "end": "SW2.2", "start_mm": [155.0, 172.5], "end_mm": [161.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW3.2", "end": "SW3.2", "start_mm": [167.0, 172.5], "end_mm": [173.5, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "SW1.2", "start_mm": [149.5, 172.5], "end_mm": [143.0, 172.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 131}
|
||||
{"net": "/GND", "start": "DS1.1", "end": "U2.2", "start_mm": [152.19, 137.5], "end_mm": [145.54, 130.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 17468}
|
||||
{"net": "/GND", "start": "SW1.2", "end": "U1.44", "start_mm": [143.0, 172.5], "end_mm": [133.4, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 27488}
|
||||
{"net": "/GND", "start": "U1.44", "end": "U1.22", "start_mm": [133.4, 157.34], "end_mm": [108.0, 157.34], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 73753}
|
||||
{"net": "/GND", "start": "U1.43", "end": "DS1.1", "start_mm": [133.4, 154.8], "end_mm": [152.19, 137.5], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 54021}
|
||||
{"net": "/GND", "start": "U2.2", "end": "U1.23", "start_mm": [145.54, 130.0], "end_mm": [133.4, 104.0], "routed": true, "width_mm": 0.5, "power_fallback": false, "expansions": 98250}
|
||||
22:47:09: Debug: Adding duplicate image handler for 'PNG file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'JPEG file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'TIFF file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'GIF file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'PNM file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'PCX file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'IFF file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'Windows icon file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'Windows cursor file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'Windows animated cursor file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'TGA file'
|
||||
22:47:09: Debug: Adding duplicate image handler for 'XPM file'
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": true,
|
||||
"grid_mm": 0.05,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.02,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 26.517815405000874,
|
||||
"routed_tree_edges": 38,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 569,
|
||||
"vias": 20,
|
||||
"immutable_identity_sha256": "81def52e6c1d574b4d3ab1a5859a5266b893df0ea6c5b9aa7d7b688cc4a98519",
|
||||
"input_sha256": "41643d6089dae5ef2eee1a2071a6d11ae4c00f3c922eddfeaafff619fc5fab19",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -1,665 +0,0 @@
|
||||
{
|
||||
"draft_only": true,
|
||||
"saved": true,
|
||||
"grid_mm": 0.05,
|
||||
"clearance_mm": 0.25,
|
||||
"safety_margin_mm": 0.02,
|
||||
"via_diameter_mm": 0.7,
|
||||
"via_drill_mm": 0.3,
|
||||
"elapsed_seconds": 26.517815405000874,
|
||||
"connections": [
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "U1.1",
|
||||
"end": "U1.2",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
104.0
|
||||
],
|
||||
"end_mm": [
|
||||
108.0,
|
||||
106.54
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 52
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "R2.1",
|
||||
"end": "DS1.2",
|
||||
"start_mm": [
|
||||
154.0,
|
||||
134.0
|
||||
],
|
||||
"end_mm": [
|
||||
154.73,
|
||||
137.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 911
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "U2.1",
|
||||
"end": "R1.1",
|
||||
"start_mm": [
|
||||
143.0,
|
||||
130.0
|
||||
],
|
||||
"end_mm": [
|
||||
143.0,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 81
|
||||
},
|
||||
{
|
||||
"net": "/BTN_NEXT",
|
||||
"start": "SW3.1",
|
||||
"end": "SW3.1",
|
||||
"start_mm": [
|
||||
167.0,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
173.5,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/BTN_PREVIOUS",
|
||||
"start": "SW1.1",
|
||||
"end": "SW1.1",
|
||||
"start_mm": [
|
||||
149.5,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
143.0,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/BTN_SELECT",
|
||||
"start": "SW2.1",
|
||||
"end": "SW2.1",
|
||||
"start_mm": [
|
||||
155.0,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
161.5,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "DS1.2",
|
||||
"end": "R3.1",
|
||||
"start_mm": [
|
||||
154.73,
|
||||
137.5
|
||||
],
|
||||
"end_mm": [
|
||||
165.0,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 5139
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "R1.1",
|
||||
"end": "R2.1",
|
||||
"start_mm": [
|
||||
143.0,
|
||||
134.0
|
||||
],
|
||||
"end_mm": [
|
||||
154.0,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 14153
|
||||
},
|
||||
{
|
||||
"net": "/BTN_SELECT",
|
||||
"start": "SW2.1",
|
||||
"end": "R2.2",
|
||||
"start_mm": [
|
||||
161.5,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
161.62,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 2039
|
||||
},
|
||||
{
|
||||
"net": "/BTN_NEXT",
|
||||
"start": "SW3.1",
|
||||
"end": "R3.2",
|
||||
"start_mm": [
|
||||
173.5,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
172.62,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 12615
|
||||
},
|
||||
{
|
||||
"net": "/BTN_PREVIOUS",
|
||||
"start": "R1.2",
|
||||
"end": "SW1.1",
|
||||
"start_mm": [
|
||||
150.62,
|
||||
134.0
|
||||
],
|
||||
"end_mm": [
|
||||
149.5,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 15179
|
||||
},
|
||||
{
|
||||
"net": "/+3V3",
|
||||
"start": "U1.2",
|
||||
"end": "U2.1",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
106.54
|
||||
],
|
||||
"end_mm": [
|
||||
143.0,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 894511
|
||||
},
|
||||
{
|
||||
"net": "/RS_RX",
|
||||
"start": "U1.11",
|
||||
"end": "U2.4",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
129.4
|
||||
],
|
||||
"end_mm": [
|
||||
150.62,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 51516
|
||||
},
|
||||
{
|
||||
"net": "/BTN_PREVIOUS",
|
||||
"start": "U1.16",
|
||||
"end": "R1.2",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
142.1
|
||||
],
|
||||
"end_mm": [
|
||||
150.62,
|
||||
134.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 94479
|
||||
},
|
||||
{
|
||||
"net": "/RS_DCD",
|
||||
"start": "U1.4",
|
||||
"end": "U2.3",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
111.62
|
||||
],
|
||||
"end_mm": [
|
||||
148.08,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 266031
|
||||
},
|
||||
{
|
||||
"net": "/RS_TX",
|
||||
"start": "U1.10",
|
||||
"end": "U2.5",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
126.86
|
||||
],
|
||||
"end_mm": [
|
||||
153.16,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 175689
|
||||
},
|
||||
{
|
||||
"net": "/RS_DTR",
|
||||
"start": "U1.7",
|
||||
"end": "U2.6",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
119.24
|
||||
],
|
||||
"end_mm": [
|
||||
155.7,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 326823
|
||||
},
|
||||
{
|
||||
"net": "/OLED_SCL",
|
||||
"start": "U1.18",
|
||||
"end": "DS1.3",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
147.18
|
||||
],
|
||||
"end_mm": [
|
||||
157.27,
|
||||
137.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 82557
|
||||
},
|
||||
{
|
||||
"net": "/BTN_SELECT",
|
||||
"start": "SW2.1",
|
||||
"end": "U1.19",
|
||||
"start_mm": [
|
||||
155.0,
|
||||
168.0
|
||||
],
|
||||
"end_mm": [
|
||||
108.0,
|
||||
149.72
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 147997
|
||||
},
|
||||
{
|
||||
"net": "/OLED_SDA",
|
||||
"start": "U1.17",
|
||||
"end": "DS1.4",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
144.64
|
||||
],
|
||||
"end_mm": [
|
||||
159.81,
|
||||
137.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 266428
|
||||
},
|
||||
{
|
||||
"net": "/RS_DSR",
|
||||
"start": "U1.5",
|
||||
"end": "U2.7",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
114.16
|
||||
],
|
||||
"end_mm": [
|
||||
158.24,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 372014
|
||||
},
|
||||
{
|
||||
"net": "/RS_RTS",
|
||||
"start": "U1.8",
|
||||
"end": "U2.8",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
121.78
|
||||
],
|
||||
"end_mm": [
|
||||
160.78,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 298514
|
||||
},
|
||||
{
|
||||
"net": "/RS_CTS",
|
||||
"start": "U1.9",
|
||||
"end": "U2.9",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
124.32
|
||||
],
|
||||
"end_mm": [
|
||||
163.32,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 265272
|
||||
},
|
||||
{
|
||||
"net": "/RS_RI",
|
||||
"start": "U1.6",
|
||||
"end": "U2.10",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
116.7
|
||||
],
|
||||
"end_mm": [
|
||||
165.86,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 332506
|
||||
},
|
||||
{
|
||||
"net": "/RS_VALID",
|
||||
"start": "U1.12",
|
||||
"end": "U2.11",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
131.94
|
||||
],
|
||||
"end_mm": [
|
||||
168.4,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 348384
|
||||
},
|
||||
{
|
||||
"net": "/BTN_NEXT",
|
||||
"start": "U1.20",
|
||||
"end": "SW3.1",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
152.26
|
||||
],
|
||||
"end_mm": [
|
||||
167.0,
|
||||
168.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 272795
|
||||
},
|
||||
{
|
||||
"net": "/RS_OFF_N",
|
||||
"start": "U1.15",
|
||||
"end": "U2.12",
|
||||
"start_mm": [
|
||||
108.0,
|
||||
139.56
|
||||
],
|
||||
"end_mm": [
|
||||
170.94,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.25,
|
||||
"power_fallback": false,
|
||||
"expansions": 351630
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "U1.44",
|
||||
"end": "U1.43",
|
||||
"start_mm": [
|
||||
133.4,
|
||||
157.34
|
||||
],
|
||||
"end_mm": [
|
||||
133.4,
|
||||
154.8
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 52
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW2.2",
|
||||
"end": "SW1.2",
|
||||
"start_mm": [
|
||||
155.0,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
149.5,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 111
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW2.2",
|
||||
"end": "SW3.2",
|
||||
"start_mm": [
|
||||
161.5,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
167.0,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 111
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW2.2",
|
||||
"end": "SW2.2",
|
||||
"start_mm": [
|
||||
155.0,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
161.5,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW3.2",
|
||||
"end": "SW3.2",
|
||||
"start_mm": [
|
||||
167.0,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
173.5,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW1.2",
|
||||
"end": "SW1.2",
|
||||
"start_mm": [
|
||||
149.5,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
143.0,
|
||||
172.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 131
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "DS1.1",
|
||||
"end": "U2.2",
|
||||
"start_mm": [
|
||||
152.19,
|
||||
137.5
|
||||
],
|
||||
"end_mm": [
|
||||
145.54,
|
||||
130.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 17468
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "SW1.2",
|
||||
"end": "U1.44",
|
||||
"start_mm": [
|
||||
143.0,
|
||||
172.5
|
||||
],
|
||||
"end_mm": [
|
||||
133.4,
|
||||
157.34
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 27488
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "U1.44",
|
||||
"end": "U1.22",
|
||||
"start_mm": [
|
||||
133.4,
|
||||
157.34
|
||||
],
|
||||
"end_mm": [
|
||||
108.0,
|
||||
157.34
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 73753
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "U1.43",
|
||||
"end": "DS1.1",
|
||||
"start_mm": [
|
||||
133.4,
|
||||
154.8
|
||||
],
|
||||
"end_mm": [
|
||||
152.19,
|
||||
137.5
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 54021
|
||||
},
|
||||
{
|
||||
"net": "/GND",
|
||||
"start": "U2.2",
|
||||
"end": "U1.23",
|
||||
"start_mm": [
|
||||
145.54,
|
||||
130.0
|
||||
],
|
||||
"end_mm": [
|
||||
133.4,
|
||||
104.0
|
||||
],
|
||||
"routed": true,
|
||||
"width_mm": 0.5,
|
||||
"power_fallback": false,
|
||||
"expansions": 98250
|
||||
}
|
||||
],
|
||||
"routed_tree_edges": 38,
|
||||
"total_tree_edges": 38,
|
||||
"tracks": 569,
|
||||
"vias": 20,
|
||||
"immutable_identity_sha256": "81def52e6c1d574b4d3ab1a5859a5266b893df0ea6c5b9aa7d7b688cc4a98519",
|
||||
"input_sha256": "41643d6089dae5ef2eee1a2071a6d11ae4c00f3c922eddfeaafff619fc5fab19",
|
||||
"validation": "KiCad DRC must be run separately; tree-edge counts are not DRC connectivity"
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
# Two-layer draft routing result
|
||||
|
||||
Date: 2026-09-20. KiCad 10.0.6; system `/usr/bin/python3`.
|
||||
|
||||
## Saved result
|
||||
|
||||
Actual copper routing is saved in `../serial-carrier.kicad_pcb`, not just a preview
|
||||
or an exported proposal. **38/38 required spanning-tree connections across 17
|
||||
multi-pad nets routed; KiCad reports 0 unconnected items, 0 errors, 0 warnings.**
|
||||
Intentional single-pad `unconnected-(...)` nets retain their original names and
|
||||
are not connected to anything else.
|
||||
|
||||
- Two copper layers, F.Cu and B.Cu.
|
||||
- 569 straight track segments: 425 at 0.25 mm for signals and 144 at 0.5 mm for
|
||||
+3V3/GND. No narrow power-track fallback was necessary.
|
||||
- 20 through vias, 0.7 mm copper diameter / 0.3 mm drill.
|
||||
- No copper pours: all ground connections were routed explicitly.
|
||||
- Original RF rule area, x110..131 / y100..113 mm on both copper layers, preserved.
|
||||
No tracks, vias or fills enter the reservation. Minimum copper-to-reservation
|
||||
gap measured independently is 0.025 mm; this is geometric compliance with the
|
||||
specified provisional boundary, **not validation of an antenna's RF needs**.
|
||||
- All 9 electrical footprints and 4 standalone mounting footprints preserved.
|
||||
Pad positions, shapes, sizes, drills, types, nets, functions, pin types and UUIDs;
|
||||
footprint placement, orientation, library IDs, values, UUIDs and schematic UUID
|
||||
paths; net table, outline and RF rule-area geometry/flags match the input.
|
||||
- Generator, README, schematic, project and libraries were not edited by routing.
|
||||
No external network dependency or manufacturing exports were used.
|
||||
|
||||
## Validation performed
|
||||
|
||||
From `hardware/PCB`:
|
||||
|
||||
```sh
|
||||
kicad-cli pcb drc --format json --refill-zones --save-board -o validation/pcb-routing-drc.json serial-carrier.kicad_pcb
|
||||
kicad-cli pcb drc --format json --all-track-errors --severity-all --exit-code-violations -o validation/pcb-routing-all-drc.json serial-carrier.kicad_pcb
|
||||
/usr/bin/python3 -B validation/pcb-routing-check.py
|
||||
```
|
||||
|
||||
Both DRC runs: **0 violations and 0 unconnected items**. No DRC settings or
|
||||
exclusions were added or changed. Existing project rules specify 0.25 mm minimum
|
||||
clearance, 0.5 mm copper-edge clearance, 0.25 mm minimum track width and 0.7 mm
|
||||
minimum via diameter.
|
||||
|
||||
Independent continuous-geometry audit (`pcb-routing-check.json`):
|
||||
|
||||
| Check | Measured minimum | Requirement |
|
||||
|---|---:|---:|
|
||||
| Different-net copper / copper-to-NPTH clearance | 0.275 mm | 0.25 mm |
|
||||
| Copper or NPTH edge to board edge | 0.525 mm | 0.5 mm |
|
||||
| Copper / NPTH edge to RF reserve | 0.025 mm | No intersection |
|
||||
| Via drill edge to another drilled hole edge | 0.840175 mm | 0.25 mm |
|
||||
|
||||
The audit uses exact straight-line/point/rectangle distance calculations with
|
||||
copper radii, rather than the router's raster masks. It also verifies dimensions,
|
||||
source-preservation invariants, and three overwrite-refusal cases. Requires
|
||||
locally installed `pcbnew`, NumPy and Shapely; the router itself only needs
|
||||
`pcbnew` and NumPy.
|
||||
|
||||
DRC still honors the project's pre-existing ignored checks:
|
||||
`missing_courtyard`, `track_not_centered_on_via`,
|
||||
`tuning_profile_track_geometries`, `footprint_filters_mismatch`, and
|
||||
`footprint_type_mismatch`. `--severity-all` does not enable ignored checks.
|
||||
The routing subtask compared electrical UUID linkage and pad-net assignments
|
||||
with the input board. Subsequent integration also ran fresh schematic-parity DRC
|
||||
and added missing symbol metadata fields to the PCB without changing copper.
|
||||
That check reports exactly one expected U2 footprint warning (provisional PCB
|
||||
footprint versus intentionally blank schematic assignment); no net mismatch.
|
||||
See `pcb-parity-drc.json` and `pcb-validation-summary.json`.
|
||||
|
||||
## Method and bounded passes
|
||||
|
||||
`../route_pcb_draft.py` uses a two-layer 8-direction raster A* search, an explicit
|
||||
via cost, exact-pad-centre terminal stubs, conservative foreign-copper and hole
|
||||
obstacles, and only collinear path compression. It preserves the L-shaped edge
|
||||
and RF reservation. Per-net minimum spanning trees connect every physical pad,
|
||||
including duplicate switch contacts; short links precede longer links and GND is
|
||||
last. It does not optimize return-current paths, topology or total via count.
|
||||
|
||||
1. Initial 0.1 mm raster / 0.04 mm safety margin / 250,000 expansions per search:
|
||||
29/38 connections; DRC 0 violations, 9 unconnected.
|
||||
2. Increased search allowance to 1,200,000 expansions: 30/38; DRC 0 violations,
|
||||
8 unconnected. Some raster header passages were blocked, not just expensive.
|
||||
3. Final 0.05 mm raster / 0.02 mm additional safety margin, with 0.25 mm physical
|
||||
clearance unchanged: 38/38 in approximately 26.5 seconds; DRC fully connected.
|
||||
Each pass had a 240-second routing deadline. No unresolved routing remains.
|
||||
|
||||
A KiCad Python binding ownership problem in the first replacement attempt caused
|
||||
an exception/segfault before any board save. Replacement now uses `board.Delete`
|
||||
instead of `board.Remove`; the subsequent save and no-save rerun both passed.
|
||||
The known startup PROPERTY_ENUM assertions do not prevent validation.
|
||||
|
||||
## Rerun safety
|
||||
|
||||
By default the script never saves. The existing routed board is protected even
|
||||
from a preview unless `--replace-routes` is given. A preview can be run with:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B route_pcb_draft.py --replace-routes --seconds 240
|
||||
```
|
||||
|
||||
This exact no-save workflow was tested: it again found 38/38 connections, 569
|
||||
segments and 20 vias, and left the board byte-for-byte unchanged.
|
||||
|
||||
**The following command discards all existing/manual tracks and vias**, then
|
||||
replaces them with fresh draft routing. Back up manual work first:
|
||||
|
||||
```sh
|
||||
/usr/bin/python3 -B route_pcb_draft.py --route --overwrite --replace-routes --seconds 240 --max-expansions 1200000
|
||||
```
|
||||
|
||||
The script refuses a PCB-editor lock, unexpected outline/RF/board structure,
|
||||
unsupported pad orientation/layers, unapproved existing routes, or detected board
|
||||
changes before save. Candidate serialization must preserve immutable geometry
|
||||
and linkage before replacing the board. It deliberately does not run DRC itself:
|
||||
rerun the validation commands after any routing change. Runtime/expansion limits
|
||||
can produce a partial board; inspect both the run report and actual DRC, not just
|
||||
the script exit status.
|
||||
|
||||
## Evidence and remaining limitations
|
||||
|
||||
- `pcb-routing-input.kicad_pcb`: unrouted input snapshot, after baseline DRC save.
|
||||
- `pcb-routing-baseline.json`: 38 unconnected / no violations before routing.
|
||||
- `pcb-routing-pass1-drc.json`, `pcb-routing-pass2-drc.json`: intermediate DRC.
|
||||
- `pcb-routing-pass2.log`, `pcb-routing-pass3.log`: bounded routing progress.
|
||||
- `pcb-routing-run.json`: final saved-route metrics and connection list.
|
||||
- `pcb-routing-drc.json`, `pcb-routing-all-drc.json`: final KiCad DRC evidence.
|
||||
- `pcb-routing-check.py`, `pcb-routing-check.json`: repeatable independent audit.
|
||||
- `pcb-routing-dry-run.log`: successful final no-save reproduction.
|
||||
|
||||
Routing-only DRC-saved snapshot SHA-256 (before integration copied schematic
|
||||
metadata fields): `bf94b6c9b00c43a97f933054758a2c9d6541981ae09f2f285dcdb567bd73d9b1`.
|
||||
The current post-integration hash is recorded by `pcb-routing-check.json`;
|
||||
geometry, pad-net linkage and copper are unchanged by the metadata copy.
|
||||
|
||||
**Still DRAFT / NOT FOR FABRICATION.** Provisional module/connector geometry,
|
||||
antenna envelope, assembly/courtyard clearances, USB access, male RS-232 connector
|
||||
fit, physical pin mapping, power integrity, EMI and hardware operation are not
|
||||
qualified by routing or DRC. All previously documented mechanical/electrical
|
||||
release gates remain. No physical-device or manufacturing validation performed.
|
||||
@@ -1,604 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<export version="E">
|
||||
<design>
|
||||
<source>/home/mscholz/Repos/ESP32_serial_swiss_army_knife/hardware/PCB/serial-carrier.kicad_sch</source>
|
||||
<date>2026-09-20T23:07:27</date>
|
||||
<tool>Eeschema 10.0.6-1.fc44</tool>
|
||||
<sheet number="1" name="/" tstamps="/">
|
||||
<title_block>
|
||||
<title>ESP32 Serial Swiss Army Knife - Module Carrier</title>
|
||||
<company/>
|
||||
<rev>A0 DRAFT</rev>
|
||||
<date>2026-09-20</date>
|
||||
<source>serial-carrier.kicad_sch</source>
|
||||
<comment number="1" value="Not fabrication-ready: provisional module footprints and male RS-232 mapping"/>
|
||||
<comment number="2" value=""/>
|
||||
<comment number="3" value=""/>
|
||||
<comment number="4" value=""/>
|
||||
<comment number="5" value=""/>
|
||||
<comment number="6" value=""/>
|
||||
<comment number="7" value=""/>
|
||||
<comment number="8" value=""/>
|
||||
<comment number="9" value=""/>
|
||||
</title_block>
|
||||
</sheet>
|
||||
</design>
|
||||
<components>
|
||||
<comp ref="DS1">
|
||||
<value>128x64 I2C OLED / 0x3C</value>
|
||||
<footprint>Carrier:OLED_26mm_I2C_Provisional</footprint>
|
||||
<fields>
|
||||
<field name="Status">Module outline/header/display user fit-verified; pad/drill fit provisional</field>
|
||||
<field name="Footprint">Carrier:OLED_26mm_I2C_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="OLED_I2C_26mm" description="User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA"/>
|
||||
<property name="Status" value="Module outline/header/display user fit-verified; pad/drill fit provisional"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>0ae411c1-94cc-5551-9037-1d59c01a8ee5</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R1">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>defb78eb-632f-5f9d-a59d-dffbb1d31dec</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R2">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>405055c0-35ab-5334-ad4d-30d45c5ef6f2</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R3">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>78f9a74f-5d91-5687-b50e-68176333df23</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW1">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Previous / back</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Previous / back"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>dd5f1f75-0093-55fc-9814-437ed26871eb</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW2">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Select / confirm</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Select / confirm"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>c522a716-55a4-584d-a12e-ab135b4c08eb</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW3">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Next</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Next"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>35caa6bb-42a6-512c-848c-f1e4457aa97f</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="U1">
|
||||
<value>HW678 / S3-N16R8</value>
|
||||
<footprint>Carrier:HW678_2x22_Provisional</footprint>
|
||||
<fields>
|
||||
<field name="Status">Header geometry nominal; outline/antenna clearance pending</field>
|
||||
<field name="Pin_numbering">Left 1-22; right 23-44; both top-to-bottom, antenna up</field>
|
||||
<field name="Footprint">Carrier:HW678_2x22_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="HW678_N16R8" description="HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained"/>
|
||||
<property name="Status" value="Header geometry nominal; outline/antenna clearance pending"/>
|
||||
<property name="Pin_numbering" value="Left 1-22; right 23-44; both top-to-bottom, antenna up"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>3ab265d0-7fd3-5891-94eb-d39426c6a8a3</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
<pin num="5"/>
|
||||
<pin num="6"/>
|
||||
<pin num="7"/>
|
||||
<pin num="8"/>
|
||||
<pin num="9"/>
|
||||
<pin num="10"/>
|
||||
<pin num="11"/>
|
||||
<pin num="12"/>
|
||||
<pin num="13"/>
|
||||
<pin num="14"/>
|
||||
<pin num="15"/>
|
||||
<pin num="16"/>
|
||||
<pin num="17"/>
|
||||
<pin num="18"/>
|
||||
<pin num="19"/>
|
||||
<pin num="20"/>
|
||||
<pin num="21"/>
|
||||
<pin num="22"/>
|
||||
<pin num="23"/>
|
||||
<pin num="24"/>
|
||||
<pin num="25"/>
|
||||
<pin num="26"/>
|
||||
<pin num="27"/>
|
||||
<pin num="28"/>
|
||||
<pin num="29"/>
|
||||
<pin num="30"/>
|
||||
<pin num="31"/>
|
||||
<pin num="32"/>
|
||||
<pin num="33"/>
|
||||
<pin num="34"/>
|
||||
<pin num="35"/>
|
||||
<pin num="36"/>
|
||||
<pin num="37"/>
|
||||
<pin num="38"/>
|
||||
<pin num="39"/>
|
||||
<pin num="40"/>
|
||||
<pin num="41"/>
|
||||
<pin num="42"/>
|
||||
<pin num="43"/>
|
||||
<pin num="44"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="U2">
|
||||
<value>Adafruit MAX3243 / male 6253</value>
|
||||
<fields>
|
||||
<field name="Status">VERIFY male header numbering; footprint intentionally unassigned</field>
|
||||
<field name="Datasheet_source">adafruit-research.md; reference female 5988 CAD</field>
|
||||
<field name="Footprint"/>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="MAX3243_Module_Reference" description="Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE"/>
|
||||
<property name="Status" value="VERIFY male header numbering; footprint intentionally unassigned"/>
|
||||
<property name="Datasheet_source" value="adafruit-research.md; reference female 5988 CAD"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>1127195e-e393-5d1d-9119-b25d10556144</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
<pin num="5"/>
|
||||
<pin num="6"/>
|
||||
<pin num="7"/>
|
||||
<pin num="8"/>
|
||||
<pin num="9"/>
|
||||
<pin num="10"/>
|
||||
<pin num="11"/>
|
||||
<pin num="12"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
</components>
|
||||
<libparts>
|
||||
<libpart lib="Carrier" part="HW678_N16R8">
|
||||
<description>HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained</description>
|
||||
<fields>
|
||||
<field name="Reference">U</field>
|
||||
<field name="Value">HW678_N16R8</field>
|
||||
<field name="Footprint">Carrier:HW678_2x22_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="3V3" type="power_out"/>
|
||||
<pin num="2" name="3V3" type="passive"/>
|
||||
<pin num="3" name="EN" type="input"/>
|
||||
<pin num="4" name="GPIO4" type="input"/>
|
||||
<pin num="5" name="GPIO5" type="input"/>
|
||||
<pin num="6" name="GPIO6" type="input"/>
|
||||
<pin num="7" name="GPIO7" type="output"/>
|
||||
<pin num="8" name="GPIO15" type="output"/>
|
||||
<pin num="9" name="GPIO16" type="input"/>
|
||||
<pin num="10" name="GPIO17" type="output"/>
|
||||
<pin num="11" name="GPIO18" type="input"/>
|
||||
<pin num="12" name="GPIO8" type="input"/>
|
||||
<pin num="13" name="GPIO3" type="bidirectional"/>
|
||||
<pin num="14" name="GPIO46" type="bidirectional"/>
|
||||
<pin num="15" name="GPIO9" type="bidirectional"/>
|
||||
<pin num="16" name="GPIO10" type="input"/>
|
||||
<pin num="17" name="GPIO11" type="bidirectional"/>
|
||||
<pin num="18" name="GPIO12" type="output"/>
|
||||
<pin num="19" name="GPIO13" type="input"/>
|
||||
<pin num="20" name="GPIO14" type="input"/>
|
||||
<pin num="21" name="5V" type="passive"/>
|
||||
<pin num="22" name="GND" type="power_out"/>
|
||||
<pin num="23" name="GND" type="passive"/>
|
||||
<pin num="24" name="GPIO43" type="bidirectional"/>
|
||||
<pin num="25" name="GPIO44" type="bidirectional"/>
|
||||
<pin num="26" name="GPIO1" type="bidirectional"/>
|
||||
<pin num="27" name="GPIO2" type="bidirectional"/>
|
||||
<pin num="28" name="GPIO42" type="bidirectional"/>
|
||||
<pin num="29" name="GPIO41" type="bidirectional"/>
|
||||
<pin num="30" name="GPIO40" type="bidirectional"/>
|
||||
<pin num="31" name="GPIO39" type="bidirectional"/>
|
||||
<pin num="32" name="GPIO38" type="bidirectional"/>
|
||||
<pin num="33" name="GPIO37" type="bidirectional"/>
|
||||
<pin num="34" name="GPIO36" type="bidirectional"/>
|
||||
<pin num="35" name="GPIO35" type="bidirectional"/>
|
||||
<pin num="36" name="GPIO0" type="bidirectional"/>
|
||||
<pin num="37" name="GPIO45" type="bidirectional"/>
|
||||
<pin num="38" name="GPIO48" type="bidirectional"/>
|
||||
<pin num="39" name="GPIO47" type="bidirectional"/>
|
||||
<pin num="40" name="GPIO21" type="bidirectional"/>
|
||||
<pin num="41" name="GPIO20" type="bidirectional"/>
|
||||
<pin num="42" name="GPIO19" type="bidirectional"/>
|
||||
<pin num="43" name="GND" type="passive"/>
|
||||
<pin num="44" name="GND" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="MAX3243_Module_Reference">
|
||||
<description>Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE</description>
|
||||
<fields>
|
||||
<field name="Reference">U</field>
|
||||
<field name="Value">MAX3243_Module_Reference</field>
|
||||
<field name="Footprint"/>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="Vin" type="power_in"/>
|
||||
<pin num="2" name="GND" type="power_in"/>
|
||||
<pin num="3" name="DCD" type="output"/>
|
||||
<pin num="4" name="RX" type="output"/>
|
||||
<pin num="5" name="TX" type="input"/>
|
||||
<pin num="6" name="DTR" type="input"/>
|
||||
<pin num="7" name="DSR" type="output"/>
|
||||
<pin num="8" name="RTS" type="input"/>
|
||||
<pin num="9" name="CTS" type="output"/>
|
||||
<pin num="10" name="RI" type="output"/>
|
||||
<pin num="11" name="VLD" type="output"/>
|
||||
<pin num="12" name="OFF_N" type="input"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="OLED_I2C_26mm">
|
||||
<description>User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA</description>
|
||||
<fields>
|
||||
<field name="Reference">DS</field>
|
||||
<field name="Value">OLED_I2C_26mm</field>
|
||||
<field name="Footprint">Carrier:OLED_26mm_I2C_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="GND" type="power_in"/>
|
||||
<pin num="2" name="VCC" type="power_in"/>
|
||||
<pin num="3" name="SCL" type="input"/>
|
||||
<pin num="4" name="SDA" type="bidirectional"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="R">
|
||||
<description>Resistor, axial DIN0207 footprint; exact MPN pending</description>
|
||||
<fields>
|
||||
<field name="Reference">R</field>
|
||||
<field name="Value">R</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Resistor, axial DIN0207 footprint; exact MPN pending</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="" type="passive"/>
|
||||
<pin num="2" name="" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="SW_NO">
|
||||
<description>Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs</description>
|
||||
<fields>
|
||||
<field name="Reference">SW</field>
|
||||
<field name="Value">SW_NO</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="" type="passive"/>
|
||||
<pin num="2" name="" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
</libparts>
|
||||
<libraries>
|
||||
<library logical="Carrier">
|
||||
<uri>${KIPRJMOD}/Carrier.kicad_sym</uri>
|
||||
</library>
|
||||
</libraries>
|
||||
<nets>
|
||||
<net code="1" name="/+3V3" class="Power">
|
||||
<node ref="DS1" pin="2" pinfunction="VCC_2" pintype="power_in"/>
|
||||
<node ref="R1" pin="1" pintype="passive"/>
|
||||
<node ref="R2" pin="1" pintype="passive"/>
|
||||
<node ref="R3" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="1" pinfunction="3V3_1" pintype="power_out"/>
|
||||
<node ref="U1" pin="2" pinfunction="3V3_2" pintype="passive"/>
|
||||
<node ref="U2" pin="1" pinfunction="Vin_1" pintype="power_in"/>
|
||||
</net>
|
||||
<net code="2" name="/BTN_NEXT" class="Default">
|
||||
<node ref="R3" pin="2" pintype="passive"/>
|
||||
<node ref="SW3" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="20" pinfunction="GPIO14_20" pintype="input"/>
|
||||
</net>
|
||||
<net code="3" name="/BTN_PREVIOUS" class="Default">
|
||||
<node ref="R1" pin="2" pintype="passive"/>
|
||||
<node ref="SW1" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="16" pinfunction="GPIO10_16" pintype="input"/>
|
||||
</net>
|
||||
<net code="4" name="/BTN_SELECT" class="Default">
|
||||
<node ref="R2" pin="2" pintype="passive"/>
|
||||
<node ref="SW2" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="19" pinfunction="GPIO13_19" pintype="input"/>
|
||||
</net>
|
||||
<net code="5" name="/GND" class="Power">
|
||||
<node ref="DS1" pin="1" pinfunction="GND_1" pintype="power_in"/>
|
||||
<node ref="SW1" pin="2" pintype="passive"/>
|
||||
<node ref="SW2" pin="2" pintype="passive"/>
|
||||
<node ref="SW3" pin="2" pintype="passive"/>
|
||||
<node ref="U1" pin="22" pinfunction="GND_22" pintype="power_out"/>
|
||||
<node ref="U1" pin="23" pinfunction="GND_23" pintype="passive"/>
|
||||
<node ref="U1" pin="43" pinfunction="GND_43" pintype="passive"/>
|
||||
<node ref="U1" pin="44" pinfunction="GND_44" pintype="passive"/>
|
||||
<node ref="U2" pin="2" pinfunction="GND_2" pintype="power_in"/>
|
||||
</net>
|
||||
<net code="6" name="/OLED_SCL" class="Default">
|
||||
<node ref="DS1" pin="3" pinfunction="SCL_3" pintype="input"/>
|
||||
<node ref="U1" pin="18" pinfunction="GPIO12_18" pintype="output"/>
|
||||
</net>
|
||||
<net code="7" name="/OLED_SDA" class="Default">
|
||||
<node ref="DS1" pin="4" pinfunction="SDA_4" pintype="bidirectional"/>
|
||||
<node ref="U1" pin="17" pinfunction="GPIO11_17" pintype="bidirectional"/>
|
||||
</net>
|
||||
<net code="8" name="/RS_CTS" class="Default">
|
||||
<node ref="U1" pin="9" pinfunction="GPIO16_9" pintype="input"/>
|
||||
<node ref="U2" pin="9" pinfunction="CTS_9" pintype="output"/>
|
||||
</net>
|
||||
<net code="9" name="/RS_DCD" class="Default">
|
||||
<node ref="U1" pin="4" pinfunction="GPIO4_4" pintype="input"/>
|
||||
<node ref="U2" pin="3" pinfunction="DCD_3" pintype="output"/>
|
||||
</net>
|
||||
<net code="10" name="/RS_DSR" class="Default">
|
||||
<node ref="U1" pin="5" pinfunction="GPIO5_5" pintype="input"/>
|
||||
<node ref="U2" pin="7" pinfunction="DSR_7" pintype="output"/>
|
||||
</net>
|
||||
<net code="11" name="/RS_DTR" class="Default">
|
||||
<node ref="U1" pin="7" pinfunction="GPIO7_7" pintype="output"/>
|
||||
<node ref="U2" pin="6" pinfunction="DTR_6" pintype="input"/>
|
||||
</net>
|
||||
<net code="12" name="/RS_OFF_N" class="Default">
|
||||
<node ref="U1" pin="15" pinfunction="GPIO9_15" pintype="bidirectional"/>
|
||||
<node ref="U2" pin="12" pinfunction="OFF_N_12" pintype="input"/>
|
||||
</net>
|
||||
<net code="13" name="/RS_RI" class="Default">
|
||||
<node ref="U1" pin="6" pinfunction="GPIO6_6" pintype="input"/>
|
||||
<node ref="U2" pin="10" pinfunction="RI_10" pintype="output"/>
|
||||
</net>
|
||||
<net code="14" name="/RS_RTS" class="Default">
|
||||
<node ref="U1" pin="8" pinfunction="GPIO15_8" pintype="output"/>
|
||||
<node ref="U2" pin="8" pinfunction="RTS_8" pintype="input"/>
|
||||
</net>
|
||||
<net code="15" name="/RS_RX" class="Default">
|
||||
<node ref="U1" pin="11" pinfunction="GPIO18_11" pintype="input"/>
|
||||
<node ref="U2" pin="4" pinfunction="RX_4" pintype="output"/>
|
||||
</net>
|
||||
<net code="16" name="/RS_TX" class="Default">
|
||||
<node ref="U1" pin="10" pinfunction="GPIO17_10" pintype="output"/>
|
||||
<node ref="U2" pin="5" pinfunction="TX_5" pintype="input"/>
|
||||
</net>
|
||||
<net code="17" name="/RS_VALID" class="Default">
|
||||
<node ref="U1" pin="12" pinfunction="GPIO8_12" pintype="input"/>
|
||||
<node ref="U2" pin="11" pinfunction="VLD_11" pintype="output"/>
|
||||
</net>
|
||||
<net code="18" name="unconnected-(U1-5V-Pad21)" class="Default">
|
||||
<node ref="U1" pin="21" pinfunction="5V_21" pintype="passive+no_connect"/>
|
||||
</net>
|
||||
<net code="19" name="unconnected-(U1-EN-Pad3)" class="Default">
|
||||
<node ref="U1" pin="3" pinfunction="EN_3" pintype="input+no_connect"/>
|
||||
</net>
|
||||
<net code="20" name="unconnected-(U1-GPIO0-Pad36)" class="Default">
|
||||
<node ref="U1" pin="36" pinfunction="GPIO0_36" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="21" name="unconnected-(U1-GPIO1-Pad26)" class="Default">
|
||||
<node ref="U1" pin="26" pinfunction="GPIO1_26" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="22" name="unconnected-(U1-GPIO2-Pad27)" class="Default">
|
||||
<node ref="U1" pin="27" pinfunction="GPIO2_27" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="23" name="unconnected-(U1-GPIO3-Pad13)" class="Default">
|
||||
<node ref="U1" pin="13" pinfunction="GPIO3_13" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="24" name="unconnected-(U1-GPIO19-Pad42)" class="Default">
|
||||
<node ref="U1" pin="42" pinfunction="GPIO19_42" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="25" name="unconnected-(U1-GPIO20-Pad41)" class="Default">
|
||||
<node ref="U1" pin="41" pinfunction="GPIO20_41" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="26" name="unconnected-(U1-GPIO21-Pad40)" class="Default">
|
||||
<node ref="U1" pin="40" pinfunction="GPIO21_40" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="27" name="unconnected-(U1-GPIO35-Pad35)" class="Default">
|
||||
<node ref="U1" pin="35" pinfunction="GPIO35_35" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="28" name="unconnected-(U1-GPIO36-Pad34)" class="Default">
|
||||
<node ref="U1" pin="34" pinfunction="GPIO36_34" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="29" name="unconnected-(U1-GPIO37-Pad33)" class="Default">
|
||||
<node ref="U1" pin="33" pinfunction="GPIO37_33" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="30" name="unconnected-(U1-GPIO38-Pad32)" class="Default">
|
||||
<node ref="U1" pin="32" pinfunction="GPIO38_32" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="31" name="unconnected-(U1-GPIO39-Pad31)" class="Default">
|
||||
<node ref="U1" pin="31" pinfunction="GPIO39_31" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="32" name="unconnected-(U1-GPIO40-Pad30)" class="Default">
|
||||
<node ref="U1" pin="30" pinfunction="GPIO40_30" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="33" name="unconnected-(U1-GPIO41-Pad29)" class="Default">
|
||||
<node ref="U1" pin="29" pinfunction="GPIO41_29" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="34" name="unconnected-(U1-GPIO42-Pad28)" class="Default">
|
||||
<node ref="U1" pin="28" pinfunction="GPIO42_28" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="35" name="unconnected-(U1-GPIO43-Pad24)" class="Default">
|
||||
<node ref="U1" pin="24" pinfunction="GPIO43_24" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="36" name="unconnected-(U1-GPIO44-Pad25)" class="Default">
|
||||
<node ref="U1" pin="25" pinfunction="GPIO44_25" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="37" name="unconnected-(U1-GPIO45-Pad37)" class="Default">
|
||||
<node ref="U1" pin="37" pinfunction="GPIO45_37" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="38" name="unconnected-(U1-GPIO46-Pad14)" class="Default">
|
||||
<node ref="U1" pin="14" pinfunction="GPIO46_14" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="39" name="unconnected-(U1-GPIO47-Pad39)" class="Default">
|
||||
<node ref="U1" pin="39" pinfunction="GPIO47_39" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="40" name="unconnected-(U1-GPIO48-Pad38)" class="Default">
|
||||
<node ref="U1" pin="38" pinfunction="GPIO48_38" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
</nets>
|
||||
</export>
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"carrier_bounds_mm": [
|
||||
82,
|
||||
80
|
||||
],
|
||||
"copper_layers": 2,
|
||||
"draft_thickness_mm": 1.6,
|
||||
"electrical_footprints": 9,
|
||||
"proposed_M3_mounts": 4,
|
||||
"physical_DRC_violations": 0,
|
||||
"unconnected_items": 0,
|
||||
"schematic_pad_nets_match": true,
|
||||
"known_parity_warning": "U2 provisional female-reference footprint assigned on PCB only; schematic footprint intentionally blank",
|
||||
"ignored_DRC_checks": [
|
||||
{
|
||||
"description": "Footprint has no courtyard defined",
|
||||
"key": "missing_courtyard"
|
||||
},
|
||||
{
|
||||
"description": "Track endpoint not centered on via",
|
||||
"key": "track_not_centered_on_via"
|
||||
},
|
||||
{
|
||||
"description": "Tuning profile track geometries",
|
||||
"key": "tuning_profile_track_geometries"
|
||||
},
|
||||
{
|
||||
"description": "Footprint doesn't match symbol's footprint filters",
|
||||
"key": "footprint_filters_mismatch"
|
||||
},
|
||||
{
|
||||
"description": "Footprint component type doesn't match footprint pads",
|
||||
"key": "footprint_type_mismatch"
|
||||
}
|
||||
],
|
||||
"not_validated": [
|
||||
"male module pin map/fit",
|
||||
"assembly envelope and heights",
|
||||
"header/socket fit",
|
||||
"RF performance and antenna clearance",
|
||||
"power/return-path integrity",
|
||||
"USB power path",
|
||||
"OLED pull-ups",
|
||||
"fabrication process",
|
||||
"hardware operation"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
@@ -1,604 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<export version="E">
|
||||
<design>
|
||||
<source>/home/mscholz/Repos/ESP32_serial_swiss_army_knife/hardware/PCB/serial-carrier.kicad_sch</source>
|
||||
<date>2026-09-20T20:14:45</date>
|
||||
<tool>Eeschema 10.0.6-1.fc44</tool>
|
||||
<sheet number="1" name="/" tstamps="/">
|
||||
<title_block>
|
||||
<title>ESP32 Serial Swiss Army Knife - Module Carrier</title>
|
||||
<company/>
|
||||
<rev>A0 DRAFT</rev>
|
||||
<date>2026-09-20</date>
|
||||
<source>serial-carrier.kicad_sch</source>
|
||||
<comment number="1" value="Not fabrication-ready: provisional module footprints and male RS-232 mapping"/>
|
||||
<comment number="2" value=""/>
|
||||
<comment number="3" value=""/>
|
||||
<comment number="4" value=""/>
|
||||
<comment number="5" value=""/>
|
||||
<comment number="6" value=""/>
|
||||
<comment number="7" value=""/>
|
||||
<comment number="8" value=""/>
|
||||
<comment number="9" value=""/>
|
||||
</title_block>
|
||||
</sheet>
|
||||
</design>
|
||||
<components>
|
||||
<comp ref="DS1">
|
||||
<value>128x64 I2C OLED / 0x3C</value>
|
||||
<footprint>Carrier:OLED_26mm_I2C_Provisional</footprint>
|
||||
<fields>
|
||||
<field name="Status">Module outline/header/display user fit-verified; pad/drill fit provisional</field>
|
||||
<field name="Footprint">Carrier:OLED_26mm_I2C_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="OLED_I2C_26mm" description="User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA"/>
|
||||
<property name="Status" value="Module outline/header/display user fit-verified; pad/drill fit provisional"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>0ae411c1-94cc-5551-9037-1d59c01a8ee5</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R1">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>defb78eb-632f-5f9d-a59d-dffbb1d31dec</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R2">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>405055c0-35ab-5334-ad4d-30d45c5ef6f2</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="R3">
|
||||
<value>2.2k 1%</value>
|
||||
<footprint>Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</footprint>
|
||||
<fields>
|
||||
<field name="Rating">At least 0.125 W; exact resistor MPN pending</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="R" description="Resistor, axial DIN0207 footprint; exact MPN pending"/>
|
||||
<property name="Rating" value="At least 0.125 W; exact resistor MPN pending"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>78f9a74f-5d91-5687-b50e-68176333df23</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW1">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Previous / back</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Previous / back"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>dd5f1f75-0093-55fc-9814-437ed26871eb</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW2">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Select / confirm</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Select / confirm"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>c522a716-55a4-584d-a12e-ab135b4c08eb</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="SW3">
|
||||
<value>B3F-1000</value>
|
||||
<footprint>Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</footprint>
|
||||
<fields>
|
||||
<field name="Manufacturer">Omron</field>
|
||||
<field name="MPN">B3F-1000</field>
|
||||
<field name="Function">Next</field>
|
||||
<field name="Procurement">Live stock not verified</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="SW_NO" description="Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs"/>
|
||||
<property name="Manufacturer" value="Omron"/>
|
||||
<property name="MPN" value="B3F-1000"/>
|
||||
<property name="Function" value="Next"/>
|
||||
<property name="Procurement" value="Live stock not verified"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>35caa6bb-42a6-512c-848c-f1e4457aa97f</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="U1">
|
||||
<value>HW678 / S3-N16R8</value>
|
||||
<footprint>Carrier:HW678_2x22_Provisional</footprint>
|
||||
<fields>
|
||||
<field name="Status">Header geometry nominal; outline/antenna clearance pending</field>
|
||||
<field name="Pin_numbering">Left 1-22; right 23-44; both top-to-bottom, antenna up</field>
|
||||
<field name="Footprint">Carrier:HW678_2x22_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="HW678_N16R8" description="HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained"/>
|
||||
<property name="Status" value="Header geometry nominal; outline/antenna clearance pending"/>
|
||||
<property name="Pin_numbering" value="Left 1-22; right 23-44; both top-to-bottom, antenna up"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>3ab265d0-7fd3-5891-94eb-d39426c6a8a3</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
<pin num="5"/>
|
||||
<pin num="6"/>
|
||||
<pin num="7"/>
|
||||
<pin num="8"/>
|
||||
<pin num="9"/>
|
||||
<pin num="10"/>
|
||||
<pin num="11"/>
|
||||
<pin num="12"/>
|
||||
<pin num="13"/>
|
||||
<pin num="14"/>
|
||||
<pin num="15"/>
|
||||
<pin num="16"/>
|
||||
<pin num="17"/>
|
||||
<pin num="18"/>
|
||||
<pin num="19"/>
|
||||
<pin num="20"/>
|
||||
<pin num="21"/>
|
||||
<pin num="22"/>
|
||||
<pin num="23"/>
|
||||
<pin num="24"/>
|
||||
<pin num="25"/>
|
||||
<pin num="26"/>
|
||||
<pin num="27"/>
|
||||
<pin num="28"/>
|
||||
<pin num="29"/>
|
||||
<pin num="30"/>
|
||||
<pin num="31"/>
|
||||
<pin num="32"/>
|
||||
<pin num="33"/>
|
||||
<pin num="34"/>
|
||||
<pin num="35"/>
|
||||
<pin num="36"/>
|
||||
<pin num="37"/>
|
||||
<pin num="38"/>
|
||||
<pin num="39"/>
|
||||
<pin num="40"/>
|
||||
<pin num="41"/>
|
||||
<pin num="42"/>
|
||||
<pin num="43"/>
|
||||
<pin num="44"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
<comp ref="U2">
|
||||
<value>Adafruit MAX3243 / male 6253</value>
|
||||
<fields>
|
||||
<field name="Status">VERIFY male header numbering; footprint intentionally unassigned</field>
|
||||
<field name="Datasheet_source">adafruit-research.md; reference female 5988 CAD</field>
|
||||
<field name="Footprint"/>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description"/>
|
||||
</fields>
|
||||
<libsource lib="Carrier" part="MAX3243_Module_Reference" description="Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE"/>
|
||||
<property name="Status" value="VERIFY male header numbering; footprint intentionally unassigned"/>
|
||||
<property name="Datasheet_source" value="adafruit-research.md; reference female 5988 CAD"/>
|
||||
<property name="Sheetname" value="serial-carrier"/>
|
||||
<property name="Sheetfile" value="serial-carrier.kicad_sch"/>
|
||||
<sheetpath names="/" tstamps="/"/>
|
||||
<tstamps>1127195e-e393-5d1d-9119-b25d10556144</tstamps>
|
||||
<units>
|
||||
<unit name="A">
|
||||
<pins>
|
||||
<pin num="1"/>
|
||||
<pin num="2"/>
|
||||
<pin num="3"/>
|
||||
<pin num="4"/>
|
||||
<pin num="5"/>
|
||||
<pin num="6"/>
|
||||
<pin num="7"/>
|
||||
<pin num="8"/>
|
||||
<pin num="9"/>
|
||||
<pin num="10"/>
|
||||
<pin num="11"/>
|
||||
<pin num="12"/>
|
||||
</pins>
|
||||
</unit>
|
||||
</units>
|
||||
</comp>
|
||||
</components>
|
||||
<libparts>
|
||||
<libpart lib="Carrier" part="HW678_N16R8">
|
||||
<description>HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained</description>
|
||||
<fields>
|
||||
<field name="Reference">U</field>
|
||||
<field name="Value">HW678_N16R8</field>
|
||||
<field name="Footprint">Carrier:HW678_2x22_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">HW-678 V0.0.0 / N16R8 carrier interface; project numbering; onboard USB/regulator retained</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="3V3" type="power_out"/>
|
||||
<pin num="2" name="3V3" type="passive"/>
|
||||
<pin num="3" name="EN" type="input"/>
|
||||
<pin num="4" name="GPIO4" type="input"/>
|
||||
<pin num="5" name="GPIO5" type="input"/>
|
||||
<pin num="6" name="GPIO6" type="input"/>
|
||||
<pin num="7" name="GPIO7" type="output"/>
|
||||
<pin num="8" name="GPIO15" type="output"/>
|
||||
<pin num="9" name="GPIO16" type="input"/>
|
||||
<pin num="10" name="GPIO17" type="output"/>
|
||||
<pin num="11" name="GPIO18" type="input"/>
|
||||
<pin num="12" name="GPIO8" type="input"/>
|
||||
<pin num="13" name="GPIO3" type="bidirectional"/>
|
||||
<pin num="14" name="GPIO46" type="bidirectional"/>
|
||||
<pin num="15" name="GPIO9" type="bidirectional"/>
|
||||
<pin num="16" name="GPIO10" type="input"/>
|
||||
<pin num="17" name="GPIO11" type="bidirectional"/>
|
||||
<pin num="18" name="GPIO12" type="output"/>
|
||||
<pin num="19" name="GPIO13" type="input"/>
|
||||
<pin num="20" name="GPIO14" type="input"/>
|
||||
<pin num="21" name="5V" type="passive"/>
|
||||
<pin num="22" name="GND" type="power_out"/>
|
||||
<pin num="23" name="GND" type="passive"/>
|
||||
<pin num="24" name="GPIO43" type="bidirectional"/>
|
||||
<pin num="25" name="GPIO44" type="bidirectional"/>
|
||||
<pin num="26" name="GPIO1" type="bidirectional"/>
|
||||
<pin num="27" name="GPIO2" type="bidirectional"/>
|
||||
<pin num="28" name="GPIO42" type="bidirectional"/>
|
||||
<pin num="29" name="GPIO41" type="bidirectional"/>
|
||||
<pin num="30" name="GPIO40" type="bidirectional"/>
|
||||
<pin num="31" name="GPIO39" type="bidirectional"/>
|
||||
<pin num="32" name="GPIO38" type="bidirectional"/>
|
||||
<pin num="33" name="GPIO37" type="bidirectional"/>
|
||||
<pin num="34" name="GPIO36" type="bidirectional"/>
|
||||
<pin num="35" name="GPIO35" type="bidirectional"/>
|
||||
<pin num="36" name="GPIO0" type="bidirectional"/>
|
||||
<pin num="37" name="GPIO45" type="bidirectional"/>
|
||||
<pin num="38" name="GPIO48" type="bidirectional"/>
|
||||
<pin num="39" name="GPIO47" type="bidirectional"/>
|
||||
<pin num="40" name="GPIO21" type="bidirectional"/>
|
||||
<pin num="41" name="GPIO20" type="bidirectional"/>
|
||||
<pin num="42" name="GPIO19" type="bidirectional"/>
|
||||
<pin num="43" name="GND" type="passive"/>
|
||||
<pin num="44" name="GND" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="MAX3243_Module_Reference">
|
||||
<description>Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE</description>
|
||||
<fields>
|
||||
<field name="Reference">U</field>
|
||||
<field name="Value">MAX3243_Module_Reference</field>
|
||||
<field name="Footprint"/>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Adafruit male 6253 intended; physical numbering from published female 5988 CAD, VERIFY MALE</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="Vin" type="power_in"/>
|
||||
<pin num="2" name="GND" type="power_in"/>
|
||||
<pin num="3" name="DCD" type="output"/>
|
||||
<pin num="4" name="RX" type="output"/>
|
||||
<pin num="5" name="TX" type="input"/>
|
||||
<pin num="6" name="DTR" type="input"/>
|
||||
<pin num="7" name="DSR" type="output"/>
|
||||
<pin num="8" name="RTS" type="input"/>
|
||||
<pin num="9" name="CTS" type="output"/>
|
||||
<pin num="10" name="RI" type="output"/>
|
||||
<pin num="11" name="VLD" type="output"/>
|
||||
<pin num="12" name="OFF_N" type="input"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="OLED_I2C_26mm">
|
||||
<description>User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA</description>
|
||||
<fields>
|
||||
<field name="Reference">DS</field>
|
||||
<field name="Value">OLED_I2C_26mm</field>
|
||||
<field name="Footprint">Carrier:OLED_26mm_I2C_Provisional</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">User fit-verified 26 mm OLED; front/top header GND VCC SCL SDA</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="GND" type="power_in"/>
|
||||
<pin num="2" name="VCC" type="power_in"/>
|
||||
<pin num="3" name="SCL" type="input"/>
|
||||
<pin num="4" name="SDA" type="bidirectional"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="R">
|
||||
<description>Resistor, axial DIN0207 footprint; exact MPN pending</description>
|
||||
<fields>
|
||||
<field name="Reference">R</field>
|
||||
<field name="Value">R</field>
|
||||
<field name="Footprint">Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Resistor, axial DIN0207 footprint; exact MPN pending</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="" type="passive"/>
|
||||
<pin num="2" name="" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
<libpart lib="Carrier" part="SW_NO">
|
||||
<description>Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs</description>
|
||||
<fields>
|
||||
<field name="Reference">SW</field>
|
||||
<field name="Value">SW_NO</field>
|
||||
<field name="Footprint">Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x</field>
|
||||
<field name="Datasheet"/>
|
||||
<field name="Description">Normally open contact; footprint duplicates pads 1 and 2 for internally common terminal pairs</field>
|
||||
</fields>
|
||||
<pins>
|
||||
<pin num="1" name="" type="passive"/>
|
||||
<pin num="2" name="" type="passive"/>
|
||||
</pins>
|
||||
</libpart>
|
||||
</libparts>
|
||||
<libraries>
|
||||
<library logical="Carrier">
|
||||
<uri>${KIPRJMOD}/Carrier.kicad_sym</uri>
|
||||
</library>
|
||||
</libraries>
|
||||
<nets>
|
||||
<net code="1" name="/+3V3" class="Default">
|
||||
<node ref="DS1" pin="2" pinfunction="VCC_2" pintype="power_in"/>
|
||||
<node ref="R1" pin="1" pintype="passive"/>
|
||||
<node ref="R2" pin="1" pintype="passive"/>
|
||||
<node ref="R3" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="1" pinfunction="3V3_1" pintype="power_out"/>
|
||||
<node ref="U1" pin="2" pinfunction="3V3_2" pintype="passive"/>
|
||||
<node ref="U2" pin="1" pinfunction="Vin_1" pintype="power_in"/>
|
||||
</net>
|
||||
<net code="2" name="/BTN_NEXT" class="Default">
|
||||
<node ref="R3" pin="2" pintype="passive"/>
|
||||
<node ref="SW3" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="20" pinfunction="GPIO14_20" pintype="input"/>
|
||||
</net>
|
||||
<net code="3" name="/BTN_PREVIOUS" class="Default">
|
||||
<node ref="R1" pin="2" pintype="passive"/>
|
||||
<node ref="SW1" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="16" pinfunction="GPIO10_16" pintype="input"/>
|
||||
</net>
|
||||
<net code="4" name="/BTN_SELECT" class="Default">
|
||||
<node ref="R2" pin="2" pintype="passive"/>
|
||||
<node ref="SW2" pin="1" pintype="passive"/>
|
||||
<node ref="U1" pin="19" pinfunction="GPIO13_19" pintype="input"/>
|
||||
</net>
|
||||
<net code="5" name="/GND" class="Default">
|
||||
<node ref="DS1" pin="1" pinfunction="GND_1" pintype="power_in"/>
|
||||
<node ref="SW1" pin="2" pintype="passive"/>
|
||||
<node ref="SW2" pin="2" pintype="passive"/>
|
||||
<node ref="SW3" pin="2" pintype="passive"/>
|
||||
<node ref="U1" pin="22" pinfunction="GND_22" pintype="power_out"/>
|
||||
<node ref="U1" pin="23" pinfunction="GND_23" pintype="passive"/>
|
||||
<node ref="U1" pin="43" pinfunction="GND_43" pintype="passive"/>
|
||||
<node ref="U1" pin="44" pinfunction="GND_44" pintype="passive"/>
|
||||
<node ref="U2" pin="2" pinfunction="GND_2" pintype="power_in"/>
|
||||
</net>
|
||||
<net code="6" name="/OLED_SCL" class="Default">
|
||||
<node ref="DS1" pin="3" pinfunction="SCL_3" pintype="input"/>
|
||||
<node ref="U1" pin="18" pinfunction="GPIO12_18" pintype="output"/>
|
||||
</net>
|
||||
<net code="7" name="/OLED_SDA" class="Default">
|
||||
<node ref="DS1" pin="4" pinfunction="SDA_4" pintype="bidirectional"/>
|
||||
<node ref="U1" pin="17" pinfunction="GPIO11_17" pintype="bidirectional"/>
|
||||
</net>
|
||||
<net code="8" name="/RS_CTS" class="Default">
|
||||
<node ref="U1" pin="9" pinfunction="GPIO16_9" pintype="input"/>
|
||||
<node ref="U2" pin="9" pinfunction="CTS_9" pintype="output"/>
|
||||
</net>
|
||||
<net code="9" name="/RS_DCD" class="Default">
|
||||
<node ref="U1" pin="4" pinfunction="GPIO4_4" pintype="input"/>
|
||||
<node ref="U2" pin="3" pinfunction="DCD_3" pintype="output"/>
|
||||
</net>
|
||||
<net code="10" name="/RS_DSR" class="Default">
|
||||
<node ref="U1" pin="5" pinfunction="GPIO5_5" pintype="input"/>
|
||||
<node ref="U2" pin="7" pinfunction="DSR_7" pintype="output"/>
|
||||
</net>
|
||||
<net code="11" name="/RS_DTR" class="Default">
|
||||
<node ref="U1" pin="7" pinfunction="GPIO7_7" pintype="output"/>
|
||||
<node ref="U2" pin="6" pinfunction="DTR_6" pintype="input"/>
|
||||
</net>
|
||||
<net code="12" name="/RS_OFF_N" class="Default">
|
||||
<node ref="U1" pin="15" pinfunction="GPIO9_15" pintype="bidirectional"/>
|
||||
<node ref="U2" pin="12" pinfunction="OFF_N_12" pintype="input"/>
|
||||
</net>
|
||||
<net code="13" name="/RS_RI" class="Default">
|
||||
<node ref="U1" pin="6" pinfunction="GPIO6_6" pintype="input"/>
|
||||
<node ref="U2" pin="10" pinfunction="RI_10" pintype="output"/>
|
||||
</net>
|
||||
<net code="14" name="/RS_RTS" class="Default">
|
||||
<node ref="U1" pin="8" pinfunction="GPIO15_8" pintype="output"/>
|
||||
<node ref="U2" pin="8" pinfunction="RTS_8" pintype="input"/>
|
||||
</net>
|
||||
<net code="15" name="/RS_RX" class="Default">
|
||||
<node ref="U1" pin="11" pinfunction="GPIO18_11" pintype="input"/>
|
||||
<node ref="U2" pin="4" pinfunction="RX_4" pintype="output"/>
|
||||
</net>
|
||||
<net code="16" name="/RS_TX" class="Default">
|
||||
<node ref="U1" pin="10" pinfunction="GPIO17_10" pintype="output"/>
|
||||
<node ref="U2" pin="5" pinfunction="TX_5" pintype="input"/>
|
||||
</net>
|
||||
<net code="17" name="/RS_VALID" class="Default">
|
||||
<node ref="U1" pin="12" pinfunction="GPIO8_12" pintype="input"/>
|
||||
<node ref="U2" pin="11" pinfunction="VLD_11" pintype="output"/>
|
||||
</net>
|
||||
<net code="18" name="unconnected-(U1-5V-Pad21)" class="Default">
|
||||
<node ref="U1" pin="21" pinfunction="5V_21" pintype="passive+no_connect"/>
|
||||
</net>
|
||||
<net code="19" name="unconnected-(U1-EN-Pad3)" class="Default">
|
||||
<node ref="U1" pin="3" pinfunction="EN_3" pintype="input+no_connect"/>
|
||||
</net>
|
||||
<net code="20" name="unconnected-(U1-GPIO0-Pad36)" class="Default">
|
||||
<node ref="U1" pin="36" pinfunction="GPIO0_36" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="21" name="unconnected-(U1-GPIO1-Pad26)" class="Default">
|
||||
<node ref="U1" pin="26" pinfunction="GPIO1_26" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="22" name="unconnected-(U1-GPIO2-Pad27)" class="Default">
|
||||
<node ref="U1" pin="27" pinfunction="GPIO2_27" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="23" name="unconnected-(U1-GPIO3-Pad13)" class="Default">
|
||||
<node ref="U1" pin="13" pinfunction="GPIO3_13" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="24" name="unconnected-(U1-GPIO19-Pad42)" class="Default">
|
||||
<node ref="U1" pin="42" pinfunction="GPIO19_42" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="25" name="unconnected-(U1-GPIO20-Pad41)" class="Default">
|
||||
<node ref="U1" pin="41" pinfunction="GPIO20_41" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="26" name="unconnected-(U1-GPIO21-Pad40)" class="Default">
|
||||
<node ref="U1" pin="40" pinfunction="GPIO21_40" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="27" name="unconnected-(U1-GPIO35-Pad35)" class="Default">
|
||||
<node ref="U1" pin="35" pinfunction="GPIO35_35" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="28" name="unconnected-(U1-GPIO36-Pad34)" class="Default">
|
||||
<node ref="U1" pin="34" pinfunction="GPIO36_34" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="29" name="unconnected-(U1-GPIO37-Pad33)" class="Default">
|
||||
<node ref="U1" pin="33" pinfunction="GPIO37_33" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="30" name="unconnected-(U1-GPIO38-Pad32)" class="Default">
|
||||
<node ref="U1" pin="32" pinfunction="GPIO38_32" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="31" name="unconnected-(U1-GPIO39-Pad31)" class="Default">
|
||||
<node ref="U1" pin="31" pinfunction="GPIO39_31" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="32" name="unconnected-(U1-GPIO40-Pad30)" class="Default">
|
||||
<node ref="U1" pin="30" pinfunction="GPIO40_30" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="33" name="unconnected-(U1-GPIO41-Pad29)" class="Default">
|
||||
<node ref="U1" pin="29" pinfunction="GPIO41_29" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="34" name="unconnected-(U1-GPIO42-Pad28)" class="Default">
|
||||
<node ref="U1" pin="28" pinfunction="GPIO42_28" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="35" name="unconnected-(U1-GPIO43-Pad24)" class="Default">
|
||||
<node ref="U1" pin="24" pinfunction="GPIO43_24" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="36" name="unconnected-(U1-GPIO44-Pad25)" class="Default">
|
||||
<node ref="U1" pin="25" pinfunction="GPIO44_25" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="37" name="unconnected-(U1-GPIO45-Pad37)" class="Default">
|
||||
<node ref="U1" pin="37" pinfunction="GPIO45_37" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="38" name="unconnected-(U1-GPIO46-Pad14)" class="Default">
|
||||
<node ref="U1" pin="14" pinfunction="GPIO46_14" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="39" name="unconnected-(U1-GPIO47-Pad39)" class="Default">
|
||||
<node ref="U1" pin="39" pinfunction="GPIO47_39" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
<net code="40" name="unconnected-(U1-GPIO48-Pad38)" class="Default">
|
||||
<node ref="U1" pin="38" pinfunction="GPIO48_38" pintype="bidirectional+no_connect"/>
|
||||
</net>
|
||||
</nets>
|
||||
</export>
|
||||
@@ -1,8 +0,0 @@
|
||||
Native schematic validation passed.
|
||||
KiCad ERC: zero reported errors/warnings; no project exclusions added.
|
||||
17 connected nets, 9 components; exact net memberships verified.
|
||||
15 firmware GPIO definitions independently checked against exported pin functions.
|
||||
All 44 MCU header pins accounted for; USB/UART0/5V/PSRAM pins unused on carrier.
|
||||
PDF and SVG exported successfully.
|
||||
Not validated: male-module physical mapping, power budget/path, OLED pull-ups,
|
||||
selected connector fit, PCB DRC/layout, procurement, or assembled hardware.
|
||||
@@ -2,7 +2,11 @@
|
||||
default_envs = esp32-s3-devkitc-1-n16r8
|
||||
|
||||
[env:esp32-s3-devkitc-1-n16r8]
|
||||
platform = platformio/espressif32@6.12.0
|
||||
platform = platformio/espressif32@6.13.0
|
||||
platform_packages =
|
||||
platformio/framework-espidf@3.50503.0
|
||||
platformio/toolchain-xtensa-esp-elf@14.2.0+20251107
|
||||
platformio/toolchain-riscv32-esp@14.2.0+20251107
|
||||
board = esp32-s3-devkitc-1-n16r8
|
||||
framework = espidf
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ CONFIG_ESP_WIFI_RX_BA_WIN=6
|
||||
CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4
|
||||
|
||||
# Keep concurrent HTTPS handshakes from exhausting scarce internal DRAM.
|
||||
# Active TLS material remains unencrypted in PSRAM; physical extraction is outside scope.
|
||||
# Active TLS material remains unencrypted in PSRAM; physical extraction is outside
|
||||
# the supported threat model (docs/security_hardening.md).
|
||||
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
|
||||
# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set
|
||||
# Hardware AES can hang in the PSRAM DMA path and cannot be shared safely with
|
||||
@@ -39,9 +40,6 @@ CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_HTTPD_WS_SUPPORT=y
|
||||
# Reserve capacity for HTTPS/WebSocket clients plus two bounded SSH sessions.
|
||||
CONFIG_LWIP_MAX_SOCKETS=16
|
||||
# One dual-stack listener per service; SLAAC is enabled on STA by its owner.
|
||||
CONFIG_LWIP_IPV4=y
|
||||
CONFIG_LWIP_IPV6=y
|
||||
# Keep work submission bounded; one-second socket timeouts limit shared-task stalls.
|
||||
# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set
|
||||
|
||||
@@ -57,7 +55,7 @@ CONFIG_ESP_WOLFSSL_NO_STACK_SIZE_BUILD_WARNING=y
|
||||
# Keep configurable hostname discovery STA-only and bounded. The responder task
|
||||
# remains internal; general mDNS metadata prefers PSRAM to protect internal heap.
|
||||
CONFIG_MDNS_MAX_INTERFACES=1
|
||||
CONFIG_MDNS_MAX_SERVICES=2
|
||||
CONFIG_MDNS_MAX_SERVICES=1
|
||||
CONFIG_MDNS_PREDEF_NETIF_STA=y
|
||||
# CONFIG_MDNS_PREDEF_NETIF_AP is not set
|
||||
# CONFIG_MDNS_PREDEF_NETIF_ETH is not set
|
||||
@@ -76,7 +74,21 @@ CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y
|
||||
# Keep diagnostic and interactive-console logging concise but useful.
|
||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||
|
||||
# Intermittent-use baseline: avoid secret-bearing crash dumps/register output.
|
||||
# Ordinary logging remains enabled; this is not physical-memory protection.
|
||||
# Crash memory/registers can contain credentials and serial payloads. Keep the
|
||||
# reserved coredump partition unused; enforce resolved settings at compile time.
|
||||
CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y
|
||||
# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set
|
||||
# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set
|
||||
CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y
|
||||
# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set
|
||||
# CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT is not set
|
||||
# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set
|
||||
# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set
|
||||
# CONFIG_ESP_DEBUG_OCDAWARE is not set
|
||||
# CONFIG_FREERTOS_DEBUG_OCDAWARE is not set
|
||||
|
||||
# Audited allocation extents and TLS record retirement used by Phase 9C.
|
||||
CONFIG_HEAP_POISONING_DISABLED=y
|
||||
# CONFIG_HEAP_POISONING_LIGHT is not set
|
||||
# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set
|
||||
# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set
|
||||
|
||||
@@ -6,6 +6,7 @@ idf_component_register(
|
||||
"network_console.c"
|
||||
"system_console.c"
|
||||
"secure_random.c"
|
||||
"security_build_policy.c"
|
||||
"status_led.c"
|
||||
"local_display.c"
|
||||
"local_boot_animation.c"
|
||||
@@ -24,6 +25,9 @@ idf_component_register(
|
||||
"admin_command_gate.c"
|
||||
"admin_ssh_console.c"
|
||||
"ssh_transport.c"
|
||||
"ssh_auth_policy.c"
|
||||
"ssh_memory.c"
|
||||
"ssh_protocol_policy.c"
|
||||
"ssh_console.c"
|
||||
"usb_cdc_transport.c"
|
||||
"usb_console.c"
|
||||
@@ -38,7 +42,6 @@ idf_component_register(
|
||||
"web_broker_settings.c"
|
||||
"web_ssh_settings.c"
|
||||
"web_lifecycle_settings.c"
|
||||
"web_firmware_update.c"
|
||||
"web_admin_tickets.c"
|
||||
"web_admin_transport.c"
|
||||
"web_assets_data.c"
|
||||
@@ -60,7 +63,6 @@ idf_component_register(
|
||||
"mdns_console.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES
|
||||
app_update
|
||||
bootloader_support
|
||||
console
|
||||
esp_driver_gpio
|
||||
|
||||
@@ -69,6 +69,7 @@ typedef struct {
|
||||
bool discard_next_lf;
|
||||
admin_prompt_state_t prompt_state;
|
||||
bool prompt_hidden;
|
||||
bool prompt_rejected;
|
||||
size_t prompt_capacity;
|
||||
size_t prompt_length;
|
||||
uint8_t prompt_input[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
@@ -385,6 +386,7 @@ esp_err_t admin_ssh_console_dispatch_read_input(
|
||||
session->prompt_length = 0U;
|
||||
session->prompt_capacity = capacity;
|
||||
session->prompt_hidden = hidden;
|
||||
session->prompt_rejected = false;
|
||||
session->prompt_state = ADMIN_PROMPT_WAITING;
|
||||
bool published = append_output_locked(session, (const uint8_t *)prompt, strlen(prompt));
|
||||
if (!published) {
|
||||
@@ -413,14 +415,19 @@ esp_err_t admin_ssh_console_dispatch_read_input(
|
||||
if (!current || !session->active || session->prompt_state == ADMIN_PROMPT_DISCONNECTED) {
|
||||
result = ESP_ERR_NOT_FOUND;
|
||||
} else if (session->prompt_state == ADMIN_PROMPT_SUBMITTED) {
|
||||
if (session->prompt_rejected) {
|
||||
result = ESP_ERR_INVALID_SIZE;
|
||||
} else {
|
||||
memcpy(output, session->prompt_input, session->prompt_length);
|
||||
*output_length = session->prompt_length;
|
||||
result = ESP_OK;
|
||||
}
|
||||
}
|
||||
secure_wipe(session->prompt_input, sizeof(session->prompt_input));
|
||||
session->prompt_length = 0U;
|
||||
session->prompt_capacity = 0U;
|
||||
session->prompt_hidden = false;
|
||||
session->prompt_rejected = false;
|
||||
session->prompt_state = ADMIN_PROMPT_NONE;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return result;
|
||||
@@ -556,9 +563,9 @@ static bool remote_command_allowed(const admin_request_t *request)
|
||||
strcmp(argv[1], "recover") == 0) {
|
||||
allowed = false;
|
||||
}
|
||||
/* Classify parsed canonical arguments, not raw prefixes. Wi-Fi uses the
|
||||
* canonical handler, including hidden prompts and disruptive changes;
|
||||
* unrelated browser restrictions remain narrower than UART0/SSH.
|
||||
/* Temporary browser policy until lifecycle acknowledgements/revocation are
|
||||
* coordinated (8D.7). Classify parsed canonical arguments, not raw prefixes.
|
||||
* User mutations remain available through UART0/SSH, subject to their policy.
|
||||
*/
|
||||
if (request->token.transport == ADMIN_CONSOLE_TRANSPORT_WEB && argc > 0U) {
|
||||
if (strcmp(argv[0], "web") == 0) {
|
||||
@@ -566,7 +573,7 @@ static bool remote_command_allowed(const admin_request_t *request)
|
||||
strcmp(argv[1], "stop") == 0)) ||
|
||||
(argc == 4U && strcmp(argv[1], "certificate") == 0 &&
|
||||
strcmp(argv[2], "rotate") == 0 && strcmp(argv[3], "--force") == 0);
|
||||
} else if (strcmp(argv[0], "mdns") == 0) {
|
||||
} else if (strcmp(argv[0], "wifi") == 0 || strcmp(argv[0], "mdns") == 0) {
|
||||
allowed = argc == 2U && strcmp(argv[1], "status") == 0;
|
||||
} else if (strcmp(argv[0], "user") == 0) {
|
||||
allowed = admin_ssh_console_web_user_command_allowed(
|
||||
@@ -1209,9 +1216,14 @@ bool admin_ssh_console_feed_input(const admin_ssh_console_token_t *token,
|
||||
if (!session->prompt_hidden) {
|
||||
(void)append_output_locked(session, &value, 1U);
|
||||
}
|
||||
} else if (session->prompt_hidden) {
|
||||
session->prompt_rejected = true;
|
||||
} else {
|
||||
(void)append_output_locked(session, (const uint8_t *)"\a", 1U);
|
||||
}
|
||||
} else if (session->prompt_hidden) {
|
||||
/* Do not silently normalize unrepresentable credential bytes. */
|
||||
session->prompt_rejected = true;
|
||||
}
|
||||
}
|
||||
++*consumed;
|
||||
|
||||
@@ -43,6 +43,7 @@ static esp_err_t read_input(const char *prompt, uint8_t *output, size_t capacity
|
||||
return error;
|
||||
}
|
||||
|
||||
bool rejected = false;
|
||||
for (;;) {
|
||||
uint8_t byte = 0U;
|
||||
if (uart_read_bytes(CONSOLE_INPUT_UART, &byte, 1U, portMAX_DELAY) != 1) {
|
||||
@@ -71,6 +72,11 @@ static esp_err_t read_input(const char *prompt, uint8_t *output, size_t capacity
|
||||
continue;
|
||||
}
|
||||
if (byte < 0x20U || byte > 0x7eU || *output_length >= capacity - 1U) {
|
||||
/* Hidden credentials must never accept a truncated/normalized prefix. */
|
||||
if (hidden) {
|
||||
rejected = true;
|
||||
continue;
|
||||
}
|
||||
putchar('\a');
|
||||
fflush(stdout);
|
||||
continue;
|
||||
@@ -82,6 +88,11 @@ static esp_err_t read_input(const char *prompt, uint8_t *output, size_t capacity
|
||||
}
|
||||
}
|
||||
putchar('\n');
|
||||
if (rejected) {
|
||||
secure_wipe(output, capacity);
|
||||
*output_length = 0U;
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/* Hidden input accepts printable ASCII with CR/LF submit, BS/DEL editing and
|
||||
* Ctrl-C cancellation. Overflow or any other byte rejects the entire prompt on
|
||||
* submit (ESP_ERR_INVALID_SIZE), even after editing; rejected input is wiped.
|
||||
* capacity includes the trailing NUL. Visible line editing is unchanged. */
|
||||
esp_err_t console_input_read_hidden(const char *prompt,
|
||||
uint8_t *output, size_t capacity,
|
||||
size_t minimum_length, size_t maximum_length,
|
||||
|
||||