Skip to main content

moonpool_transport/wire/
mod.rs

1//! Wire format for packet serialization.
2//!
3//! Packet format: `[length:4][checksum:4][token:16][payload:N]`
4//!
5//! - **length**: Total packet size including header (little-endian u32)
6//! - **checksum**: CRC32C of (token + payload) for integrity verification
7//! - **token**: Destination endpoint UID (two little-endian u64)
8//! - **payload**: Application data (custom serialization)
9
10use crate::UID;
11
12/// Header size: 4 (length) + 4 (checksum) + 16 (token) = 24 bytes.
13pub const HEADER_SIZE: usize = 24;
14
15/// Maximum payload size (1MB).
16///
17/// Packets larger than this are rejected to prevent memory exhaustion attacks.
18pub const MAX_PAYLOAD_SIZE: usize = 1024 * 1024;
19
20/// Wire format error types.
21#[derive(Debug, Clone, thiserror::Error)]
22pub enum WireError {
23    /// Not enough data to parse the packet.
24    #[error("insufficient data: need {needed} bytes, have {have}")]
25    InsufficientData {
26        /// Minimum bytes required to parse.
27        needed: usize,
28        /// Actual bytes available.
29        have: usize,
30    },
31
32    /// Checksum verification failed - data was corrupted.
33    #[error("checksum mismatch: expected {expected:#010x}, got {actual:#010x}")]
34    ChecksumMismatch {
35        /// Expected checksum from header.
36        expected: u32,
37        /// Computed checksum from data.
38        actual: u32,
39    },
40
41    /// Payload exceeds maximum allowed size.
42    #[error("packet too large: {size} bytes (max {MAX_PAYLOAD_SIZE})")]
43    PacketTooLarge {
44        /// Actual payload size in bytes.
45        size: usize,
46    },
47
48    /// Length field has an invalid value.
49    #[error("invalid packet length: {length}")]
50    InvalidLength {
51        /// The invalid length value from the header.
52        length: u32,
53    },
54}
55
56/// Packet header for wire format.
57///
58/// Contains the fixed-size header fields that precede the payload.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct PacketHeader {
61    /// Total packet size including header.
62    pub length: u32,
63    /// CRC32C checksum of (token + payload).
64    pub checksum: u32,
65    /// Destination endpoint token.
66    pub token: UID,
67}
68
69impl PacketHeader {
70    /// Serialize header into buffer (must be at least HEADER_SIZE bytes).
71    ///
72    /// # Panics
73    ///
74    /// Panics if buffer is smaller than HEADER_SIZE.
75    pub fn serialize_into(&self, buf: &mut [u8]) {
76        debug_assert!(buf.len() >= HEADER_SIZE);
77        buf[0..4].copy_from_slice(&self.length.to_le_bytes());
78        buf[4..8].copy_from_slice(&self.checksum.to_le_bytes());
79        buf[8..16].copy_from_slice(&self.token.first.to_le_bytes());
80        buf[16..24].copy_from_slice(&self.token.second.to_le_bytes());
81    }
82
83    /// Deserialize header from buffer.
84    ///
85    /// # Errors
86    ///
87    /// Returns `InsufficientData` if buffer is smaller than HEADER_SIZE.
88    pub fn deserialize(buf: &[u8]) -> Result<Self, WireError> {
89        if buf.len() < HEADER_SIZE {
90            return Err(WireError::InsufficientData {
91                needed: HEADER_SIZE,
92                have: buf.len(),
93            });
94        }
95
96        let length = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
97        let checksum = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);
98        let first = u64::from_le_bytes([
99            buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
100        ]);
101        let second = u64::from_le_bytes([
102            buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
103        ]);
104
105        Ok(Self {
106            length,
107            checksum,
108            token: UID::new(first, second),
109        })
110    }
111}
112
113/// Compute CRC32C checksum over token + payload.
114fn compute_checksum(token: UID, payload: &[u8]) -> u32 {
115    let mut data = Vec::with_capacity(16 + payload.len());
116    data.extend_from_slice(&token.first.to_le_bytes());
117    data.extend_from_slice(&token.second.to_le_bytes());
118    data.extend_from_slice(payload);
119    crc32c::crc32c(&data)
120}
121
122/// Serialize a packet with token and payload.
123///
124/// Returns: `[length:4][checksum:4][token:16][payload:N]`
125///
126/// # Errors
127///
128/// Returns `PacketTooLarge` if payload exceeds MAX_PAYLOAD_SIZE.
129///
130/// # Examples
131///
132/// ```
133/// use moonpool_transport::{UID, serialize_packet, deserialize_packet};
134///
135/// let token = UID::new(1, 2);
136/// let payload = b"hello";
137///
138/// let packet = serialize_packet(token, payload).expect("serialize");
139/// let (recv_token, recv_payload) = deserialize_packet(&packet).expect("deserialize");
140///
141/// assert_eq!(token, recv_token);
142/// assert_eq!(payload.as_slice(), recv_payload.as_slice());
143/// ```
144pub fn serialize_packet(token: UID, payload: &[u8]) -> Result<Vec<u8>, WireError> {
145    if payload.len() > MAX_PAYLOAD_SIZE {
146        return Err(WireError::PacketTooLarge {
147            size: payload.len(),
148        });
149    }
150
151    let total_length = HEADER_SIZE + payload.len();
152    let mut data = vec![0u8; total_length];
153
154    let checksum = compute_checksum(token, payload);
155
156    let header = PacketHeader {
157        length: total_length as u32,
158        checksum,
159        token,
160    };
161
162    header.serialize_into(&mut data[..HEADER_SIZE]);
163    data[HEADER_SIZE..].copy_from_slice(payload);
164
165    Ok(data)
166}
167
168/// Deserialize a packet, validating checksum.
169///
170/// # Errors
171///
172/// - `InsufficientData`: Not enough bytes to parse header or full packet
173/// - `ChecksumMismatch`: Data was corrupted
174/// - `InvalidLength`: Length field is malformed
175///
176/// # Examples
177///
178/// ```
179/// use moonpool_transport::{UID, serialize_packet, deserialize_packet};
180///
181/// let token = UID::new(1, 2);
182/// let packet = serialize_packet(token, b"test").expect("serialize");
183///
184/// let (recv_token, payload) = deserialize_packet(&packet).expect("deserialize");
185/// assert_eq!(token, recv_token);
186/// ```
187pub fn deserialize_packet(data: &[u8]) -> Result<(UID, Vec<u8>), WireError> {
188    let header = PacketHeader::deserialize(data)?;
189
190    // Validate length field
191    if header.length < HEADER_SIZE as u32 {
192        return Err(WireError::InvalidLength {
193            length: header.length,
194        });
195    }
196
197    let expected_len = header.length as usize;
198    if data.len() < expected_len {
199        return Err(WireError::InsufficientData {
200            needed: expected_len,
201            have: data.len(),
202        });
203    }
204
205    let payload = &data[HEADER_SIZE..expected_len];
206
207    // Validate checksum
208    let computed = compute_checksum(header.token, payload);
209    if computed != header.checksum {
210        return Err(WireError::ChecksumMismatch {
211            expected: header.checksum,
212            actual: computed,
213        });
214    }
215
216    Ok((header.token, payload.to_vec()))
217}
218
219/// Try to deserialize from a buffer that may contain partial data.
220///
221/// This is useful for streaming scenarios where packets arrive incrementally.
222///
223/// # Returns
224///
225/// - `Ok(Some((token, payload, consumed)))` if a complete packet was parsed
226/// - `Ok(None)` if more data is needed (not an error condition)
227/// - `Err` if data is malformed
228///
229/// # Examples
230///
231/// ```
232/// use moonpool_transport::{UID, serialize_packet, try_deserialize_packet};
233///
234/// let token = UID::new(1, 2);
235/// let packet = serialize_packet(token, b"test").expect("serialize");
236///
237/// // Partial data returns None
238/// assert!(try_deserialize_packet(&packet[..10]).expect("partial").is_none());
239///
240/// // Complete packet returns Some
241/// let result = try_deserialize_packet(&packet).expect("complete");
242/// assert!(result.is_some());
243/// ```
244pub fn try_deserialize_packet(data: &[u8]) -> Result<Option<(UID, Vec<u8>, usize)>, WireError> {
245    if data.len() < HEADER_SIZE {
246        return Ok(None); // Need more data for header
247    }
248
249    let header = PacketHeader::deserialize(data)?;
250
251    if header.length < HEADER_SIZE as u32 {
252        return Err(WireError::InvalidLength {
253            length: header.length,
254        });
255    }
256
257    let expected_len = header.length as usize;
258    if data.len() < expected_len {
259        return Ok(None); // Need more data for payload
260    }
261
262    let payload = &data[HEADER_SIZE..expected_len];
263
264    // Validate checksum
265    let computed = compute_checksum(header.token, payload);
266    if computed != header.checksum {
267        return Err(WireError::ChecksumMismatch {
268            expected: header.checksum,
269            actual: computed,
270        });
271    }
272
273    Ok(Some((header.token, payload.to_vec(), expected_len)))
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use crate::WellKnownToken;
280
281    #[test]
282    fn test_serialize_deserialize_roundtrip() {
283        let token = UID::new(0x123456789ABCDEF0, 0xFEDCBA9876543210);
284        let payload = b"hello world";
285
286        let packet = serialize_packet(token, payload).expect("serialize");
287        let (recv_token, recv_payload) = deserialize_packet(&packet).expect("deserialize");
288
289        assert_eq!(token, recv_token);
290        assert_eq!(payload.as_slice(), recv_payload.as_slice());
291    }
292
293    #[test]
294    fn test_checksum_validation() {
295        let token = UID::new(1, 2);
296        let packet = serialize_packet(token, b"test").expect("serialize");
297
298        // Corrupt the payload
299        let mut corrupted = packet.clone();
300        corrupted[HEADER_SIZE] ^= 0xFF;
301
302        let result = deserialize_packet(&corrupted);
303        assert!(matches!(result, Err(WireError::ChecksumMismatch { .. })));
304    }
305
306    #[test]
307    fn test_checksum_header_corruption() {
308        let token = UID::new(1, 2);
309        let packet = serialize_packet(token, b"test").expect("serialize");
310
311        // Corrupt the token in header (this will cause checksum mismatch)
312        let mut corrupted = packet.clone();
313        corrupted[10] ^= 0xFF;
314
315        let result = deserialize_packet(&corrupted);
316        assert!(matches!(result, Err(WireError::ChecksumMismatch { .. })));
317    }
318
319    #[test]
320    fn test_well_known_token() {
321        let token = UID::well_known(WellKnownToken::Ping as u32);
322        assert!(token.is_well_known());
323        assert_eq!(token.first, u64::MAX);
324        assert_eq!(token.second, 1);
325
326        // Verify serialization works with well-known tokens
327        let packet = serialize_packet(token, b"ping").expect("serialize");
328        let (recv_token, _) = deserialize_packet(&packet).expect("deserialize");
329        assert_eq!(token, recv_token);
330    }
331
332    #[test]
333    fn test_insufficient_data_header() {
334        let result = deserialize_packet(&[0u8; 10]);
335        assert!(matches!(
336            result,
337            Err(WireError::InsufficientData {
338                needed: HEADER_SIZE,
339                have: 10
340            })
341        ));
342    }
343
344    #[test]
345    fn test_insufficient_data_payload() {
346        let token = UID::new(1, 2);
347        let packet = serialize_packet(token, b"test data that is longer").expect("serialize");
348
349        // Take only header + partial payload
350        let partial = &packet[..HEADER_SIZE + 5];
351        let result = deserialize_packet(partial);
352        assert!(matches!(result, Err(WireError::InsufficientData { .. })));
353    }
354
355    #[test]
356    fn test_try_deserialize_partial_header() {
357        let token = UID::new(1, 2);
358        let packet = serialize_packet(token, b"test data").expect("serialize");
359
360        // Partial header
361        let result = try_deserialize_packet(&packet[..10]);
362        assert!(matches!(result, Ok(None)));
363    }
364
365    #[test]
366    fn test_try_deserialize_partial_payload() {
367        let token = UID::new(1, 2);
368        let packet = serialize_packet(token, b"test data").expect("serialize");
369
370        // Header complete but payload partial
371        let result = try_deserialize_packet(&packet[..HEADER_SIZE + 2]);
372        assert!(matches!(result, Ok(None)));
373    }
374
375    #[test]
376    fn test_try_deserialize_complete() {
377        let token = UID::new(1, 2);
378        let packet = serialize_packet(token, b"test data").expect("serialize");
379
380        // Complete packet
381        let result = try_deserialize_packet(&packet).expect("deserialize");
382        assert!(result.is_some());
383
384        let (recv_token, recv_payload, consumed) = result.expect("has data");
385        assert_eq!(token, recv_token);
386        assert_eq!(b"test data".as_slice(), recv_payload.as_slice());
387        assert_eq!(consumed, packet.len());
388    }
389
390    #[test]
391    fn test_try_deserialize_with_extra_data() {
392        let token = UID::new(1, 2);
393        let packet = serialize_packet(token, b"test").expect("serialize");
394
395        // Add extra data after packet
396        let mut extended = packet.clone();
397        extended.extend_from_slice(b"extra garbage");
398
399        let result = try_deserialize_packet(&extended).expect("deserialize");
400        let (recv_token, recv_payload, consumed) = result.expect("has data");
401
402        assert_eq!(token, recv_token);
403        assert_eq!(b"test".as_slice(), recv_payload.as_slice());
404        assert_eq!(consumed, packet.len()); // Only original packet consumed
405    }
406
407    #[test]
408    fn test_adjusted_uid() {
409        let base = UID::new(0x1000, 0x2000);
410        let adj1 = base.adjusted(1);
411        let adj2 = base.adjusted(2);
412
413        // Each adjusted UID should be unique
414        assert_ne!(base, adj1);
415        assert_ne!(adj1, adj2);
416        assert_ne!(base, adj2);
417
418        // Verify serialization works with adjusted UIDs
419        for (i, uid) in [base, adj1, adj2].iter().enumerate() {
420            let packet = serialize_packet(*uid, format!("msg{}", i).as_bytes()).expect("serialize");
421            let (recv_token, _) = deserialize_packet(&packet).expect("deserialize");
422            assert_eq!(*uid, recv_token);
423        }
424    }
425
426    #[test]
427    fn test_empty_payload() {
428        let token = UID::new(42, 43);
429        let packet = serialize_packet(token, &[]).expect("serialize");
430
431        assert_eq!(packet.len(), HEADER_SIZE);
432
433        let (recv_token, recv_payload) = deserialize_packet(&packet).expect("deserialize");
434        assert_eq!(token, recv_token);
435        assert!(recv_payload.is_empty());
436    }
437
438    #[test]
439    fn test_packet_too_large() {
440        let token = UID::new(1, 1);
441        let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
442
443        let result = serialize_packet(token, &large_payload);
444        assert!(matches!(result, Err(WireError::PacketTooLarge { .. })));
445    }
446
447    #[test]
448    fn test_max_size_payload() {
449        let token = UID::new(1, 1);
450        let max_payload = vec![0xAB; MAX_PAYLOAD_SIZE];
451
452        let packet = serialize_packet(token, &max_payload).expect("serialize");
453        let (recv_token, recv_payload) = deserialize_packet(&packet).expect("deserialize");
454
455        assert_eq!(token, recv_token);
456        assert_eq!(max_payload, recv_payload);
457    }
458
459    #[test]
460    fn test_invalid_length_too_small() {
461        // Create packet with length field smaller than header size
462        let mut bad_packet = vec![0u8; HEADER_SIZE];
463        bad_packet[0..4].copy_from_slice(&10u32.to_le_bytes()); // length = 10 < 24
464
465        let result = deserialize_packet(&bad_packet);
466        assert!(matches!(
467            result,
468            Err(WireError::InvalidLength { length: 10 })
469        ));
470    }
471
472    #[test]
473    fn test_header_serialization() {
474        let header = PacketHeader {
475            length: 100,
476            checksum: 0xDEADBEEF,
477            token: UID::new(0x1234567890ABCDEF, 0xFEDCBA0987654321),
478        };
479
480        let mut buf = [0u8; HEADER_SIZE];
481        header.serialize_into(&mut buf);
482
483        let deserialized = PacketHeader::deserialize(&buf).expect("deserialize");
484        assert_eq!(header, deserialized);
485    }
486
487    #[test]
488    fn test_packet_structure() {
489        let token = UID::new(0x1111111111111111, 0x2222222222222222);
490        let payload = b"test";
491
492        let packet = serialize_packet(token, payload).expect("serialize");
493
494        // Verify structure
495        assert_eq!(packet.len(), HEADER_SIZE + payload.len());
496
497        // Check length field
498        let length = u32::from_le_bytes([packet[0], packet[1], packet[2], packet[3]]);
499        assert_eq!(length as usize, packet.len());
500
501        // Check token
502        let first = u64::from_le_bytes(packet[8..16].try_into().expect("slice"));
503        let second = u64::from_le_bytes(packet[16..24].try_into().expect("slice"));
504        assert_eq!(first, token.first);
505        assert_eq!(second, token.second);
506
507        // Check payload
508        assert_eq!(&packet[HEADER_SIZE..], payload.as_slice());
509    }
510}