/ docs / WIRE_PROTOCOL_API.md
WIRE_PROTOCOL_API.md
  1  # Abzu Wire Protocol API Reference
  2  
  3  > **Version**: 1.0  
  4  > **Updated**: January 27, 2026  
  5  > **Crate**: `abzu-transport`
  6  
  7  ---
  8  
  9  ## Overview
 10  
 11  The wire protocol defines the binary frame format for all Abzu network communication. All frames are encoded using [postcard](https://crates.io/crates/postcard), a `no_std`-compatible varint-compressed format suitable for embedded and high-throughput scenarios.
 12  
 13  ```text
 14  ┌──────────────────────────────────────────────────────────┐
 15  │                    Wire Protocol Stack                    │
 16  ├──────────────────────────────────────────────────────────┤
 17  │  AbzuFrame (postcard)                                     │
 18  │     ↓                                                     │
 19  │  ChaCha20-Poly1305 AEAD                                   │
 20  │     ↓                                                     │
 21  │  FakeTLS Record Framing (0x17 0x03 0x03)                 │
 22  │     ↓                                                     │
 23  │  TCP / WebSocket                                          │
 24  └──────────────────────────────────────────────────────────┘
 25  ```
 26  
 27  ---
 28  
 29  ## Protocol Versioning
 30  
 31  All connections exchange version information via `Hello`/`HelloAck` frames during the initial handshake and key renegotiation.
 32  
 33  ### Version Constants
 34  
 35  ```rust
 36  // Current protocol version
 37  pub const PROTOCOL_VERSION_MAJOR: u16 = 1;
 38  pub const PROTOCOL_VERSION_MINOR: u16 = 0;
 39  
 40  // Minimum compatible version (for backward compatibility)
 41  pub const MIN_COMPATIBLE_VERSION_MAJOR: u16 = 1;
 42  ```
 43  
 44  ### Compatibility Rules
 45  
 46  | Peer Version | Our Behavior |
 47  |--------------|--------------|
 48  | Major < MIN_COMPATIBLE | **Reject** (too old, can't understand us) |
 49  | Major > ours | **Reject** (too new, we can't understand them) |
 50  | Same major, different minor | **Accept** — negotiate to min(peer_minor, our_minor) |
 51  | Compatible older major | **Accept** — use peer's minor version |
 52  
 53  ### Rejection Signal
 54  
 55  If version negotiation fails, the responder sends `HelloAck` with `version_major = 0`, signaling the initiator to disconnect.
 56  
 57  ---
 58  
 59  ## AbzuFrame Enum
 60  
 61  ```rust
 62  #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 63  pub enum AbzuFrame {
 64      // Heartbeat
 65      KeepAlive,
 66      
 67      // Content addressing (Iroh-style)
 68      Chunk { cid: [u8; 32], data: Vec<u8> },
 69      Request { cid: [u8; 32], requester: [u8; 32] },
 70      
 71      // Multi-hop routing (onion-wrapped)
 72      Route { target: [u8; 32], next_hop: [u8; 32], payload: Vec<u8> },
 73      
 74      // Direct messaging
 75      Chat { id: u64, to: [u8; 32], msg: Vec<u8>, timestamp: u64 },
 76      ChatAck { id: u64 },
 77      ReadReceipt { id: u64 },
 78      
 79      // Handshake (with versioning)
 80      Hello { 
 81          version_major: u16, version_minor: u16,
 82          ephemeral_pub: [u8; 32], timestamp: u64 
 83      },
 84      HelloAck { 
 85          version_major: u16, version_minor: u16,
 86          ephemeral_pub: [u8; 32], confirmation: Vec<u8> 
 87      },
 88      
 89      // Cover traffic (Ghost mode)
 90      Cover { noise: Vec<u8> },
 91      
 92      // Circle (group) frames
 93      CircleCreate { ... },
 94      CircleInvite { ... },
 95      CircleMessage { ... },
 96      CircleAck { ... },
 97      CirclePrune { ... },
 98      GossipHave { ... },
 99  }
100  ```
101  
102  ---
103  
104  ## Core Methods
105  
106  ### Encoding / Decoding
107  
108  ```rust
109  impl AbzuFrame {
110      /// Serialize to postcard bytes
111      pub fn encode(&self) -> Result<Vec<u8>, postcard::Error>
112      
113      /// Deserialize from postcard bytes
114      pub fn decode(bytes: &[u8]) -> Result<Self, postcard::Error>
115  }
116  ```
117  
118  ### Version Negotiation Helpers
119  
120  ```rust
121  impl AbzuFrame {
122      /// Create a Hello frame with current protocol version.
123      ///
124      /// # Arguments
125      /// * `ephemeral_pub` - X25519 ephemeral public key for PFS
126      /// * `timestamp` - Unix millis for replay protection
127      pub fn hello(ephemeral_pub: [u8; 32], timestamp: u64) -> Self
128      
129      /// Create a HelloAck frame with negotiated version.
130      ///
131      /// # Arguments
132      /// * `version_major` - Negotiated major version (0 = rejected)
133      /// * `version_minor` - Negotiated minor version  
134      /// * `ephemeral_pub` - X25519 ephemeral public key
135      /// * `confirmation` - Encrypted session confirmation
136      pub fn hello_ack(
137          version_major: u16,
138          version_minor: u16,
139          ephemeral_pub: [u8; 32],
140          confirmation: Vec<u8>,
141      ) -> Self
142      
143      /// Check if a peer's version is compatible with ours.
144      ///
145      /// Returns `Some((negotiated_major, negotiated_minor))` if compatible,
146      /// or `None` if the connection should be rejected.
147      pub fn is_version_compatible(
148          peer_major: u16, 
149          peer_minor: u16
150      ) -> Option<(u16, u16)>
151  }
152  ```
153  
154  ### Frame Constructors
155  
156  | Method | Returns | Description |
157  |--------|---------|-------------|
158  | `keep_alive()` | `AbzuFrame` | Create heartbeat frame |
159  | `chunk(cid, data)` | `AbzuFrame` | Create content chunk |
160  | `request(cid, requester)` | `AbzuFrame` | Create content request |
161  | `route(target, next_hop, payload)` | `AbzuFrame` | Create routing frame |
162  | `chat(id, to, msg, timestamp)` | `AbzuFrame` | Create chat message |
163  | `chat_ack(id)` | `AbzuFrame` | Create delivery acknowledgment |
164  | `hello(ephemeral_pub, timestamp)` | `AbzuFrame` | Create versioned handshake initiation |
165  | `hello_ack(major, minor, pub, confirm)` | `AbzuFrame` | Create versioned handshake response |
166  | `cover(target_size)` | `AbzuFrame` | Create cover traffic with overhead compensation |
167  
168  ### Frame Inspection
169  
170  ```rust
171  impl AbzuFrame {
172      /// Returns true if this is a control frame (not data)
173      pub fn is_control(&self) -> bool
174  }
175  ```
176  
177  Control frames: `KeepAlive`, `Hello`, `HelloAck`, `ChatAck`, `ReadReceipt`, `CircleAck`
178  
179  ---
180  
181  ## Multi-hop Routing
182  
183  ### Onion Wrapping
184  
185  ```rust
186  impl AbzuFrame {
187      /// Wrap a frame in onion routing layers.
188      ///
189      /// # Arguments
190      /// * `target` - Final destination pubkey
191      /// * `hops` - Intermediate relay pubkeys (in order)
192      /// * `keys` - Encryption keys for each hop
193      pub fn wrap_onion(
194          payload: Vec<u8>,
195          target: [u8; 32],
196          hops: &[[u8; 32]],
197          keys: &[ChaCha20Poly1305],
198      ) -> Result<Self, TransportError>
199  }
200  ```
201  
202  ### Unwrapping
203  
204  Each relay node unwraps one layer:
205  
206  ```rust
207  match frame {
208      AbzuFrame::Route { target, next_hop, payload } => {
209          if target == my_pubkey {
210              // Final destination - unwrap payload
211              let inner = AbzuFrame::decode(&decrypt(payload))?;
212              handle(inner)
213          } else {
214              // Intermediate - forward to next_hop
215              send_to_peer(next_hop, frame)
216          }
217      }
218  }
219  ```
220  
221  ---
222  
223  ## Cover Traffic (Ghost Mode)
224  
225  ### Cover Frame Generation
226  
227  ```rust
228  /// Create cover traffic sized to mimic real traffic
229  /// Compensates for postcard serialization overhead
230  pub fn cover(target_size: usize) -> Self
231  ```
232  
233  **Security Properties:**
234  
235  - Cryptographically indistinguishable on wire
236  - Serialization overhead compensation prevents size fingerprinting
237  - 16-byte bucket fuzzing prevents echo attacks
238  
239  ### Pattern Observation
240  
241  ```rust
242  pub struct PatternModel {
243      // Records outbound frame sizes (bucketed to 16 bytes)
244      pub fn record_size(&mut self, size: usize);
245      
246      // Sample a size from observed distribution
247      pub fn sample_size(&self) -> usize;
248  }
249  ```
250  
251  ---
252  
253  ## Handshake Flow
254  
255  ### Connection Initiation
256  
257  ```
258  Client                                Server
259     |                                    |
260     |------ Hello (v1.0, ephemeral) ---->|
261     |                                    | is_version_compatible(1, 0)?
262     |                                    | → Some((1, 0))
263     |<--- HelloAck (v1.0, ephemeral) ----|
264     |                                    |
265     |====== Session Established =========|
266  ```
267  
268  ### Version Rejection
269  
270  ```
271  Client                                Server
272     |                                    |
273     |------ Hello (v2.0, ephemeral) ---->|
274     |                                    | is_version_compatible(2, 0)?
275     |                                    | → None (too new!)
276     |<--- HelloAck (v0.0, [], []) -------|
277     |                                    |
278     | Close connection                   |
279  ```
280  
281  ---
282  
283  ## Error Handling
284  
285  ### TransportError
286  
287  ```rust
288  pub enum TransportError {
289      IoError(std::io::Error),
290      HandshakeFailed,          // Version mismatch, crypto failure
291      EncryptionError,          // AEAD failed
292      FrameTooLarge,            // Exceeds MAX_FRAME_SIZE
293      InvalidFrame,             // Postcard decode failed
294      ConnectionClosed,
295  }
296  ```
297  
298  ---
299  
300  ## Frame Size Limits
301  
302  | Constant | Value | Purpose |
303  |----------|-------|---------|
304  | `MAX_FRAME_SIZE` | 65535 | Maximum encoded frame size |
305  | `MTU_PADDING_SIZE` | 1400 | Shadow mode padding target |
306  
307  ---
308  
309  ## Usage Examples
310  
311  ### Basic Frame Exchange
312  
313  ```rust
314  use abzu_transport::wire::{AbzuFrame, PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR};
315  
316  // Create versioned Hello
317  let ephemeral_pub = generate_x25519_keypair().public_bytes();
318  let hello = AbzuFrame::hello(ephemeral_pub, unix_millis());
319  
320  // Encode and send
321  let bytes = hello.encode()?;
322  stream.write_all(&bytes).await?;
323  
324  // Receive and decode
325  let response = AbzuFrame::decode(&recv_bytes)?;
326  
327  // Check version compatibility
328  match response {
329      AbzuFrame::HelloAck { version_major, version_minor, .. } => {
330          if version_major == 0 {
331              return Err(TransportError::HandshakeFailed);
332          }
333          println!("Negotiated v{}.{}", version_major, version_minor);
334      }
335      _ => return Err(TransportError::InvalidFrame),
336  }
337  ```
338  
339  ### Version Compatibility Check
340  
341  ```rust
342  // Peer claims v1.2
343  let (neg_major, neg_minor) = AbzuFrame::is_version_compatible(1, 2)
344      .ok_or(TransportError::HandshakeFailed)?;
345  
346  // neg_major = 1, neg_minor = min(2, PROTOCOL_VERSION_MINOR) = 0
347  assert_eq!(neg_minor, 0);
348  ```
349  
350  ---
351  
352  ## Wire Format Details
353  
354  ### Postcard Encoding
355  
356  Postcard uses varint encoding for integers:
357  
358  - 0-127: 1 byte
359  - 128-16383: 2 bytes
360  - etc.
361  
362  Byte arrays are length-prefixed.
363  
364  ### Frame Discrimination
365  
366  The first varint in the encoded frame indicates the variant index:
367  
368  - `0` = KeepAlive
369  - `1` = Chunk
370  - `2` = Route
371  - `3` = Request
372  - `4` = Chat
373  - ...
374  
375  > **Security Note**: After ChaCha20-Poly1305 encryption, this information is hidden. Frame type is only visible post-decryption.
376  
377  ---
378  
379  ## See Also
380  
381  - [ARCHITECTURE.md](./ARCHITECTURE.md) — System overview
382  - [NAT_TRAVERSAL_AND_PFS.md](./NAT_TRAVERSAL_AND_PFS.md) — Handshake details
383  - [Ghost_Mode_Deep_Dive.md](./Ghost_Mode_Deep_Dive.md) — Cover traffic