Skip to main content

phantom_protocol/transport/legs/embedded/
framing.rs

1//! Length-prefix framing for `EmbeddedLeg`.
2//!
3//! `embedded-io-async` is a byte stream; `SessionTransport` is message-
4//! oriented. Each frame is `[len: u32 BE][payload]` — byte-identical to
5//! `TcpSessionTransport`'s framing, so an embedded client and a TCP server
6//! interoperate at this layer. Pure: no I/O and no state — the leg drives the
7//! actual reads and writes and owns the buffer.
8//!
9//! Phase 3.6 (no-std foundation): pure logic, zero imports outside the
10//! prelude. The crate-level `#![cfg_attr(not(feature = "std"), no_std)]` in
11//! `lib.rs` drives the no_std switch; this module needs no extra attribute.
12
13/// Width of the big-endian length prefix prepended to every frame.
14pub const HEADER_LEN: usize = 4;
15
16/// Errors from length-prefix framing.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum FramingError {
19    /// A frame length — declared on the wire, or requested for send — exceeds
20    /// the fixed buffer capacity it has to fit in.
21    FrameTooLarge {
22        /// The offending frame length, in bytes.
23        len: usize,
24        /// The buffer capacity it overran, in bytes.
25        capacity: usize,
26    },
27}
28
29/// Encode the big-endian length prefix for an outbound frame of `payload_len`
30/// bytes, validating it fits a receiver buffer of `capacity` bytes.
31pub fn encode_header(
32    payload_len: usize,
33    capacity: usize,
34) -> Result<[u8; HEADER_LEN], FramingError> {
35    if payload_len > capacity {
36        return Err(FramingError::FrameTooLarge {
37            len: payload_len,
38            capacity,
39        });
40    }
41    match u32::try_from(payload_len) {
42        Ok(len) => Ok(len.to_be_bytes()),
43        Err(_) => Err(FramingError::FrameTooLarge {
44            len: payload_len,
45            capacity,
46        }),
47    }
48}
49
50/// Decode a big-endian length prefix, validating the frame it announces fits
51/// a receiver buffer of `capacity` bytes.
52pub fn decode_header(header: &[u8; HEADER_LEN], capacity: usize) -> Result<usize, FramingError> {
53    let len = u32::from_be_bytes(*header) as usize;
54    if len > capacity {
55        return Err(FramingError::FrameTooLarge { len, capacity });
56    }
57    Ok(len)
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn encode_header_writes_big_endian_length() {
66        let header = encode_header(0x0102_0304, 0x1000_0000).expect("within capacity");
67        assert_eq!(header, [0x01, 0x02, 0x03, 0x04]);
68    }
69
70    #[test]
71    fn encode_header_rejects_length_over_capacity() {
72        let err = encode_header(2000, 1024).expect_err("2000 > 1024 capacity");
73        assert_eq!(
74            err,
75            FramingError::FrameTooLarge {
76                len: 2000,
77                capacity: 1024
78            }
79        );
80    }
81
82    #[test]
83    #[cfg(target_pointer_width = "64")]
84    fn encode_header_rejects_length_over_u32_max() {
85        // The 4-byte wire prefix cannot express a length past u32::MAX, even
86        // if the receiver buffer notionally could. Only reachable on 64-bit
87        // hosts; on 32-bit targets `usize` already caps at u32::MAX.
88        let huge = u32::MAX as usize + 1;
89        let err = encode_header(huge, usize::MAX).expect_err("over u32::MAX wire limit");
90        assert_eq!(
91            err,
92            FramingError::FrameTooLarge {
93                len: huge,
94                capacity: usize::MAX
95            }
96        );
97    }
98
99    #[test]
100    fn encode_decode_round_trips() {
101        let capacity = 0x1000_0000;
102        for len in [0_usize, 1, 255, 0x0102_0304] {
103            let header = encode_header(len, capacity).expect("within capacity");
104            let decoded = decode_header(&header, capacity).expect("within capacity");
105            assert_eq!(decoded, len, "round-trip failed for len {len}");
106        }
107    }
108
109    #[test]
110    fn decode_header_rejects_length_over_capacity() {
111        // 0x0000_0800 = 2048; the receiver buffer is only 1024 bytes.
112        let err = decode_header(&[0x00, 0x00, 0x08, 0x00], 1024).expect_err("2048 > 1024 capacity");
113        assert_eq!(
114            err,
115            FramingError::FrameTooLarge {
116                len: 2048,
117                capacity: 1024
118            }
119        );
120    }
121}