Skip to main content

yo_resp/
frame.rs

1//! Replies in: the general decoder, for the side of the wire that reads them.
2//!
3//! The server never parses a reply, so nothing here is on the engine's hot
4//! path. It exists for three callers that all matter:
5//!
6//! - the differential harness in `yo-compat`, which sends the same command
7//!   stream to `yo`, Redis and Valkey and compares what comes back;
8//! - the replication client, which is a Redis client wearing a different hat;
9//! - these tests, which is the only way to know that what [`crate::Out`] wrote
10//!   is what a client would read back.
11//!
12//! Frames borrow. A bulk string is a slice of the buffer it arrived in. The
13//! aggregates own a `Vec` of their children, which is an allocation per
14//! aggregate and is the right trade here: the alternative is a cursor API that
15//! every caller would have to drive by hand, and none of these callers is
16//! counting nanoseconds.
17//!
18//! Streamed aggregates and streamed strings, RESP3's `?` and `;` forms, are not
19//! decoded. Redis does not send them and no client asks for them. They return
20//! [`ProtocolError::Unsupported`] rather than being silently mis-parsed.
21
22use crate::error::ProtocolError;
23use crate::proto::Limits;
24use yo_common::num::parse_i64;
25
26/// One reply, borrowed from the buffer it arrived in.
27#[derive(Debug, Clone, PartialEq)]
28#[non_exhaustive]
29pub enum Frame<'a> {
30    /// `+`, a one line string.
31    Simple(&'a [u8]),
32    /// `-`, a one line error, prefix included.
33    Error(&'a [u8]),
34    /// `!`, RESP3's error that may span lines.
35    BlobError(&'a [u8]),
36    /// `:`, an integer.
37    Int(i64),
38    /// `$`, a length prefixed string.
39    Bulk(&'a [u8]),
40    /// A missing value: RESP2's `$-1` or `*-1`, or RESP3's `_`.
41    ///
42    /// The two RESP2 spellings decode to the same thing on purpose. A caller
43    /// that needs to tell them apart is testing the protocol rather than the
44    /// reply, and [`crate::Out`] is where that is tested.
45    Null,
46    /// `,`, a double.
47    Double(f64),
48    /// `#`, a boolean.
49    Bool(bool),
50    /// `(`, an integer too large for `i64`, as its digits.
51    BigNumber(&'a [u8]),
52    /// `=`, a string with a three byte format tag.
53    Verbatim {
54        /// The tag, such as `txt` or `mkd`.
55        format: &'a [u8],
56        /// Everything after the colon.
57        text: &'a [u8],
58    },
59    /// `*`, an ordered list.
60    Array(Vec<Frame<'a>>),
61    /// `%`, pairs in the order they were sent.
62    Map(Vec<(Frame<'a>, Frame<'a>)>),
63    /// `~`, an unordered list.
64    Set(Vec<Frame<'a>>),
65    /// `>`, an out of band message: pub/sub delivery or a cache invalidation.
66    Push(Vec<Frame<'a>>),
67    /// `|`, metadata that belongs to the frame after it.
68    ///
69    /// Returned on its own rather than attached, because attaching it would
70    /// mean every caller matching on a reply has to look through a wrapper.
71    /// A caller that cares reads the next frame; a caller that does not can
72    /// drop it, which is what RESP3 says clients may do.
73    Attribute(Vec<(Frame<'a>, Frame<'a>)>),
74}
75
76impl Frame<'_> {
77    /// Whether this is an error of either kind.
78    pub fn is_error(&self) -> bool {
79        matches!(self, Frame::Error(_) | Frame::BlobError(_))
80    }
81}
82
83/// Reads one frame from the front of `buf`.
84///
85/// Returns the frame and how many bytes it used, or `None` if the frame has not
86/// fully arrived. Pipelined replies are read by calling this again on what is
87/// left.
88///
89/// # Errors
90///
91/// Any [`ProtocolError`]. As on the request side, there is no recovering from
92/// one: the two ends disagree about where the next frame starts.
93pub fn decode<'a>(
94    buf: &'a [u8],
95    limits: &Limits,
96) -> Result<Option<(Frame<'a>, usize)>, ProtocolError> {
97    decode_at(buf, 0, limits, 0)
98}
99
100fn decode_at<'a>(
101    buf: &'a [u8],
102    at: usize,
103    limits: &Limits,
104    depth: usize,
105) -> Result<Option<(Frame<'a>, usize)>, ProtocolError> {
106    if depth > limits.max_depth {
107        return Err(ProtocolError::TooDeep);
108    }
109    let Some(&kind) = buf.get(at) else {
110        return Ok(None);
111    };
112    let Some((line, after)) = line_at(buf, at + 1) else {
113        return Ok(None);
114    };
115    match kind {
116        b'+' => Ok(Some((Frame::Simple(line), after))),
117        b'-' => Ok(Some((Frame::Error(line), after))),
118        b':' => {
119            let n = parse_i64(line).ok_or(ProtocolError::InvalidBulkLength)?;
120            Ok(Some((Frame::Int(n), after)))
121        }
122        b'_' => Ok(Some((Frame::Null, after))),
123        b'#' => match line {
124            b"t" => Ok(Some((Frame::Bool(true), after))),
125            b"f" => Ok(Some((Frame::Bool(false), after))),
126            _ => Err(ProtocolError::UnknownType(b'#')),
127        },
128        b',' => Ok(Some((Frame::Double(parse_double(line)?), after))),
129        b'(' => Ok(Some((Frame::BigNumber(line), after))),
130        b'$' | b'!' | b'=' => {
131            if line == b"?" {
132                return Err(ProtocolError::Unsupported(kind));
133            }
134            let len = parse_i64(line).ok_or(ProtocolError::InvalidBulkLength)?;
135            if len < 0 {
136                // Only `$-1` is a null. `!-1` and `=-1` do not exist.
137                return if kind == b'$' && len == -1 {
138                    Ok(Some((Frame::Null, after)))
139                } else {
140                    Err(ProtocolError::InvalidBulkLength)
141                };
142            }
143            let len = len as usize;
144            if len > limits.max_bulk {
145                return Err(ProtocolError::InvalidBulkLength);
146            }
147            if buf.len() < after + len + 2 {
148                return Ok(None);
149            }
150            let body = &buf[after..after + len];
151            let end = after + len + 2;
152            match kind {
153                b'$' => Ok(Some((Frame::Bulk(body), end))),
154                b'!' => Ok(Some((Frame::BlobError(body), end))),
155                _ => {
156                    // `=` carries a three byte format, a colon, then the text.
157                    if body.len() < 4 || body[3] != b':' {
158                        return Err(ProtocolError::InvalidBulkLength);
159                    }
160                    Ok(Some((
161                        Frame::Verbatim {
162                            format: &body[..3],
163                            text: &body[4..],
164                        },
165                        end,
166                    )))
167                }
168            }
169        }
170        b'*' | b'~' | b'>' => {
171            if line == b"?" {
172                return Err(ProtocolError::Unsupported(kind));
173            }
174            let n = parse_i64(line).ok_or(ProtocolError::InvalidMultibulkLength)?;
175            if n < 0 {
176                return if kind == b'*' && n == -1 {
177                    Ok(Some((Frame::Null, after)))
178                } else {
179                    Err(ProtocolError::InvalidMultibulkLength)
180                };
181            }
182            let Some((items, end)) = children(buf, after, n as usize, limits, depth)? else {
183                return Ok(None);
184            };
185            Ok(Some((
186                match kind {
187                    b'*' => Frame::Array(items),
188                    b'~' => Frame::Set(items),
189                    _ => Frame::Push(items),
190                },
191                end,
192            )))
193        }
194        b'%' | b'|' => {
195            if line == b"?" {
196                return Err(ProtocolError::Unsupported(kind));
197            }
198            let n = parse_i64(line).ok_or(ProtocolError::InvalidMultibulkLength)?;
199            if n < 0 {
200                return Err(ProtocolError::InvalidMultibulkLength);
201            }
202            // A map of n pairs is 2n frames on the wire.
203            let Some((items, end)) = children(buf, after, (n as usize) * 2, limits, depth)? else {
204                return Ok(None);
205            };
206            let mut pairs = Vec::with_capacity(n as usize);
207            let mut it = items.into_iter();
208            while let (Some(k), Some(v)) = (it.next(), it.next()) {
209                pairs.push((k, v));
210            }
211            Ok(Some((
212                if kind == b'%' {
213                    Frame::Map(pairs)
214                } else {
215                    Frame::Attribute(pairs)
216                },
217                end,
218            )))
219        }
220        other => Err(ProtocolError::UnknownType(other)),
221    }
222}
223
224/// Reads `n` frames in a row, or `None` if they have not all arrived.
225///
226/// The count is not used to reserve, because it comes off the wire: a `*` line
227/// claiming four billion elements would otherwise be four billion frames of
228/// capacity before the second byte of the array has been seen. The vector grows
229/// as the frames actually arrive, which bounds it by the bytes received.
230fn children<'a>(
231    buf: &'a [u8],
232    from: usize,
233    n: usize,
234    limits: &Limits,
235    depth: usize,
236) -> Result<Option<(Vec<Frame<'a>>, usize)>, ProtocolError> {
237    let mut items = Vec::new();
238    let mut at = from;
239    for _ in 0..n {
240        let Some((frame, next)) = decode_at(buf, at, limits, depth + 1)? else {
241            return Ok(None);
242        };
243        items.push(frame);
244        at = next;
245    }
246    Ok(Some((items, at)))
247}
248
249/// A double as RESP3 spells it, the three words included.
250fn parse_double(line: &[u8]) -> Result<f64, ProtocolError> {
251    match line {
252        b"inf" | b"+inf" => return Ok(f64::INFINITY),
253        b"-inf" => return Ok(f64::NEG_INFINITY),
254        b"nan" => return Ok(f64::NAN),
255        _ => {}
256    }
257    core::str::from_utf8(line)
258        .ok()
259        .and_then(|s| s.parse::<f64>().ok())
260        .ok_or(ProtocolError::UnknownType(b','))
261}
262
263/// The CRLF terminated line starting at `from`, and the offset just past it.
264fn line_at(buf: &[u8], from: usize) -> Option<(&[u8], usize)> {
265    let off = buf.get(from..)?.iter().position(|&b| b == b'\r')?;
266    let cr = from + off;
267    if buf.get(cr + 1) == Some(&b'\n') {
268        Some((&buf[from..cr], cr + 2))
269    } else {
270        None
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use crate::proto::Proto;
278    use crate::reply::Out;
279
280    fn whole(buf: &[u8]) -> Frame<'_> {
281        let (frame, used) = decode(buf, &Limits::default())
282            .expect("should not be a protocol error")
283            .expect("should be a whole frame");
284        assert_eq!(used, buf.len(), "the frame should use the whole buffer");
285        frame
286    }
287
288    #[test]
289    fn the_resp2_types_decode() {
290        assert_eq!(whole(b"+OK\r\n"), Frame::Simple(b"OK"));
291        assert_eq!(whole(b"-ERR nope\r\n"), Frame::Error(b"ERR nope"));
292        assert_eq!(whole(b":-7\r\n"), Frame::Int(-7));
293        assert_eq!(whole(b"$3\r\nabc\r\n"), Frame::Bulk(b"abc"));
294        assert_eq!(whole(b"$0\r\n\r\n"), Frame::Bulk(b""));
295        assert_eq!(whole(b"$-1\r\n"), Frame::Null);
296        assert_eq!(whole(b"*-1\r\n"), Frame::Null);
297        assert_eq!(
298            whole(b"*2\r\n$1\r\na\r\n:1\r\n"),
299            Frame::Array(vec![Frame::Bulk(b"a"), Frame::Int(1)])
300        );
301        assert_eq!(whole(b"*0\r\n"), Frame::Array(Vec::new()));
302    }
303
304    #[test]
305    fn the_resp3_types_decode() {
306        assert_eq!(whole(b"_\r\n"), Frame::Null);
307        assert_eq!(whole(b"#t\r\n"), Frame::Bool(true));
308        assert_eq!(whole(b"#f\r\n"), Frame::Bool(false));
309        assert_eq!(whole(b",1.5\r\n"), Frame::Double(1.5));
310        assert_eq!(whole(b",inf\r\n"), Frame::Double(f64::INFINITY));
311        assert_eq!(whole(b",-inf\r\n"), Frame::Double(f64::NEG_INFINITY));
312        assert_eq!(
313            whole(b"(12345678901234567890\r\n"),
314            Frame::BigNumber(b"12345678901234567890")
315        );
316        assert_eq!(whole(b"!5\r\nboom!\r\n"), Frame::BlobError(b"boom!"));
317        assert_eq!(
318            whole(b"=15\r\ntxt:Some string\r\n"),
319            Frame::Verbatim {
320                format: b"txt",
321                text: b"Some string"
322            }
323        );
324        assert_eq!(
325            whole(b"%1\r\n$1\r\na\r\n:1\r\n"),
326            Frame::Map(vec![(Frame::Bulk(b"a"), Frame::Int(1))])
327        );
328        assert_eq!(whole(b"~1\r\n:9\r\n"), Frame::Set(vec![Frame::Int(9)]));
329        assert_eq!(whole(b">1\r\n:9\r\n"), Frame::Push(vec![Frame::Int(9)]));
330        assert_eq!(
331            whole(b"|1\r\n$3\r\nttl\r\n:60\r\n"),
332            Frame::Attribute(vec![(Frame::Bulk(b"ttl"), Frame::Int(60))])
333        );
334    }
335
336    #[test]
337    fn a_nan_decodes_even_though_it_never_equals_itself() {
338        let Frame::Double(d) = whole(b",nan\r\n") else {
339            panic!("not a double")
340        };
341        assert!(d.is_nan());
342    }
343
344    /// Every prefix of a reply must say "not yet" rather than guess. A decoder
345    /// that returns a short frame from a partial buffer hands the client half
346    /// an answer, which is worse than no answer.
347    #[test]
348    fn every_prefix_of_a_reply_is_incomplete() {
349        let replies: &[&[u8]] = &[
350            b"+OK\r\n",
351            b"$5\r\nhello\r\n",
352            b"*2\r\n$1\r\na\r\n$1\r\nb\r\n",
353            b"%1\r\n$1\r\na\r\n*2\r\n:1\r\n:2\r\n",
354            b"=15\r\ntxt:Some string\r\n",
355        ];
356        for reply in replies {
357            for n in 0..reply.len() {
358                assert_eq!(
359                    decode(&reply[..n], &Limits::default()),
360                    Ok(None),
361                    "{:?} truncated to {n} bytes",
362                    core::str::from_utf8(reply).unwrap_or("?")
363                );
364            }
365            assert!(decode(reply, &Limits::default()).unwrap().is_some());
366        }
367    }
368
369    #[test]
370    fn pipelined_replies_come_out_one_at_a_time() {
371        let buf = b"+OK\r\n:1\r\n$3\r\nabc\r\n";
372        let mut at = 0;
373        let mut seen = Vec::new();
374        while let Some((frame, used)) = decode(&buf[at..], &Limits::default()).unwrap() {
375            seen.push(frame);
376            at += used;
377        }
378        assert_eq!(at, buf.len());
379        assert_eq!(
380            seen,
381            vec![Frame::Simple(b"OK"), Frame::Int(1), Frame::Bulk(b"abc")]
382        );
383    }
384
385    /// The reason the depth limit exists. Without it this is a stack overflow,
386    /// which on a server is a crash rather than an error.
387    #[test]
388    fn a_deeply_nested_reply_is_refused_rather_than_overflowing_the_stack() {
389        let mut buf = Vec::new();
390        for _ in 0..10_000 {
391            buf.extend_from_slice(b"*1\r\n");
392        }
393        buf.extend_from_slice(b":1\r\n");
394        assert_eq!(
395            decode(&buf, &Limits::default()),
396            Err(ProtocolError::TooDeep)
397        );
398    }
399
400    /// A count off the wire must not become capacity. This says four billion
401    /// elements and delivers none, and the decoder has to survive it.
402    #[test]
403    fn an_enormous_element_count_does_not_reserve_anything() {
404        assert_eq!(
405            decode(b"*4000000000\r\n", &Limits::default()),
406            Ok(None),
407            "it should be waiting for elements, not allocating for them"
408        );
409    }
410
411    #[test]
412    fn the_streamed_forms_say_so_rather_than_being_mis_parsed() {
413        for buf in [&b"$?\r\n"[..], b"*?\r\n", b"%?\r\n", b"~?\r\n"] {
414            assert!(
415                matches!(
416                    decode(buf, &Limits::default()),
417                    Err(ProtocolError::Unsupported(_))
418                ),
419                "{buf:?}"
420            );
421        }
422    }
423
424    #[test]
425    fn an_unknown_type_byte_is_named() {
426        assert_eq!(
427            decode(b"@1\r\n", &Limits::default()),
428            Err(ProtocolError::UnknownType(b'@'))
429        );
430    }
431
432    /// The round trip that makes the encoder and the decoder check each other.
433    /// Everything the encoder can write is written in both protocols and read
434    /// back, which is how a downgrade that produces bytes no client can parse
435    /// gets caught here rather than in a client library's issue tracker.
436    #[test]
437    fn everything_the_encoder_writes_reads_back() {
438        for proto in [Proto::Resp2, Proto::Resp3] {
439            let mut out = Out::new(proto);
440            out.simple(b"OK");
441            out.error(b"ERR nope");
442            out.int(-7);
443            out.bulk(b"hello");
444            out.nil();
445            out.nil_array();
446            out.bool(true);
447            out.double(1.5);
448            out.verbatim(b"txt", b"note");
449            out.big_number(b"123456789012345678901234567890");
450            out.array(2);
451            out.bulk(b"a");
452            out.int(1);
453            out.map(1);
454            out.bulk(b"k");
455            out.bulk(b"v");
456            out.set(1);
457            out.bulk(b"m");
458            out.push(2);
459            out.bulk(b"message");
460            out.bulk(b"ch");
461
462            let buf = out.into_inner();
463            let mut at = 0;
464            let mut count = 0;
465            while at < buf.len() {
466                let (_, used) = decode(&buf[at..], &Limits::default())
467                    .unwrap_or_else(|e| panic!("{proto:?} produced bytes that do not parse: {e}"))
468                    .unwrap_or_else(|| panic!("{proto:?} produced a truncated frame at {at}"));
469                at += used;
470                count += 1;
471            }
472            assert_eq!(at, buf.len());
473            // Fourteen top level frames either way. RESP2 spells seven of them
474            // differently, and the count is what proves the downgrade did not
475            // quietly drop one or split one in two.
476            assert_eq!(count, 14, "{proto:?} top level frames");
477        }
478    }
479}