Skip to main content

yo_resp/
proto.rs

1//! The protocol version a connection is speaking, and the limits it is held to.
2
3/// RESP2 or RESP3, per connection.
4///
5/// A connection starts at RESP2 and moves to RESP3 when the client sends
6/// `HELLO 3`. It can move back. The version is a property of the connection and
7/// never of the command, which is why every reply writer takes it once at
8/// construction rather than at each call.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum Proto {
11    /// The protocol every client understands. Five types and nothing else.
12    #[default]
13    Resp2,
14    /// Redis 6 and later. Maps, sets, doubles, booleans, push messages, and a
15    /// null that is not a length of minus one.
16    Resp3,
17}
18
19impl Proto {
20    /// The number a `HELLO` reply reports, and the number `HELLO` takes.
21    #[inline]
22    pub const fn version(self) -> i64 {
23        match self {
24            Proto::Resp2 => 2,
25            Proto::Resp3 => 3,
26        }
27    }
28
29    /// The protocol for a `HELLO` argument, or `None` if there is no such
30    /// version. `HELLO 4` is an error and this is where that is decided.
31    #[inline]
32    pub const fn from_version(v: i64) -> Option<Proto> {
33        match v {
34            2 => Some(Proto::Resp2),
35            3 => Some(Proto::Resp3),
36            _ => None,
37        }
38    }
39
40    /// Whether the richer type set is available.
41    #[inline]
42    pub const fn is_resp3(self) -> bool {
43        matches!(self, Proto::Resp3)
44    }
45}
46
47/// The bounds the codec enforces, which are Redis's bounds.
48///
49/// These are not tuning knobs, they are the difference between a protocol error
50/// and an allocation the size of whatever a stranger asked for. A count line
51/// saying two billion arguments has to be refused before anything is reserved
52/// for it, which is why the multibulk limit is checked against the parsed
53/// number and not against what arrives.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct Limits {
56    /// The largest `*` count accepted. Redis's `PROTO_MAX_MULTIBULK`.
57    pub max_multibulk: usize,
58    /// The largest `$` length accepted. Redis's `proto-max-bulk-len`, which is
59    /// configurable there and so is configurable here.
60    pub max_bulk: usize,
61    /// The longest an inline request or an unterminated count line may get
62    /// before it is refused. Redis's `PROTO_INLINE_MAX_SIZE`.
63    pub max_inline: usize,
64    /// How deeply a reply may nest before the decoder gives up.
65    ///
66    /// Redis has no equivalent because Redis never parses a reply. This exists
67    /// because the reply decoder recurses, and `*1\r\n` repeated a million
68    /// times would otherwise be a stack overflow rather than an error. There is
69    /// no legitimate reply anywhere near this deep.
70    pub max_depth: usize,
71}
72
73impl Limits {
74    /// Redis's defaults: a million arguments, a 512 MiB bulk, a 64 KiB inline
75    /// request.
76    pub const DEFAULT: Limits = Limits {
77        max_multibulk: 1024 * 1024,
78        max_bulk: 512 * 1024 * 1024,
79        max_inline: 64 * 1024,
80        max_depth: 128,
81    };
82}
83
84impl Default for Limits {
85    fn default() -> Limits {
86        Limits::DEFAULT
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn a_connection_starts_at_resp2() {
96        assert_eq!(Proto::default(), Proto::Resp2);
97        assert!(!Proto::default().is_resp3());
98    }
99
100    #[test]
101    fn hello_takes_two_and_three_and_nothing_else() {
102        assert_eq!(Proto::from_version(2), Some(Proto::Resp2));
103        assert_eq!(Proto::from_version(3), Some(Proto::Resp3));
104        for v in [-1, 0, 1, 4, 300] {
105            assert_eq!(Proto::from_version(v), None, "HELLO {v}");
106        }
107        for p in [Proto::Resp2, Proto::Resp3] {
108            assert_eq!(Proto::from_version(p.version()), Some(p));
109        }
110    }
111
112    #[test]
113    fn the_limits_are_the_redis_numbers() {
114        let l = Limits::default();
115        assert_eq!(l.max_multibulk, 1024 * 1024);
116        assert_eq!(l.max_bulk, 512 * 1024 * 1024);
117        assert_eq!(l.max_inline, 64 * 1024);
118    }
119}