tollan docs

ESP32 — API reference#

The public surface of the TollanAgent library. Everything lives in the tollan namespace; include <TollanAgent.h>.

cpp
#include <TollanAgent.h>
using namespace tollan;

class TollanAgent#

The agent object. Create one globally, configure it in setup(), and drive it from loop().

cpp
void begin(const TollanConfig& cfg);
void loop();

// Handler-mode replies
bool send(uint32_t channel, const uint8_t* data, size_t len);
bool closeChannel(uint32_t channel);

// Handler-mode callbacks
void onOpen(OpenHandler h);
void onData(DataHandler h);
void onClose(CloseHandler h);

// State
TollanState state() const;
bool isConnected() const;   // state() == Connected
bool clockReady() const;    // system clock is SNTP-synced (TLS dates can pass)

begin(cfg)#

Stores the configuration and prepares the agent. Call once in setup() after Wi-Fi is up. Does not block on the network — the actual connect happens in loop().

loop()#

Drives the agent cooperatively — call it every loop() iteration. It handles, in order as needed: SNTP clock sync, TLS + WebSocket connect, tollan.v1 subprotocol negotiation, jittered exponential-backoff reconnect, keepalive PONGs, and inbound frame dispatch. It never blocks for long; a short delay(2) after it is fine.

send(channel, data, len)bool#

Handler mode. Write len bytes back to the visitor on channel as a DATA frame. Returns false if the tunnel isn't up or the write fails. Call as many times as you like to stream a response.

closeChannel(channel)bool#

Handler mode. Send a CLOSE on channel to signal end-of-reply. Returns false if the tunnel isn't up.

Callbacks#

Register handlers for inbound frames (handler mode only; ignored when cfg.forward is true):

cpp
using OpenHandler  = std::function<void(uint32_t channel, const char* target, size_t targetLen)>;
using DataHandler  = std::function<void(uint32_t channel, const uint8_t* data, size_t len)>;
using CloseHandler = std::function<void(uint32_t channel)>;
  • onOpen — a new channel was opened. target is the relay's requested host:port (may be empty); a self-serving endpoint can ignore it.
  • onDatalen bytes arrived on channel. This is the raw bytes of the tunneled connection (e.g. an HTTP request). Reply with send() / closeChannel().
  • onClose — the channel was closed; clean up any per-channel state.

State accessors#

cpp
TollanState s = agent.state();
if (agent.isConnected()) { /* tunnel up */ }
if (agent.clockReady())  { /* SNTP-synced; TLS date checks can pass */ }

clockReady() is exposed so a sketch can show status or wait explicitly; connect() already gates on it internally when cfg.syncClock is set.

enum class TollanState#

cpp
enum class TollanState {
  Disconnected,  // idle / waiting out reconnect backoff
  Connected,     // the tunnel is up
  Terminal,      // fatal (e.g. protocol-version mismatch) — will NOT reconnect
};

Terminal is reached only on an unrecoverable condition such as a tollan.v1 version mismatch. The agent deliberately stops rather than reconnect-loop against a relay it can't talk to. Everything else (network drops, TLS hiccups) resolves back through Disconnected → Connected on backoff.

struct TollanConfig#

Passed to begin(). Fields with defaults may be omitted.

Connection#

FieldTypeDefaultPurpose
hostconst char*nullptrRelay hostname to dial — also the TLS SNI the relay's cert is pinned to (RELAY_PUBLIC_SNI)
portuint16_t0Relay control-channel port, e.g. 7000
pathconst char*TOLLAN_TUNNEL_PATHWebSocket upgrade path
caCertconst char*nullptrPEM: CA that signed the relay's server cert
clientCertconst char*nullptrPEM: this device's client certificate
clientKeyconst char*nullptrPEM: this device's private key

Reconnect & framing#

FieldTypeDefaultPurpose
minBackoffMsuint32_t1000Reconnect backoff floor
maxBackoffMsuint32_t30000Reconnect backoff ceiling
maxFrameBytesuint16_t1600Per-frame payload cap (RAM guard)

Clock sync (SNTP)#

mTLS verifies the relay cert's validity dates; a board with no RTC boots near 1970, so every current cert looks "not yet valid" (mbedTLS -9984). connect() syncs time over SNTP before the first TLS handshake.

FieldTypeDefaultPurpose
syncClockbooltrueSync the clock before first TLS. Set false if the board already keeps real time (RTC, or your own configTime())
ntpServer1const char*"pool.ntp.org"Primary NTP server
ntpServer2const char*"time.nist.gov"Secondary NTP server (use a LAN NTP if you have one)
clockSyncTimeoutMsuint32_t8000Max wait for the first NTP reply

Forwarding mode#

See forwarding. Ignored unless forward is true.

FieldTypeDefaultPurpose
forwardboolfalseEnable LAN-proxy mode (dial targets, pipe bytes)
defaultTargetconst char*nullptrTarget for an OPEN with no explicit target; always allowlisted
forwardTargetsconst char* const*nullptrAdditional allowed host:port targets
forwardTargetCountsize_t0Number of entries in forwardTargets
forwardAllowAnyboolfalseDial any relay-named target (drops the allowlist protection)
localConnectTimeoutMsuint32_t4000TCP dial timeout to the LAN host
localReadChunkuint16_t256Max LAN→relay bytes per channel per loop()

Compile-time macros#

Define before including TollanAgent.h to override:

MacroDefaultPurpose
TOLLAN_MAX_FWD_CHANNELS6Max concurrent forwarded connections (each uses one lwIP socket)
TOLLAN_FWD_BUF2048Per-channel relay→local staging buffer, bytes (≥ maxFrameBytes)
cpp
#define TOLLAN_MAX_FWD_CHANNELS 4
#define TOLLAN_FWD_BUF 4096
#include <TollanAgent.h>

Portable helpers#

Two headers are dependency-free and host-testable (no Arduino deps), and are what keep the C++ port byte-compatible with the Go and Node sides:

  • TollanProtocol.h — the tollan.v1 framing codec (Frame, encode/decode). Verified against the same conformance vectors as the Go agent and the Node relay.
  • TollanTargets.hhost:port parsing and allowlist membership used by forwarding mode.

You rarely call these directly, but they're the reason the wire protocol stays consistent across all three languages. Run their host tests with:

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