tollan docs

ESP32 — TLS, clock & troubleshooting#

The two things most likely to stop a first connection are the TLS version and the clock. Both are by-design consequences of running mTLS on a microcontroller, and both have a clean fix.

TLS version#

The protocol specifies TLS 1.3, and the relay's agent control channel defaults to requiring it. But ESP32 WiFiClientSecure (mbedTLS) historically negotiates TLS 1.2 — whether it can do 1.3 depends on your Arduino-ESP32 / ESP-IDF version.

Symptom: the handshake fails with a fatal alert — mbedTLS -30592, "SSL - A fatal alert message was received from our peer."

Fix — one of:

  • Lower the relay's TLS floor to 1.2 for MCU agents (a relay setting; on managed Tollan, support enables it). This only widens what's accepted — the rest of the fleet keeps negotiating 1.3, and the 1.2 path still uses forward-secret elliptic-curve cipher suites, with no weak-cipher downgrade.
  • Or use a recent ESP32 core that can negotiate TLS 1.3, which needs no relay change.

Note

Lowering the TLS floor is a per-relay setting, off by default. On managed Tollan, ask support to enable it for a relay that serves ESP32 agents; if you self-host, it's covered in your deployment runbooks.

Clock#

mTLS verifies the relay certificate, and X.509 verification checks the certificate's validity window. A board with no RTC boots near 1970, so every 2026-dated cert looks "not yet valid."

Symptom: TLS fails with mbedTLS -9984 ("X509 - Certificate verification failed").

Fix: the agent handles this for you. connect() syncs the clock over SNTP before the first TLS handshake and won't attempt TLS until the clock is real — it backs off and retries instead. Tune or disable via TollanConfig:

FieldDefaultWhen to change
syncClocktrueSet false only if the board already keeps real time (external RTC, or you call configTime() yourself)
ntpServer1 / ntpServer2pool.ntp.org / time.nist.govPoint at a LAN NTP server on an isolated network with no public NTP access
clockSyncTimeoutMs8000Raise on a slow or high-latency link

You can check readiness in code with agent.clockReady().

WebSocket handshake notes#

The client requires an HTTP 101 Switching Protocols response with the echoed Sec-WebSocket-Protocol: tollan.v1. It does not verify Sec-WebSocket-Accept — the mTLS layer has already authenticated the relay, so the extra check is redundant.

A tollan.v1 version mismatch is terminal: the agent enters TollanState::Terminal and stops, rather than reconnect-looping against a relay it can't talk to. If you see this, the relay is speaking a different protocol version than the library.

Common issues#

SymptomLikely causeFix
state() never leaves Disconnected, mbedTLS -30592Relay requires TLS 1.3; core only does 1.2Lower the relay's TLS floor to 1.2, or use a newer core
mbedTLS -9984 on every attemptClock not synced (board thinks it's 1970)Ensure syncClock is on and NTP is reachable
Stuck before TLS, no NTP replyNo route to pool.ntp.orgSet ntpServer1 to a LAN NTP server
state() goes straight to Terminaltollan.v1 version mismatchMatch the relay and library versions
Build warning about tollan_config.hBundle header not next to the sketchDrop the console's tollan_config.h into the sketch folder
Connects, but forwarded target refusedTarget not on the allowlistAdd the host:port to forwardTargets / defaultTarget; keep it in sync with the route
Reboots / heap exhaustion under loadToo many forwarded channels or buffers too largeLower TOLLAN_MAX_FWD_CHANNELS, tune TOLLAN_FWD_BUF and maxFrameBytes

Verifying the wire codec#

If you suspect a framing problem, the codec is host-testable off-hardware and checked against the same vectors as the Go and Node sides:

bash
cd embedded/tollan-agent-library/test/native && make
# -> test_protocol: PASS (0 failures)
# -> test_targets:  PASS (0 failures)

Green here means the C++ port is byte-compatible with the rest of the fleet — so a connection problem is transport/TLS/clock, not framing.

Serial debugging#

Both examples print state transitions to serial at 115200. Watch them while the board boots:

text
wifi... ok, ip=192.168.1.42
tollan agent started
tollan state -> connected

If you never see connected, work down the common issues table — it's almost always TLS version or clock on the first bring-up.

Roadmap#

The library is a first cut. Known next steps:

  • On-hardware end-to-end validation against a live relay (the transport's remaining gate).
  • Enrollment mode: on-device EC keygen + CSR via mbedTLS, so the private key never leaves the board (today it's provisioned via the bundle).
  • Live, signed allowlist sync so the board tracks console route edits without a re-flash — today the allowlist is the static bundle set.
  • Fragmented-WebSocket-message handling and a configurable receive buffer.