Skip to main content

protocol/
frame.rs

1//! Framing (spec: Framed self-describing messages): every message travels as
2//! a 32-bit big-endian payload length followed by exactly one CBOR item of
3//! that length.
4//!
5//! The limit applies to the payload length. Before the handshake each side
6//! uses its own configured maximum; afterwards both use the negotiated one,
7//! the smaller of the two sides' maxima.
8
9use core::fmt;
10
11use minicbor::Decoder;
12use minicbor::data::Type;
13
14use crate::message::{Message, Method, kind};
15
16/// Bytes in a frame header.
17pub const HEADER_LEN: usize = 4;
18
19/// The default maximum frame payload: 16 MiB, room for a full-resolution PNG
20/// screenshot, as agent-device's bridge allows.
21pub const DEFAULT_MAX_FRAME: u32 = 16 * 1024 * 1024;
22
23/// The smallest maximum frame payload a side may negotiate: 4 KiB, so that
24/// hellos, errors and pings always fit.
25pub const MIN_MAX_FRAME: u32 = 4 * 1024;
26
27/// How many payload bytes of an oversized frame are read to find its kind
28/// and request id before the rest is discarded.
29const PEEK_LEN: usize = 32;
30
31/// Appends `msg` to `out` as one frame. Refuses a payload larger than `limit`,
32/// leaving `out` as it was.
33pub fn write_frame(msg: &Message, limit: u32, out: &mut Vec<u8>) -> Result<(), FrameTooLarge> {
34    let start = out.len();
35    out.extend_from_slice(&[0; HEADER_LEN]);
36    msg.encode(out);
37    let size = out.len() - start - HEADER_LEN;
38    match u32::try_from(size) {
39        Ok(len) if len <= limit => {
40            out[start..start + HEADER_LEN].copy_from_slice(&len.to_be_bytes());
41            Ok(())
42        }
43        _ => {
44            out.truncate(start);
45            Err(FrameTooLarge { size, limit })
46        }
47    }
48}
49
50/// A message whose payload is larger than the frame limit, refused by the sender.
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub struct FrameTooLarge {
53    /// Payload bytes.
54    pub size: usize,
55    pub limit: u32,
56}
57
58impl fmt::Display for FrameTooLarge {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(
61            f,
62            "a frame of {} bytes exceeds the maximum frame size of {} bytes",
63            self.size, self.limit
64        )
65    }
66}
67
68impl std::error::Error for FrameTooLarge {}
69
70/// An incoming frame whose declared length exceeds the limit. Its payload is
71/// discarded, never delivered; the kind and request id come from its first
72/// bytes when they can be read.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub struct Oversized {
75    /// The declared payload length.
76    pub size: u32,
77    pub limit: u32,
78    /// The message kind, if the payload starts like a message.
79    pub kind: Option<u8>,
80    /// The request id of a request or response.
81    pub id: Option<u32>,
82    /// The method of a request or response.
83    pub method: Option<Method>,
84}
85
86impl fmt::Display for Oversized {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(f, "a frame of {} bytes", self.size)?;
89        match (self.kind, self.method, self.id) {
90            (Some(k), Some(method), Some(id)) => {
91                write!(f, " ({method} {} for request {id})", kind::name(k))?;
92            }
93            (Some(k), None, Some(id)) => write!(f, " ({} for request {id})", kind::name(k))?,
94            (Some(k), _, None) => write!(f, " ({})", kind::name(k))?,
95            (None, ..) => {}
96        }
97        write!(
98            f,
99            " exceeds the maximum frame size of {} bytes; its payload was discarded",
100            self.limit
101        )
102    }
103}
104
105impl std::error::Error for Oversized {}
106
107/// Splits a byte stream into frame payloads, buffering partial frames.
108#[derive(Debug)]
109pub struct FrameDecoder {
110    limit: u32,
111    buf: Vec<u8>,
112    /// Where the unconsumed bytes of `buf` start.
113    start: usize,
114    /// Bytes of an oversized frame still to drop as they arrive.
115    discard: u64,
116}
117
118impl FrameDecoder {
119    pub fn new(limit: u32) -> FrameDecoder {
120        FrameDecoder {
121            limit,
122            buf: Vec::new(),
123            start: 0,
124            discard: 0,
125        }
126    }
127
128    pub fn limit(&self) -> u32 {
129        self.limit
130    }
131
132    /// Applies `limit` to frames whose header has not been read yet.
133    pub fn set_limit(&mut self, limit: u32) {
134        self.limit = limit;
135    }
136
137    /// Adds bytes read from the stream.
138    pub fn push(&mut self, bytes: &[u8]) {
139        let mut bytes = bytes;
140        if self.discard > 0 {
141            let n = usize::try_from(self.discard).map_or(bytes.len(), |d| d.min(bytes.len()));
142            self.discard -= n as u64;
143            bytes = &bytes[n..];
144        }
145        if bytes.is_empty() {
146            return;
147        }
148        if self.start > 0 {
149            self.buf.drain(..self.start);
150            self.start = 0;
151        }
152        self.buf.extend_from_slice(bytes);
153    }
154
155    /// The next complete payload, an oversized frame's report, or `None` until
156    /// more bytes arrive.
157    pub fn next_frame(&mut self) -> Option<Result<&[u8], Oversized>> {
158        if self.discard > 0 || self.buf.len() - self.start < HEADER_LEN {
159            return None;
160        }
161        let body = self.start + HEADER_LEN;
162        let header: [u8; HEADER_LEN] = self.buf[self.start..body]
163            .try_into()
164            .expect("the header is HEADER_LEN bytes");
165        let declared = u32::from_be_bytes(header);
166        let len = declared as usize;
167        let have = self.buf.len() - body;
168        if declared > self.limit {
169            let wanted = len.min(PEEK_LEN);
170            if have < wanted {
171                return None;
172            }
173            let (kind, id, method) = peek(&self.buf[body..body + wanted]);
174            let taken = have.min(len);
175            self.start = body + taken;
176            self.discard = u64::from(declared) - taken as u64;
177            return Some(Err(Oversized {
178                size: declared,
179                limit: self.limit,
180                kind,
181                id,
182                method,
183            }));
184        }
185        if have < len {
186            self.buf.reserve(len - have);
187            return None;
188        }
189        self.start = body + len;
190        Some(Ok(&self.buf[body..body + len]))
191    }
192
193    /// Whether the decoder is dropping the rest of an oversized frame.
194    pub fn is_discarding(&self) -> bool {
195        self.discard > 0
196    }
197
198    /// Bytes held for frames not yet complete.
199    pub fn buffered(&self) -> usize {
200        self.buf.len() - self.start
201    }
202}
203
204/// The kind and, for a request or response, the id and method at the start of
205/// a payload: keys 0, 1 and 2 of a map, which the encoders write first, or
206/// element 0 of an array. Whatever cannot be read is `None`.
207fn peek(prefix: &[u8]) -> (Option<u8>, Option<u32>, Option<Method>) {
208    let mut d = Decoder::new(prefix);
209    match d.datatype() {
210        Ok(Type::Map | Type::MapIndef) => {
211            let Ok(len) = d.map() else {
212                return (None, None, None);
213            };
214            let mut found_kind = None;
215            let mut id = None;
216            let mut method = None;
217            for _ in 0..len.unwrap_or(u64::MAX).min(3) {
218                let Ok(key) = d.i64() else { break };
219                let read = match key {
220                    0 => d.u8().map(|k| found_kind = Some(k)),
221                    1 => d.u32().map(|v| id = Some(v)),
222                    2 => d.u16().map(|m| method = Some(Method(m))),
223                    _ => d.skip(),
224                };
225                if read.is_err() {
226                    break;
227                }
228            }
229            if matches!(found_kind, Some(kind::REQUEST | kind::RESPONSE)) {
230                (found_kind, id, method)
231            } else {
232                (found_kind, None, None)
233            }
234        }
235        Ok(Type::Array | Type::ArrayIndef) => match d.array() {
236            Ok(_) => (d.u8().ok(), None, None),
237            Err(_) => (None, None, None),
238        },
239        _ => (None, None, None),
240    }
241}