1# ALPN and the service wire protocol23dev.site currently defines one application protocol for service traffic:45| ALPN | Purpose | Transport |6| --- | --- | --- |7| `devsite/tcp/1` | Authorize and carry arbitrary TCP byte streams | Iroh QUIC, direct or relayed |89ALPN means Application-Layer Protocol Negotiation. During the QUIC/TLS handshake, the10client requests `devsite/tcp/1` and the daemon advertises that exact value. This selects the11wire protocol before either side interprets an application frame.1213## Where it fits1415The dev.site control plane and service path are separate:1617```text18CLI ── HTTPS ──► dev.site control plane19 │ session validation + capability issuance20 │21 └── Iroh QUIC, ALPN devsite/tcp/1 ──► owner's daemon ── TCP ──► loopback service22 encrypted service bytes23```2425`devsite/tcp/1` is not the protocol used by the HTTPS API, OIDC login, the local MCP stdio26adapter, or the hosted application. The application behind a service can speak PostgreSQL,27HTTP, SSH, Redis, or any other TCP protocol; after authorization, dev.site treats its bytes28as opaque.2930## One connection and one stream3132The client keeps one authenticated QUIC connection per daemon and can open many33bidirectional streams on it. Each forwarded local TCP connection uses a new QUIC stream:34351. The client opens a bidirectional stream.362. It sends one length-prefixed Postcard `Connect` request containing a signed capability.373. The daemon verifies the control-plane signature, daemon audience, authenticated client38 endpoint, expiry, permission, resource, and one-use nonce.394. The daemon connects to the resource's fixed loopback TCP target.405. It sends `Connected`, or a deliberately coarse error.416. After `Connected`, both sides copy uninterpreted TCP bytes until half-close or shutdown.4243The control plane is contacted to mint the capability, but it is not on this data path and44never sees the service bytes or local target port.4546## Frame format4748The request and response each use this envelope:4950```text514-byte little-endian payload length52Postcard-encoded payload53```5455The length is checked before allocation and may not exceed 4 MiB. Version 1 has one request56variant, `Connect { capability }`, and these responses:5758- `Connected`;59- `Error { Denied }` for invalid authorization, unknown resources, or capability replay;60- `Error { BadRequest }` for malformed protocol input; and61- `Error { UpstreamUnavailable }` when the configured local TCP service does not accept a62 connection.6364Authorization failures intentionally collapse to `Denied`. A remote peer cannot use error65differences to discover whether a resource exists, whether it is hosted, or which66capability check failed.6768Once `Connected` is sent, there are no more dev.site frames on that stream. Everything69after it is the service's raw byte stream.7071## Security properties7273Iroh QUIC encrypts the network and authenticates endpoint public keys. The capability then74adds application authorization:7576- its server signature identifies the control plane that issued it;77- its audience binds it to one daemon endpoint;78- its client key must match the connection's authenticated remote endpoint;79- its resource maps only to a fixed loopback target configured by the host;80- its permission is currently only `TcpConnect`;81- its expiry limits it to three minutes; and82- its nonce is accepted only once by the daemon.8384ALPN contributes protocol separation and version selection, not user authorization. A85client that negotiates `devsite/tcp/1` still gets no service access without a valid86capability. Conversely, capability bytes sent under some other protocol are never parsed87as a dev.site request.8889Iroh may establish a direct path or use a relay. Relays carry encrypted QUIC traffic and do90not terminate the dev.site application stream. The final daemon-to-service hop is local TCP91on the host and uses whatever encryption, if any, the service protocol provides.9293## Versioning9495The `/1` suffix is a compatibility boundary for framing and semantics. A change that old96peers cannot safely decode or interpret should use a new ALPN rather than silently changing97version 1. The current client requests one ALPN and the daemon advertises one ALPN; there is98no dev.site protocol fallback or multi-version negotiation today.99100The public `/api/config` response reports the expected daemon protocol. `devsite doctor`101compares it with the CLI's compiled `devsite/tcp/1` value so a control-plane/client mismatch102is visible before debugging the data path.103104Adding another ALPN should define its exact request and response frames, authorization105checks, maximum sizes, transition from framing to application bytes, compatibility policy,106and diagnostic behavior. A new service application protocol alone does not require a new107ALPN because version 1 already transports arbitrary TCP bytes.