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