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