Skip to main content

scv_protocol/
frame.rs

1//! Newline-delimited framing, without I/O.
2//!
3//! Every SCV connection carries one JSON object per line. Frame limits are a
4//! trust boundary: a peer must not make the other side buffer without bound.
5//! [`FrameDecoder`] is the one implementation of that limit; the reading
6//! loop that feeds it lives with the I/O (`scv_client::read_frame`).
7
8use serde::Serialize;
9
10/// What a decoder does with a line longer than its limit.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Overflow {
13    /// Report [`Frame::TooLarge`] at once, consuming nothing more. The caller
14    /// is expected to drop the connection.
15    Stop,
16    /// Discard the rest of the line, then report [`Frame::TooLarge`], so the
17    /// connection can carry on with the next line.
18    Skip,
19}
20
21/// One result of decoding.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum Frame {
24    /// A complete line, without its `\n`.
25    Line(Vec<u8>),
26    /// A line over the limit.
27    TooLarge,
28    /// End of input on a line boundary.
29    End,
30    /// End of input in the middle of a line; the bytes read so far.
31    Truncated(Vec<u8>),
32}
33
34/// How much of a buffer [`FrameDecoder::feed`] used, and what it completed.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct Step {
37    /// Bytes of the buffer that belong to the decoder now; consume them.
38    pub consumed: usize,
39    /// A completed line or an overflow, if any.
40    pub frame: Option<Frame>,
41}
42
43/// Splits a byte stream into bounded lines.
44///
45/// The decoder keeps a partial line between calls, so a read loop that is
46/// cancelled between buffers loses nothing: feed what the reader has, consume
47/// exactly [`Step::consumed`], and call again.
48#[derive(Debug, Clone)]
49pub struct FrameDecoder {
50    /// Most bytes one line may take, counting its `\n`.
51    limit: usize,
52    overflow: Overflow,
53    partial: Vec<u8>,
54    discarding: bool,
55}
56
57impl FrameDecoder {
58    /// A decoder whose lines, counting the `\n`, take at most `limit` bytes.
59    pub fn new(limit: usize, overflow: Overflow) -> Self {
60        Self {
61            limit,
62            overflow,
63            partial: Vec::new(),
64            discarding: false,
65        }
66    }
67
68    /// Change the limit for the lines still to come.
69    pub fn set_limit(&mut self, limit: usize) {
70        self.limit = limit;
71    }
72
73    /// Whether no partial line is buffered or being discarded.
74    #[cfg(test)]
75    pub(crate) fn is_empty(&self) -> bool {
76        self.partial.is_empty() && !self.discarding
77    }
78
79    /// Take bytes from `available` up to and including the next `\n`.
80    pub fn feed(&mut self, available: &[u8]) -> Step {
81        let newline = available.iter().position(|byte| *byte == b'\n');
82        let take = newline.map_or(available.len(), |index| index + 1);
83        if !self.discarding {
84            if self.partial.len().saturating_add(take) > self.limit {
85                match self.overflow {
86                    Overflow::Stop => {
87                        return Step {
88                            consumed: 0,
89                            frame: Some(Frame::TooLarge),
90                        };
91                    }
92                    Overflow::Skip => {
93                        self.discarding = true;
94                        self.partial.clear();
95                    }
96                }
97            } else {
98                self.partial.extend_from_slice(&available[..take]);
99            }
100        }
101        let frame = newline.map(|_| {
102            if std::mem::take(&mut self.discarding) {
103                Frame::TooLarge
104            } else {
105                let mut line = std::mem::take(&mut self.partial);
106                line.pop();
107                Frame::Line(line)
108            }
109        });
110        Step {
111            consumed: take,
112            frame,
113        }
114    }
115
116    /// The input ended: what was left, if anything.
117    pub fn finish(&mut self) -> Frame {
118        if std::mem::take(&mut self.discarding) {
119            Frame::TooLarge
120        } else if self.partial.is_empty() {
121            Frame::End
122        } else {
123            Frame::Truncated(std::mem::take(&mut self.partial))
124        }
125    }
126}
127
128/// Strip a line's trailing `\r` (and `\n`) and check it against `max_bytes`:
129/// the rule for peers that may send CRLF and whose limit excludes the line
130/// ending. `None` means too large.
131pub fn trim_line(mut line: Vec<u8>, max_bytes: usize) -> Option<Vec<u8>> {
132    while matches!(line.last(), Some(b'\n' | b'\r')) {
133        line.pop();
134    }
135    (line.len() <= max_bytes).then_some(line)
136}
137
138/// Encode `message` as one frame: its JSON and a `\n`.
139pub fn encode_frame(message: &impl Serialize) -> serde_json::Result<Vec<u8>> {
140    let mut bytes = serde_json::to_vec(message)?;
141    bytes.push(b'\n');
142    Ok(bytes)
143}
144
145#[cfg(test)]
146mod tests;