Why the Same Tunnel Can Wear Different Carriers
OpenFlux’s transport layer asks a narrower question than the tunnel itself: once the tunnel has produced packet bytes, what does a carrier have to do with them? This chapter examines the common Transport contract, the compression and encryption wrappers around it, and the state that each adapter still owns. The practical result is a way to compare carriers without mistaking a shared interface for shared delivery guarantees.
The interface defines a seam, not a promise
The Transport interface is intentionally small. A concrete adapter can start and stop, send a byte slice, register a callback for received bytes, report whether it considers itself connected, and return statistics. The tunnel needs no Yandex-specific method or MAX-specific call operation. It asks for a lifecycle and a path in both directions.
That small surface is valuable because it places the packet boundary in one predictable location. The client and exit-side stacks can emit and accept packet bytes without knowing whether the next hop is a document editor session, an HTTP relay, or a messaging workflow. A new carrier can implement the same operations and be selected by the program’s transport choice. The tunnel’s TCP state remains in the tunnel; the carrier’s service state remains in the adapter.
The interface does not define everything a reader might expect from the word “transport.” It does not specify reliable delivery, ordering, fairness between flows, authentication, flow control, or a wire format. Send returns an error when an adapter cannot accept data, but the interface does not say whether a successful return means that the bytes have reached the exit node, entered a queue, or merely been handed to another goroutine. Receive supplies a callback, but it does not describe how an adapter handles duplicates, out-of-order data, or a disconnected callback owner.
Those omissions are not necessarily mistakes. They leave room for carriers with different internal protocols. They also mean that a reader must inspect each adapter before turning the interface into a behavioral claim. The common contract tells us what the tunnel can ask for; it does not tell us how a service session will honor that request.
OpenFlux’s base transport supplies some shared machinery. It tracks running and connected state with atomic values, protects the receive callback with a mutex, records bytes and packet counts, and measures uptime and reconnect attempts. The common configuration also names reconnect limits, delay growth, queue size, and keep-alive timing. This creates a useful vocabulary for adapters, but a vocabulary is not enforcement. An adapter may use the configuration carefully, partially, or not in a way that makes its reported state meaningful.
The lifecycle has two related but distinct transitions. Start means that the adapter has begun its work, while connected is meant to indicate that a usable service session exists. An adapter can be running while it is still connecting or reconnecting. Stop clears the shared running and connected state, but any adapter-specific goroutine, WebSocket, call, or queue must also honor that transition correctly. The interface leaves that cleanup responsibility with the implementation. A wrapper generally delegates lifecycle calls to its inner transport, so the visible state is only as coherent as the deepest active adapter.