Skip to main content

subc_protocol/
frame.rs

1//! The complete wire frame: the decoded envelope header plus its opaque body.
2//!
3//! `Frame` is the natural companion to [`EnvelopeHeader`](crate::EnvelopeHeader)
4//! — a header and the `len` opaque body bytes that follow it. It is pure data
5//! (no async, no tokio); the async read/write loop lives in `subc-transport`,
6//! the crate that owns the authenticated stream.
7
8use std::{error::Error, fmt};
9
10use crate::{EnvelopeHeader, Flags, FrameType, MAX_FRAME_BODY_LEN, PROTOCOL_VERSION};
11
12/// A complete wire frame: the decoded envelope header plus its opaque body.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Frame {
15    pub header: EnvelopeHeader,
16    pub body: Vec<u8>,
17}
18
19impl Frame {
20    /// Build a current-version frame, filling `len` from the opaque body bytes.
21    pub fn build(
22        ty: FrameType,
23        flags: Flags,
24        channel: u16,
25        epoch: u32,
26        corr: u64,
27        body: Vec<u8>,
28    ) -> Result<Self, FrameBuildError> {
29        Self::build_with_version(PROTOCOL_VERSION, ty, flags, channel, epoch, corr, body)
30    }
31
32    /// Build a frame for an already-negotiated envelope version, filling `len`
33    /// from the opaque body bytes.
34    pub fn build_with_version(
35        ver: u8,
36        ty: FrameType,
37        flags: Flags,
38        channel: u16,
39        epoch: u32,
40        corr: u64,
41        body: Vec<u8>,
42    ) -> Result<Self, FrameBuildError> {
43        // The reader rejects any frame whose declared length exceeds this cap
44        // before allocating, so a frame built larger than the cap could never be
45        // read back by a peer. Reject it here too, symmetrically, rather than emit
46        // an unreadable frame.
47        if body.len() > MAX_FRAME_BODY_LEN as usize {
48            return Err(FrameBuildError::BodyExceedsMax {
49                body_len: body.len(),
50                max: MAX_FRAME_BODY_LEN,
51            });
52        }
53        let len = u32::try_from(body.len()).map_err(|_| FrameBuildError::BodyTooLarge {
54            body_len: body.len(),
55        })?;
56        Ok(Self {
57            header: EnvelopeHeader {
58                len,
59                ver,
60                ty,
61                flags,
62                channel,
63                epoch,
64                corr,
65            },
66            body,
67        })
68    }
69
70    /// Assemble a frame from an already-decoded header and its body bytes.
71    ///
72    /// Callers must ensure `header.len == body.len()`; frame readers obtain the
73    /// body by reading exactly `header.len` bytes, so this holds by construction.
74    pub fn from_wire(header: EnvelopeHeader, body: Vec<u8>) -> Self {
75        debug_assert_eq!(header.len as usize, body.len());
76        Self { header, body }
77    }
78}
79
80/// Why a frame could not be constructed or emitted coherently.
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum FrameBuildError {
83    /// The opaque body cannot be represented by the envelope's `u32` length.
84    BodyTooLarge { body_len: usize },
85    /// The opaque body exceeds the maximum frame body the wire allows; a peer's
86    /// reader would reject it before allocating, so it must not be built.
87    BodyExceedsMax { body_len: usize, max: u32 },
88}
89
90impl fmt::Display for FrameBuildError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        match self {
93            Self::BodyTooLarge { body_len } => {
94                write!(f, "frame body is too large for u32 len: {body_len} bytes")
95            }
96            Self::BodyExceedsMax { body_len, max } => {
97                write!(f, "frame body {body_len} bytes exceeds max {max} bytes")
98            }
99        }
100    }
101}
102
103impl Error for FrameBuildError {}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn build_rejects_body_over_max_frame_len() {
111        // A reader rejects any frame whose declared length exceeds the cap before
112        // allocating, so building one larger than the cap must fail rather than
113        // produce a frame no peer can read back.
114        let body = vec![0u8; MAX_FRAME_BODY_LEN as usize + 1];
115        let err = Frame::build(
116            FrameType::Request,
117            Flags::new(false, crate::Priority::Interactive, false),
118            1,
119            0,
120            7,
121            body,
122        )
123        .expect_err("body over the cap must be rejected");
124        assert!(matches!(
125            err,
126            FrameBuildError::BodyExceedsMax { max, .. } if max == MAX_FRAME_BODY_LEN
127        ));
128    }
129
130    #[test]
131    fn build_accepts_body_at_max_frame_len() {
132        let body = vec![0u8; MAX_FRAME_BODY_LEN as usize];
133        let frame = Frame::build(
134            FrameType::Request,
135            Flags::new(false, crate::Priority::Interactive, false),
136            1,
137            0,
138            7,
139            body,
140        )
141        .expect("body exactly at the cap is allowed");
142        assert_eq!(frame.header.len, MAX_FRAME_BODY_LEN);
143    }
144}