emdashes begone (#10847)
This commit is contained in:
@@ -26,7 +26,7 @@ static constexpr const char *PROTOBUF_SCHEMA =
|
||||
// PhoneAPI subclass for the Ethernet HTTP transport. Mirrors mesh/http/HttpAPI
|
||||
// but lives outside the MESHTASTIC_EXCLUDE_WEBSERVER gate (which is ESP32-only).
|
||||
// A single instance is shared between the HTTP and HTTPS servers since they
|
||||
// represent the same logical "phone" — same state machine, same packet queue.
|
||||
// represent the same logical "phone" - same state machine, same packet queue.
|
||||
class EthHttpAPI : public PhoneAPI
|
||||
{
|
||||
public:
|
||||
@@ -192,7 +192,7 @@ static bool handleFromRadio(IStreamReadWrite &client, const Request &req)
|
||||
// Buffer all packets first so we can emit an accurate Content-Length and
|
||||
// keep the connection alive. Phase 2 used Connection: close framing, which
|
||||
// forced clients to redo the TLS handshake (~625 ms) for every single
|
||||
// /fromradio poll — client.meshtastic.org needs dozens of those during
|
||||
// /fromradio poll - client.meshtastic.org needs dozens of those during
|
||||
// initial sync, so the user-visible load time was 15-30 s of pure
|
||||
// handshakes. With Content-Length + keep-alive a whole sync rides one
|
||||
// handshake. Buffer is dynamic (std::vector) so the common 1-packet case
|
||||
@@ -290,7 +290,7 @@ void handleApiClient(IStreamReadWrite &client)
|
||||
// the parent OSThread is not returning to mainController, and the
|
||||
// RP2350 hardware watchdog (8 s default in arduino-pico) only gets
|
||||
// pet by the main loop. A client.meshtastic.org sync produces ~80
|
||||
// back-to-back requests over a single TLS session — well past the
|
||||
// back-to-back requests over a single TLS session - well past the
|
||||
// watchdog deadline. yield() between requests lets the rest of core0
|
||||
// (Periodic ticks, NTP, MQTT, LoRa packet pump) run + pets the
|
||||
// watchdog; the cap puts a hard ceiling so a chatty client can never
|
||||
@@ -320,7 +320,7 @@ void handleApiClient(IStreamReadWrite &client)
|
||||
keepAlive = handleToRadio(client, req);
|
||||
} else {
|
||||
sendError(client, 404, "Not Found", "unknown endpoint");
|
||||
return; // errors are terminal — Connection: close framing
|
||||
return; // errors are terminal - Connection: close framing
|
||||
}
|
||||
// A handler that emitted an error advertised Connection: close. Stop the
|
||||
// keep-alive loop so any unread/leftover body bytes (e.g. after a 408
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
// transports without recompiling the handlers.
|
||||
//
|
||||
// Inherits Print so all `print(int)`, `print(const char *)`, `print(char)`
|
||||
// helpers are available for free — the only thing implementations have to
|
||||
// helpers are available for free - the only thing implementations have to
|
||||
// supply on the write side is `write(uint8_t)` + the bulk `write(buf, len)`.
|
||||
class IStreamReadWrite : public Print
|
||||
{
|
||||
public:
|
||||
virtual ~IStreamReadWrite() = default;
|
||||
|
||||
// Write side — Print pure virtual + bulk override
|
||||
// Write side - Print pure virtual + bulk override
|
||||
size_t write(uint8_t b) override = 0;
|
||||
size_t write(const uint8_t *buf, size_t len) override = 0;
|
||||
using Print::write; // bring in write(const char *str) and friends
|
||||
@@ -34,7 +34,7 @@ class IStreamReadWrite : public Print
|
||||
virtual bool connected() = 0;
|
||||
void flush() override = 0; // Print::flush is virtual void with empty default
|
||||
|
||||
// Logging helper — used by request log line
|
||||
// Logging helper - used by request log line
|
||||
virtual IPAddress remoteIP() = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ static EthApiServerThread *apiThread = nullptr;
|
||||
|
||||
void initEthApiServer()
|
||||
{
|
||||
// Bind the listener (idempotent — deInitEthApiServer() drops apiServer on a
|
||||
// Bind the listener (idempotent - deInitEthApiServer() drops apiServer on a
|
||||
// W5500 reset, and this rebinds it on the restart path).
|
||||
if (!apiServer) {
|
||||
apiServer = new EthernetServer(ETH_API_PORT);
|
||||
@@ -92,7 +92,7 @@ void initEthApiServer()
|
||||
}
|
||||
// The worker is created once and kept for the lifetime of the process. It
|
||||
// idles harmlessly while apiServer is null (runOnce guards on it), so we
|
||||
// never delete it from another thread's runOnce — that would corrupt the
|
||||
// never delete it from another thread's runOnce - that would corrupt the
|
||||
// scheduler's thread list mid-iteration.
|
||||
if (!apiThread)
|
||||
apiThread = new EthApiServerThread(); // OSThread base auto-registers with the scheduler
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
/// Initialize the Ethernet HTTP API server (call after Ethernet is connected).
|
||||
/// Spawns an internal OSThread that polls accept() on a sub-second cadence,
|
||||
/// independent of the 5s Ethernet client periodic — needed because the web
|
||||
/// independent of the 5s Ethernet client periodic - needed because the web
|
||||
/// client makes many small back-to-back requests.
|
||||
void initEthApiServer();
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ static constexpr const char *CERT_PATH = "/eth_cert_v2.der";
|
||||
static constexpr const char *KEY_PATH = "/eth_key_v2.der";
|
||||
static constexpr const char *IP_PATH = "/eth_cert_ip_v2.txt";
|
||||
|
||||
// Random callback for mbedtls — sources entropy from the RP2350 ROSC TRNG via
|
||||
// Random callback for mbedtls - sources entropy from the RP2350 ROSC TRNG via
|
||||
// pico-sdk get_rand_64(). Used directly as f_rng in mbedtls calls so we don't
|
||||
// have to plumb a full mbedtls_entropy_context + ctr_drbg. The hardware TRNG
|
||||
// is cryptographically suitable per pico-sdk docs (ROSC + whitening).
|
||||
@@ -175,7 +175,7 @@ static bool generateCert(IPAddress ip, EthCertMaterial &out)
|
||||
}
|
||||
|
||||
// ExtendedKeyUsage: serverAuth. NSS / Firefox refuse to treat a cert
|
||||
// as a TLS server cert without this extension since 2023 — the error
|
||||
// as a TLS server cert without this extension since 2023 - the error
|
||||
// surfaces as a non-overridable "Secure Connection Failed" with no
|
||||
// "Accept the Risk" path.
|
||||
mbedtls_asn1_sequence ekuSeq;
|
||||
@@ -293,13 +293,13 @@ bool ensureCertForIp(IPAddress ip, EthCertMaterial &out)
|
||||
// current process still has the in-memory cert ready for use.
|
||||
//
|
||||
// Clear the IP commit-marker FIRST so a reset mid-write can't leave the marker
|
||||
// pointing at a half-written (or stale-paired) cert/key — the load path only
|
||||
// pointing at a half-written (or stale-paired) cert/key - the load path only
|
||||
// trusts the cache when the marker matches. Writing the marker LAST commits the
|
||||
// new pair atomically w.r.t. the loader.
|
||||
writeText(IP_PATH, "");
|
||||
if (!writeBinary(CERT_PATH, out.certDer.data(), out.certDer.size()) ||
|
||||
!writeBinary(KEY_PATH, out.keyDer.data(), out.keyDer.size()) || !writeText(IP_PATH, ipStr)) {
|
||||
LOG_WARN("ETH CERT: persist failed — will regenerate next boot");
|
||||
LOG_WARN("ETH CERT: persist failed - will regenerate next boot");
|
||||
} else {
|
||||
LOG_INFO("ETH CERT: persisted to LittleFS");
|
||||
}
|
||||
@@ -310,7 +310,7 @@ bool ensureCertForIp(IPAddress ip, EthCertMaterial &out)
|
||||
// Worker that defers cert gen off the Periodic thread (which has a tight stack
|
||||
// and ticks every 5s alongside reconnect / NTP / MQTT). Waits for a non-zero IP,
|
||||
// generates/loads the cert for it, then keeps polling at CERT_RECHECK_MS so a
|
||||
// DHCP lease change to a new IP regenerates the cert — the SAN must track the
|
||||
// DHCP lease change to a new IP regenerates the cert - the SAN must track the
|
||||
// current address or browsers reject the new one. The steady-state poll is just
|
||||
// localIP() + compare; ECDSA keygen only reruns when the IP actually changes,
|
||||
// and it runs on this thread's own stack (not the Periodic's).
|
||||
@@ -337,7 +337,7 @@ class EthCertThread : public concurrency::OSThread
|
||||
// regenerates whenever its saved IP != ip, so the cert SAN follows.
|
||||
bool ok = ensureCertForIp(ip, material_);
|
||||
if (!ok) {
|
||||
LOG_ERROR("ETH CERT: pipeline FAILED — TLS server will not start");
|
||||
LOG_ERROR("ETH CERT: pipeline FAILED - TLS server will not start");
|
||||
// Don't leave isReady() reporting true with empty material: a later TLS
|
||||
// teardown (e.g. a W5500 reset) would then fail initTlsContext() and stay
|
||||
// disabled. Clear readiness so the TLS worker waits and the next poll
|
||||
|
||||
@@ -35,7 +35,7 @@ bool isEthCertReady();
|
||||
// Snapshot of the generated material once isEthCertReady(). Empty otherwise.
|
||||
const EthCertMaterial &getEthCert();
|
||||
|
||||
// Monotonic counter bumped each time the cert is (re)generated — e.g. when a DHCP
|
||||
// Monotonic counter bumped each time the cert is (re)generated - e.g. when a DHCP
|
||||
// lease change moves us to a new IP. The TLS server reloads when this changes.
|
||||
uint32_t getEthCertGeneration();
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "mesh/eth/ethTlsApiServer.h"
|
||||
#endif
|
||||
#ifdef USE_ARDUINO_ETHERNET
|
||||
#include <Ethernet.h> // arduino-libraries/Ethernet — supports W5100/W5200/W5500
|
||||
#include <Ethernet.h> // arduino-libraries/Ethernet - supports W5100/W5200/W5500
|
||||
// Shorter DHCP timeout so LoRa startup isn't blocked when no DHCP server is present.
|
||||
#define ETH_DHCP_TIMEOUT_MS 10000
|
||||
#else
|
||||
@@ -181,12 +181,12 @@ static int32_t reconnectETH()
|
||||
initEthApiServer();
|
||||
#endif
|
||||
#if HAS_ETHERNET && defined(HAS_ETHERNET_TLS_API) && defined(ARCH_RP2040)
|
||||
// Phase 2.1-bis — cert gen runs on its own OSThread so ECDSA keygen
|
||||
// Phase 2.1-bis - cert gen runs on its own OSThread so ECDSA keygen
|
||||
// + DER encoding + LittleFS write don't share the Periodic stack
|
||||
// (which overflowed in the original inline attempt). The thread
|
||||
// polls for a non-zero IP itself and runs once.
|
||||
initEthCertThread();
|
||||
// Phase 2.2 — TLS server skeleton on TCP/443. The worker waits
|
||||
// Phase 2.2 - TLS server skeleton on TCP/443. The worker waits
|
||||
// until the cert thread signals isEthCertReady() before binding.
|
||||
initEthTlsApiServer();
|
||||
#endif
|
||||
@@ -220,7 +220,7 @@ static int32_t reconnectETH()
|
||||
#if HAS_ETHERNET && defined(HAS_ETHERNET_OTA)
|
||||
ethOTALoop();
|
||||
#endif
|
||||
// ethApiServer runs on its own OSThread (20ms ticks) — not polled here.
|
||||
// ethApiServer runs on its own OSThread (20ms ticks) - not polled here.
|
||||
|
||||
return 5000; // every 5 seconds
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ static const uint32_t OTA_AUTH_COOLDOWN_MS = 5000; // 5s cooldown after failed a
|
||||
static const size_t OTA_NONCE_SIZE = 32;
|
||||
static const size_t OTA_HASH_SIZE = 32;
|
||||
|
||||
// OTA PSK — override via USERPREFS_OTA_PSK in userPrefs.jsonc
|
||||
// OTA PSK - override via USERPREFS_OTA_PSK in userPrefs.jsonc
|
||||
// USERPREFS_OTA_PSK is stringified by PlatformIO (wrapped in quotes), so we
|
||||
// use a char[] and sizeof-1 to exclude the trailing NUL byte from the hash.
|
||||
#ifdef USERPREFS_OTA_PSK
|
||||
@@ -96,7 +96,7 @@ static void computeAuthHash(const uint8_t *nonce, size_t nonceLen, const uint8_t
|
||||
/// Challenge-response authentication. Returns true if client is authenticated.
|
||||
static bool authenticateClient(EthernetClient &client)
|
||||
{
|
||||
// Rate-limit after failed auth — close silently so the error byte is not
|
||||
// Rate-limit after failed auth - close silently so the error byte is not
|
||||
// misinterpreted as part of the nonce by a re-trying client.
|
||||
if (lastAuthFailure != 0 && (millis() - lastAuthFailure) < OTA_AUTH_COOLDOWN_MS) {
|
||||
LOG_WARN("ETH OTA: Auth cooldown active, rejecting connection");
|
||||
@@ -140,7 +140,7 @@ static bool authenticateClient(EthernetClient &client)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Auth success — send ACK
|
||||
// Auth success - send ACK
|
||||
client.write(OTA_ACK);
|
||||
LOG_INFO("ETH OTA: Authentication successful");
|
||||
return true;
|
||||
@@ -179,14 +179,14 @@ static void handleOTAClient(EthernetClient &client)
|
||||
return;
|
||||
}
|
||||
|
||||
// Begin the update — this opens firmware.bin on LittleFS
|
||||
// Begin the update - this opens firmware.bin on LittleFS
|
||||
if (!Update.begin(hdr.firmwareSize)) {
|
||||
LOG_ERROR("ETH OTA: Update.begin() failed, error=%u", Update.getError());
|
||||
client.write(OTA_ERR_BEGIN);
|
||||
return;
|
||||
}
|
||||
|
||||
// ACK the header — client can start sending firmware data
|
||||
// ACK the header - client can start sending firmware data
|
||||
client.write(OTA_ACK);
|
||||
|
||||
// Receive firmware in chunks
|
||||
@@ -252,7 +252,7 @@ static void handleOTAClient(EthernetClient &client)
|
||||
return;
|
||||
}
|
||||
|
||||
// Finalize — this calls picoOTA.commit() which stages the update for the
|
||||
// Finalize - this calls picoOTA.commit() which stages the update for the
|
||||
// bootloader
|
||||
if (!Update.end(true)) {
|
||||
LOG_ERROR("ETH OTA: Update.end() failed, error=%u", Update.getError());
|
||||
@@ -265,7 +265,7 @@ static void handleOTAClient(EthernetClient &client)
|
||||
client.flush();
|
||||
delay(500);
|
||||
|
||||
// Reboot — the built-in bootloader will apply the update from LittleFS
|
||||
// Reboot - the built-in bootloader will apply the update from LittleFS
|
||||
rp2040.reboot();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ static constexpr uint32_t MEDIUM_THRESHOLD_MS = 30000;
|
||||
static constexpr int32_t ACTIVE_INTERVAL_MS = 20;
|
||||
static constexpr int32_t MEDIUM_INTERVAL_MS = 100;
|
||||
static constexpr int32_t IDLE_INTERVAL_MS = 500;
|
||||
// Matches the keep-alive idle window in ethApiHandlers — if the handler
|
||||
// Matches the keep-alive idle window in ethApiHandlers - if the handler
|
||||
// loop calls read() and netRecv blocked for 10 s, the 3 s idle deadline
|
||||
// inside parseRequest would be irrelevant and the OSThread would stay stuck
|
||||
// long after a quiet browser closed its end of the TCP socket.
|
||||
@@ -52,7 +52,7 @@ static int picoRand(void * /*ctx*/, unsigned char *out, size_t len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// One-shot TLS context lives in BSS — keeps mbedtls allocations off the
|
||||
// One-shot TLS context lives in BSS - keeps mbedtls allocations off the
|
||||
// OSThread stack (lesson from Phase 2.1-bis: stack budget is tight on M33).
|
||||
static EthernetServer *tlsServer = nullptr;
|
||||
static mbedtls_x509_crt certChain;
|
||||
@@ -73,7 +73,7 @@ static int netSend(void *ctx, const unsigned char *buf, size_t len)
|
||||
// Block-with-yield until the W5500 TX buffer can absorb the chunk.
|
||||
// Returning WANT_WRITE without delay made mbedtls_ssl_handshake() spin
|
||||
// at ~180k iter/s when Chrome was slow to drain the socket during the
|
||||
// ECDHE-ECDSA ServerKeyExchange — the original code logged exactly that
|
||||
// ECDHE-ECDSA ServerKeyExchange - the original code logged exactly that
|
||||
// signature (ret=-0x6880 / WANT_WRITE) tight-looping forever. Firefox
|
||||
// happened to read fast enough that the buffer never filled.
|
||||
uint32_t t0 = millis();
|
||||
@@ -96,12 +96,12 @@ static int netRecv(void *ctx, unsigned char *buf, size_t len)
|
||||
// Block-with-timeout: spin until bytes arrive, the peer closes, or we
|
||||
// exceed the per-recv budget. Pure non-blocking (return WANT_READ) would
|
||||
// require mbedtls_ssl_handshake to be driven from the runOnce dispatcher
|
||||
// — overkill for the Phase 2.2 skeleton with a single in-flight session.
|
||||
// - overkill for the Phase 2.2 skeleton with a single in-flight session.
|
||||
//
|
||||
// Pet the 8 s hardware watchdog from inside the poll loop. We sit here
|
||||
// for up to RECV_TIMEOUT_MS waiting for the next keep-alive request, and
|
||||
// a quiet client can string two such waits back-to-back (6 s) plus the
|
||||
// earlier handshake/handler time — easily past the watchdog deadline.
|
||||
// earlier handshake/handler time - easily past the watchdog deadline.
|
||||
// The main loop()'s watchdog_update() never runs while the OSThread is
|
||||
// inside serveClient(), so it has to be done here.
|
||||
uint32_t t0 = millis();
|
||||
@@ -152,7 +152,7 @@ class MbedTlsStream : public IStreamReadWrite
|
||||
size_t pending = mbedtls_ssl_get_bytes_avail(ssl_);
|
||||
if (pending > 0)
|
||||
return (int)pending;
|
||||
// Best-effort: report network bytes (rough proxy — handlers usually
|
||||
// Best-effort: report network bytes (rough proxy - handlers usually
|
||||
// call read() in a loop and tolerate slow streams).
|
||||
return client_->available();
|
||||
}
|
||||
@@ -197,7 +197,7 @@ class EthTlsApiServerThread : public concurrency::OSThread
|
||||
if (!isEthCertReady())
|
||||
return 500;
|
||||
if (!initTlsContext())
|
||||
return INT32_MAX; // hard fail — TLS server stays disabled
|
||||
return INT32_MAX; // hard fail - TLS server stays disabled
|
||||
loadedCertGen_ = getEthCertGeneration();
|
||||
tlsReady = true;
|
||||
}
|
||||
@@ -330,7 +330,7 @@ void deInitEthTlsApiServer()
|
||||
{
|
||||
// A W5500 chip reset leaves tlsServer bound to a dead socket and the cached
|
||||
// mbedTLS context stale. Reset the worker back to Phase A (free the context,
|
||||
// drop the listener, clear tlsReady) WITHOUT deleting the OSThread — its next
|
||||
// drop the listener, clear tlsReady) WITHOUT deleting the OSThread - its next
|
||||
// runOnce re-waits for isEthCertReady() and rebuilds the context + rebinds
|
||||
// TCP/443. Safe to free here: this runs in reconnectETH (ethConnect thread),
|
||||
// and the cooperative scheduler guarantees tlsThread is not mid-runOnce, so
|
||||
|
||||
Reference in New Issue
Block a user