Skip to content

Commit 049a6e4

Browse files
authored
refactor(mux): integrate uid-mux functionality (#86)
* chore(mux): rename to mux * feat(mux): sync close * feat(mux): extract IO * refactor(mux): move active connection state into module * doc(mux): add protocol spec * refactor(mux): only client initiates streams * refactor(mux): user ID validation and fmt * refactor(mux): increase max user id length to 256 bytes * chore(uid-mux): delete * chore(mux): remove unused error variant * chore(mux): clippy * fix(mux): send StreamInit on read as well * clippy * refactor(mux): significant simplification with deterministic ids * fmt * clippy * feat(mux): is_complete getter
1 parent fd53b48 commit 049a6e4

30 files changed

+2018
-3561
lines changed

Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
members = [
33
"serio",
44
"spansy",
5-
"uid-mux",
65
"mux/mux",
76
"mux/test-harness",
87
"utils",
@@ -22,7 +21,6 @@ serio = { path = "serio" }
2221
spansy = { path = "spansy" }
2322
tlsn-utils = { path = "utils" }
2423
tlsn-utils-aio = { path = "utils-aio" }
25-
uid-mux = { path = "uid-mux" }
2624
tlsn-mux = { path = "mux/mux" }
2725
rangeset = { path = "rangeset" }
2826

mux/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This crate provides a multiplexer for the TLSNotary library.
44

5-
It is forked from the [`rust-yamux`](https://github.com/libp2p/rust-yamux) crate with modifications made to meet the specific requirements of TLSNotary.
5+
It is based on the [`rust-yamux`](https://github.com/libp2p/rust-yamux) crate with modifications made to meet the specific requirements of TLSNotary.
66

77
## License
88

mux/SPEC.md

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
# MUX Protocol Specification
2+
3+
## 1. Overview
4+
5+
MUX is a symmetric stream multiplexing protocol designed to run over reliable, ordered connections such as TCP. It enables multiple logical streams to share a single underlying connection with independent flow control per stream.
6+
7+
### 1.1 Design Goals
8+
9+
- Lightweight framing with minimal overhead (14-byte headers)
10+
- Per-stream flow control with automatic window tuning
11+
- Graceful and abrupt stream termination
12+
- Connection-level resource limits
13+
- Symmetric operation (no client/server distinction)
14+
- Deterministic stream IDs derived from user-defined identifiers
15+
16+
### 1.2 Terminology
17+
18+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).
19+
20+
## 2. Framing
21+
22+
All data transmitted over a MUX connection is encapsulated in frames. Each frame consists of a fixed 14-byte header followed by an optional payload.
23+
24+
### 2.1 Header Format
25+
26+
| Field | Offset | Size | Encoding | Description |
27+
|-------|--------|------|----------|-------------|
28+
| Type | 0 | 1 byte | unsigned | Frame type (see Section 3). |
29+
| Flags | 1 | 1 byte | unsigned | Bitfield of flags (see Section 4). |
30+
| Length | 2 | 4 bytes | big-endian | Type-specific value (see Section 3). |
31+
| Stream ID | 6 | 8 bytes | raw bytes | Stream identifier (see Section 5.1). |
32+
33+
### 2.2 Byte Order
34+
35+
Multi-byte Length fields MUST be encoded in network byte order (big-endian). Stream IDs are stored as raw bytes.
36+
37+
### 2.3 Header Size
38+
39+
The header size is fixed at 14 bytes. Implementations MUST NOT send or accept headers of any other size.
40+
41+
## 3. Frame Types
42+
43+
The Type field identifies the frame's purpose. The Length field's interpretation varies by type.
44+
45+
| Type | Value | Length Field | Has Payload |
46+
|------|-------|--------------|-------------|
47+
| Data | `0x00` | Payload length in bytes | Yes |
48+
| Window Update | `0x01` | Window increment in bytes | No |
49+
| Ping | `0x02` | Opaque nonce value | No |
50+
| GoAway | `0x03` | Error code | No |
51+
52+
Implementations MUST reject frames with unknown Type values by sending a GoAway frame with error code `0x01` (protocol error).
53+
54+
### 3.1 Data Frame (Type 0x00)
55+
56+
Data frames carry stream payload. The Length field specifies the number of payload bytes immediately following the header.
57+
58+
**Constraints:**
59+
- Length MUST NOT exceed the receiver's available stream receive window.
60+
- Length MUST NOT exceed `1,048,576` bytes (1 MiB).
61+
62+
### 3.2 Window Update Frame (Type 0x01)
63+
64+
Window Update frames adjust the sender's view of the receiver's available receive window. The Length field contains the number of bytes to add to the stream's receive window.
65+
66+
**Constraints:**
67+
- Length of `0` is valid but has no effect.
68+
- The resulting window MUST NOT exceed `2^32 - 1` bytes.
69+
70+
### 3.3 Ping Frame (Type 0x02)
71+
72+
Ping frames measure round-trip time and serve as keep-alives. The Length field contains an opaque 32-bit nonce.
73+
74+
**Behavior:**
75+
- A Ping with the SYN flag set is a request. The receiver MUST respond with a Ping carrying the ACK flag and the same nonce.
76+
- A Ping with the ACK flag set is a response. The nonce MUST match a previously sent request.
77+
- Ping frames MUST use the zero Stream ID.
78+
79+
### 3.4 GoAway Frame (Type 0x03)
80+
81+
GoAway frames signal connection termination. The Length field contains an error code.
82+
83+
| Error Code | Value | Description |
84+
|------------|-------|-------------|
85+
| Normal | `0x00000000` | Graceful shutdown, no error. |
86+
| Protocol Error | `0x00000001` | Peer violated the protocol. |
87+
| Internal Error | `0x00000002` | Internal implementation error. |
88+
89+
**Behavior:**
90+
- GoAway frames MUST use the zero Stream ID.
91+
- After sending GoAway, an implementation MUST NOT open new streams.
92+
- After receiving GoAway, an implementation MUST NOT open new streams.
93+
- Existing streams MAY continue until closed.
94+
95+
## 4. Flags
96+
97+
Flags modify frame behavior. Multiple flags MAY be set simultaneously by combining their values with bitwise OR.
98+
99+
| Flag | Value | Applicable Types | Description |
100+
|------|-------|------------------|-------------|
101+
| FIN | `0x01` | Data, Window Update | Half-closes the stream in the sender's direction. |
102+
| RST | `0x02` | Data, Window Update | Immediately resets (terminates) the stream. |
103+
| SYN | `0x04` | Ping | Ping request. |
104+
| ACK | `0x08` | Ping | Ping response. |
105+
106+
### 4.1 Flag Constraints
107+
108+
- FIN and RST MUST NOT be set together. If both are set, RST takes precedence.
109+
- SYN and ACK are only valid on Ping frames.
110+
111+
## 5. Stream Management
112+
113+
### 5.1 Stream Identifiers
114+
115+
Stream IDs are 8-byte values derived from user-defined identifiers using the BLAKE3 hash function:
116+
117+
```
118+
StreamID = BLAKE3(user_id)[0..8]
119+
```
120+
121+
The first 8 bytes of the BLAKE3 hash are used as the Stream ID. The zero Stream ID (all bytes zero) is reserved for connection-level frames (Ping, GoAway) and MUST NOT be used for data streams.
122+
123+
**Properties:**
124+
- Stream IDs are deterministic: the same user ID always produces the same Stream ID.
125+
- Either peer MAY open any stream by sending frames to that Stream ID.
126+
- If both peers open the same stream simultaneously, the streams merge automatically.
127+
128+
### 5.2 Implicit Stream Creation
129+
130+
Streams are created implicitly when the first frame for a Stream ID is sent or received:
131+
132+
**When sending:**
133+
1. Compute the Stream ID from the user-defined identifier.
134+
2. If the stream does not exist locally, create it.
135+
3. Send the frame.
136+
137+
**When receiving:**
138+
1. If the Stream ID is unknown, create the stream.
139+
2. If the stream limit is exceeded, send GoAway with Protocol Error.
140+
3. Process the frame normally.
141+
142+
### 5.3 Stream Lifecycle
143+
144+
```
145+
+-------+
146+
| Open |
147+
+-------+
148+
/ | \
149+
FIN← / | \ →FIN
150+
/ | \
151+
+----------+ | +----------+
152+
|RecvClosed| | |SendClosed|
153+
+----------+ | +----------+
154+
\ | /
155+
FIN→ \ | / ←FIN
156+
\ | /
157+
+-------+
158+
|Closed |
159+
+-------+
160+
161+
RST (any state)
162+
```
163+
164+
| State | Description |
165+
|-------|-------------|
166+
| Open | Bidirectional data flow. |
167+
| SendClosed | Local side sent FIN. Can still receive data. |
168+
| RecvClosed | Remote side sent FIN. Can still send data. |
169+
| Closed | Both directions closed. Stream resources may be released. |
170+
171+
### 5.4 Half-Close (FIN)
172+
173+
Sending FIN indicates the sender will transmit no more data on this stream. The stream transitions:
174+
- `Open``SendClosed`
175+
- `RecvClosed``Closed`
176+
177+
Receiving FIN transitions:
178+
- `Open``RecvClosed`
179+
- `SendClosed``Closed`
180+
181+
### 5.5 Reset (RST)
182+
183+
RST immediately terminates a stream from any state. Both sides SHOULD release stream resources upon sending or receiving RST. No further frames SHOULD be sent on a reset stream.
184+
185+
## 6. Flow Control
186+
187+
MUX implements per-stream flow control using a credit-based window mechanism.
188+
189+
### 6.1 Stream Receive Window
190+
191+
Each stream maintains an independent receive window representing the number of bytes the receiver is willing to buffer.
192+
193+
**Initial window size:** 262,144 bytes (256 KiB)
194+
195+
**Behavior:**
196+
- Senders MUST NOT send more data than the receiver's advertised window.
197+
- Each byte of Data payload consumes one byte of window.
198+
- Window Update frames replenish the window.
199+
200+
### 6.2 Window Updates
201+
202+
Receivers send Window Update frames to grant additional receive capacity.
203+
204+
**Timing:**
205+
- Implementations SHOULD send Window Update when approximately half the window has been consumed.
206+
- Implementations MAY batch window updates for efficiency.
207+
208+
### 6.3 Connection-Level Window Limit
209+
210+
Implementations SHOULD enforce a connection-level limit on the total receive window across all streams.
211+
212+
**Recommended limit:** 1,073,741,824 bytes (1 GiB)
213+
214+
This prevents a peer from opening many streams to exhaust memory.
215+
216+
### 6.4 Automatic Window Tuning
217+
218+
Implementations MAY automatically increase stream receive windows based on observed throughput and round-trip time.
219+
220+
## 7. Session Management
221+
222+
### 7.1 Connection Initialization
223+
224+
MUX does not require an explicit handshake. The connection is considered established once the underlying transport is connected. Either peer may immediately begin opening streams.
225+
226+
### 7.2 Graceful Shutdown
227+
228+
To gracefully close a connection:
229+
230+
1. Send a GoAway frame with error code `0x00` (Normal).
231+
2. Stop opening new streams.
232+
3. Wait for existing streams to close naturally or with timeout.
233+
4. Close the underlying connection.
234+
235+
### 7.3 Synchronized Close Mode
236+
237+
In synchronized close mode, both sides exchange GoAway frames before closing.
238+
239+
**Initiator behavior:**
240+
1. Send GoAway.
241+
2. Wait to receive GoAway from peer (with timeout).
242+
3. Close underlying connection.
243+
244+
**Responder behavior:**
245+
1. Receive GoAway.
246+
2. Send GoAway.
247+
3. Close underlying connection.
248+
249+
### 7.4 Keep-Alive
250+
251+
Implementations MAY send periodic Ping frames to detect connection liveness and measure RTT.
252+
253+
## 8. Error Handling
254+
255+
### 8.1 Protocol Violations
256+
257+
Upon detecting a protocol violation, implementations MUST:
258+
1. Send a GoAway frame with appropriate error code.
259+
2. Close all streams.
260+
3. Close the underlying connection.
261+
262+
**Examples of protocol violations:**
263+
- Unknown frame type
264+
- Data exceeding receive window
265+
- Stream limit exceeded
266+
- Invalid flags for frame type
267+
268+
### 8.2 Stream Errors vs Connection Errors
269+
270+
- **Stream errors** (e.g., application-level errors) SHOULD be handled with RST on that stream.
271+
- **Connection errors** (e.g., protocol violations) MUST be handled with GoAway and connection closure.
272+
273+
## 9. Constants Summary
274+
275+
| Constant | Value | Description |
276+
|----------|-------|-------------|
277+
| Header Size | 14 bytes | Fixed frame header size |
278+
| Default Receive Window | 262,144 bytes (256 KiB) | Initial per-stream receive window |
279+
| Max Frame Payload | 1,048,576 bytes (1 MiB) | Maximum Data frame payload |
280+
| Recommended Connection Window | 1,073,741,824 bytes (1 GiB) | Maximum total receive window |
281+
282+
## 10. Security Considerations
283+
284+
- Implementations MUST validate all frame fields to prevent integer overflows.
285+
- Implementations SHOULD enforce connection-level resource limits to prevent denial of service.
286+
- Stream ID collisions are theoretically possible but extremely unlikely (64-bit hash space).
287+
288+
## 11. References
289+
290+
- [RFC 2119: Key words for use in RFCs](https://www.rfc-editor.org/rfc/rfc2119)
291+
- [BLAKE3 Hash Function](https://github.com/BLAKE3-team/BLAKE3)

mux/mux/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ rand = "0.9.0"
2121
static_assertions = "1"
2222
pin-project = "1.1.0"
2323
web-time = "1.1.0"
24+
blake3 = { version = "1.8" }
2425

2526
[dev-dependencies]
2627
futures = { version = "0.3.12", default-features = false, features = [

mux/mux/src/chunks.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Copyright (c) 2019 Parity Technologies (UK) Ltd.
22
// Modifications Copyright (c) 2026 TLSNotary
33
//
4-
// Licensed under the Apache License, Version 2.0 or MIT license, at your option.
4+
// Licensed under the Apache License, Version 2.0 or MIT license, at your
5+
// option.
56
//
67
// A copy of the Apache License, Version 2.0 is included in the software as
78
// LICENSE-APACHE and a copy of the MIT license is included in the software
@@ -99,11 +100,6 @@ impl Chunk {
99100
self.cursor
100101
.set_position(self.cursor.position() + amount as u64);
101102
}
102-
103-
/// Consume `self` and return the inner vector.
104-
pub(crate) fn into_vec(self) -> Vec<u8> {
105-
self.cursor.into_inner()
106-
}
107103
}
108104

109105
impl AsRef<[u8]> for Chunk {

0 commit comments

Comments
 (0)