Stream Architecture
Overview
The stream subsystem gives each MixnetClient the ability to hold many concurrent byte channels (AsyncRead + AsyncWrite) to different remote peers, multiplexed over a single client connection.
Wire protocol
Every stream message has a fixed 16-byte LP frame header prepended to the payload:
[LpFrameKind: 2 bytes LE][StreamId: 8 bytes BE][MsgType: 1 byte][SequenceNum: 4 bytes BE][Reserved: 1 byte][payload ...]- LpFrameKind:
3(SphinxStream). Distinguishes stream traffic from other LP frame types (Opaque, Registration, Forward). - StreamId: random
u64generated by the opener, used to multiplex streams. - MsgType:
Open(0) orData(1). - SequenceNum:
u32counter, incremented per write. Used by the receiver's per-stream reorder buffer to deliver data in the correct order. - Reserved: must be
0x00.
There is no Close message type; see Known Limitations for why.
Stream mode
Stream mode is activated lazily on the first call to open_stream() or listener(). This is a one-way transition:
- The client's message receiver is handed off to a background router task
stream_modeflag is set totrue- Message-based methods (
send_plain_message,wait_for_messages) are disabled and return errors
There is no switching back without disconnecting and creating a new client.
Opening and accepting streams
Opening (outbound):
open_stream(recipient, surbs)generates a randomStreamId- An
Openmessage is sent through the Mixnet to the recipient - A
MixnetStreamis returned, ready for writing and reading
Accepting (inbound):
listener.accept()waits for anOpenmessage from a remote peer- A
MixnetStreamis created with the opener'ssender_tagfor anonymous replies - The stream is ready for bidirectional I/O
Cleanup
- On
drop: the stream deregisters from the routing table. No close message is sent over the wire. - Idle timeout: streams idle for longer than the configured timeout (default: 30 minutes) are automatically cleaned up. Configure with
MixnetClientBuilder::with_stream_idle_timeout()(opens in a new tab).
Known limitations
Sequence-based reordering. The Mixnet does not guarantee message ordering at the transport level, but each stream write includes a sequence_num in the LP frame header. The receiver maintains a per-stream reorder buffer (BTreeMap keyed by sequence number) that buffers out-of-order messages and drains them in sequence. This means protocols that depend on byte ordering (HTTP, TLS, protobuf) work correctly over streams.
- Buffer cap: 256 messages per stream. If the buffer fills (e.g. a large gap in sequence numbers), the receiver skips ahead to the lowest buffered sequence.
- Duplicates: messages with a sequence number below the next expected are dropped.
- There is no
Closemessage type, since a close could race ahead of in-flight data.
Internal details
For the full implementation details (router task, StreamMap, PollSender usage, base-client type rationale), see the ARCHITECTURE.md file next to the module source code. This will also be available on docs.rs (opens in a new tab) once crate publication resumes with the Lewes Protocol.