Skip to main content

yo_resp/
request.rs

1//! Requests in: the multibulk decoder and the inline decoder.
2//!
3//! This is the read half of the hot path and it does not allocate once a
4//! connection is warm. An argument is a range in the connection's own read
5//! buffer, so the command layer works on the bytes the kernel delivered and
6//! nothing is copied on the way. The one exception is an inline request with
7//! escapes in it, which has to be unescaped somewhere, and that goes into a
8//! scratch buffer on the same [`Argv`].
9//!
10//! # The contract
11//!
12//! A connection owns one [`Argv`] and one read buffer, and drives them like
13//! this:
14//!
15//! 1. Read bytes and append them to the buffer. Never insert, never reorder.
16//! 2. Call [`Argv::decode`]. On [`Step::Incomplete`], go back to 1.
17//! 3. On [`Step::Command`], read the arguments, then drop `consumed` bytes from
18//!    the front of the buffer, then go back to 2 in case the read carried more
19//!    than one command.
20//!
21//! The arguments are only valid between step 3 and the moment the buffer is
22//! drained, because they are ranges into it. That is the price of not copying
23//! and it is the reason the buffer is passed to [`Argv::arg`] rather than held.
24//!
25//! Between calls the decoder remembers how far it got, so a 512 MiB value that
26//! arrives in ten thousand pieces is scanned once rather than ten thousand
27//! times. That is the difference between linear and quadratic on a slow link
28//! and it is why the resume state exists at all.
29
30use crate::error::ProtocolError;
31use crate::proto::Limits;
32use yo_common::num::parse_i64;
33
34/// One argument: where it starts, how long it is, and which buffer it is in.
35#[derive(Debug, Clone, Copy)]
36struct Span {
37    start: usize,
38    len: u32,
39    /// True for an unescaped inline argument, which lives in the scratch buffer
40    /// rather than in the caller's read buffer.
41    scratch: bool,
42}
43
44/// What a decode attempt produced.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Step {
47    /// Nothing yet. Read more and call again with the same buffer, extended.
48    Incomplete,
49    /// A whole command. Read the arguments, then drop `consumed` bytes from the
50    /// front of the buffer.
51    ///
52    /// `consumed` can describe a command with no arguments at all, which is
53    /// what `*0\r\n` and a blank inline line are. Redis accepts both and
54    /// replies to neither, so the connection should skip them rather than
55    /// treat them as an error.
56    Command {
57        /// Bytes at the front of the buffer that this command used up.
58        consumed: usize,
59    },
60}
61
62/// A command's arguments, and the decoder that fills them.
63///
64/// One per connection, reused for the life of the connection. After the first
65/// few commands it has the capacity it needs and never allocates again.
66#[derive(Debug, Default)]
67pub struct Argv {
68    spans: Vec<Span>,
69    /// Unescaped inline arguments. Empty for every multibulk command, which is
70    /// every command a real client sends.
71    scratch: Vec<u8>,
72    /// Where the next unparsed byte is, for a command that arrived in pieces.
73    next: usize,
74    /// Arguments still to come, or `None` when no command is part way through.
75    want: Option<u32>,
76}
77
78impl Argv {
79    /// An empty one.
80    pub fn new() -> Argv {
81        Argv::default()
82    }
83
84    /// An empty one with room for `n` arguments already reserved.
85    ///
86    /// Worth doing at accept time. The first command on a connection is the
87    /// only one that would otherwise allocate.
88    pub fn with_capacity(n: usize) -> Argv {
89        Argv {
90            spans: Vec::with_capacity(n),
91            ..Argv::default()
92        }
93    }
94
95    /// How many arguments there is room for before the spans grow again.
96    ///
97    /// Only a test asks. A `Vec` reaches the allocator exactly when this
98    /// changes, so a test that wants to say a decode did not allocate can say
99    /// it by reading this on either side of the decode.
100    #[cfg(test)]
101    pub(crate) fn room(&self) -> usize {
102        self.spans.capacity()
103    }
104
105    /// Forgets everything, including any half read command.
106    ///
107    /// The connection calls this if it discards unread bytes for any reason,
108    /// because the resume state is an offset into a buffer that is about to
109    /// stop being the same buffer.
110    pub fn reset(&mut self) {
111        self.spans.clear();
112        self.scratch.clear();
113        self.next = 0;
114        self.want = None;
115    }
116
117    /// How many arguments the last complete command had.
118    pub fn len(&self) -> usize {
119        self.spans.len()
120    }
121
122    /// Whether the last complete command had no arguments.
123    pub fn is_empty(&self) -> bool {
124        self.spans.is_empty()
125    }
126
127    /// Argument `i`, or `None` if there are fewer than that many.
128    ///
129    /// `buf` must be the same buffer that was decoded and must not have been
130    /// drained since.
131    pub fn arg<'a>(&'a self, buf: &'a [u8], i: usize) -> Option<&'a [u8]> {
132        let s = self.spans.get(i)?;
133        let src = if s.scratch { &self.scratch[..] } else { buf };
134        src.get(s.start..s.start + s.len as usize)
135    }
136
137    /// Every argument in order.
138    pub fn args<'a>(&'a self, buf: &'a [u8]) -> impl Iterator<Item = &'a [u8]> {
139        (0..self.spans.len()).filter_map(move |i| self.arg(buf, i))
140    }
141
142    /// Reads one command from the front of `buf`.
143    ///
144    /// # Errors
145    ///
146    /// Any [`ProtocolError`]. Redis closes the connection after one of these
147    /// and so should the caller: a protocol error means the two ends no longer
148    /// agree on where the next frame starts, and there is no recovering from
149    /// that by reading further.
150    pub fn decode(&mut self, buf: &[u8], limits: &Limits) -> Result<Step, ProtocolError> {
151        if self.want.is_none() {
152            // A fresh command. The previous one's arguments stop being valid
153            // here rather than when it finished, so that the caller has the
154            // whole gap between the two calls to read them.
155            self.spans.clear();
156            self.scratch.clear();
157            self.next = 0;
158
159            if buf.is_empty() {
160                return Ok(Step::Incomplete);
161            }
162            if buf[0] != b'*' {
163                return self.inline(buf, limits);
164            }
165            let Some((line, after)) = line_at(buf, 1, ProtocolError::InvalidMultibulkLength)?
166            else {
167                // No end to the count line yet. A client that keeps sending
168                // digits and never a newline is not going to become valid, and
169                // the pending bytes are being held for it, so there is a bound.
170                return if buf.len() > limits.max_inline {
171                    Err(ProtocolError::TooBigMbulkCount)
172                } else {
173                    Ok(Step::Incomplete)
174                };
175            };
176            let count = parse_i64(line).ok_or(ProtocolError::InvalidMultibulkLength)?;
177            if count > limits.max_multibulk as i64 {
178                return Err(ProtocolError::InvalidMultibulkLength);
179            }
180            if count <= 0 {
181                // `*0` and `*-1` are both a command with nothing in it. Redis
182                // consumes them and replies to neither.
183                return Ok(Step::Command { consumed: after });
184            }
185            // The count is bounded above, so this reserve is bounded too. It is
186            // the only reason the limit is checked before this line.
187            self.spans.reserve(count as usize);
188            self.next = after;
189            self.want = Some(count as u32);
190        }
191
192        while self.want.is_some_and(|w| w > 0) {
193            let Some(&kind) = buf.get(self.next) else {
194                return Ok(Step::Incomplete);
195            };
196            if kind != b'$' {
197                return Err(ProtocolError::ExpectedDollar(kind));
198            }
199            let Some((line, after)) =
200                line_at(buf, self.next + 1, ProtocolError::InvalidBulkLength)?
201            else {
202                return if buf.len() - self.next > limits.max_inline {
203                    Err(ProtocolError::TooBigBulkCount)
204                } else {
205                    Ok(Step::Incomplete)
206                };
207            };
208            let len = parse_i64(line).ok_or(ProtocolError::InvalidBulkLength)?;
209            if len < 0 || len > limits.max_bulk as i64 {
210                return Err(ProtocolError::InvalidBulkLength);
211            }
212            let len = len as usize;
213            // The body and its trailing CRLF, which is not optional and is not
214            // checked here: a client that lies about it desynchronises itself
215            // and the next `expected '$'` says so.
216            if buf.len() < after + len + 2 {
217                return Ok(Step::Incomplete);
218            }
219            self.spans.push(Span {
220                start: after,
221                len: len as u32,
222                scratch: false,
223            });
224            self.next = after + len + 2;
225            self.want = self.want.map(|w| w - 1);
226        }
227
228        let consumed = self.next;
229        self.want = None;
230        self.next = 0;
231        Ok(Step::Command { consumed })
232    }
233
234    /// A telnet style request: one line, split on whitespace, quotes honoured.
235    ///
236    /// Cold by construction. Nothing that cares about speed sends inline
237    /// commands, and the unescaping copies, which is why this is the one path
238    /// that touches the scratch buffer.
239    fn inline(&mut self, buf: &[u8], limits: &Limits) -> Result<Step, ProtocolError> {
240        let Some(nl) = buf.iter().position(|&b| b == b'\n') else {
241            return if buf.len() > limits.max_inline {
242                Err(ProtocolError::TooBigInline)
243            } else {
244                Ok(Step::Incomplete)
245            };
246        };
247        let mut line = &buf[..nl];
248        if line.last() == Some(&b'\r') {
249            line = &line[..line.len() - 1];
250        }
251        self.split_inline(line)?;
252        Ok(Step::Command { consumed: nl + 1 })
253    }
254
255    /// Redis's `sdssplitargs`, byte for byte.
256    ///
257    /// Reimplemented rather than approximated because `redis-cli` sends inline
258    /// commands in some modes and the test suite has cases for every corner of
259    /// it: `\x41` hex escapes inside double quotes, `\'` inside single quotes,
260    /// and the rule that a closing quote must be followed by whitespace or by
261    /// the end of the line.
262    fn split_inline(&mut self, line: &[u8]) -> Result<(), ProtocolError> {
263        let mut i = 0;
264        loop {
265            while i < line.len() && is_space(line[i]) {
266                i += 1;
267            }
268            if i >= line.len() {
269                return Ok(());
270            }
271            let start = self.scratch.len();
272            let mut in_double = false;
273            let mut in_single = false;
274            let mut done = false;
275            while !done {
276                let c = line.get(i).copied();
277                if in_double {
278                    match c {
279                        Some(b'\\')
280                            if i + 3 < line.len()
281                                && line[i + 1] == b'x'
282                                && hex(line[i + 2]).is_some()
283                                && hex(line[i + 3]).is_some() =>
284                        {
285                            let hi = hex(line[i + 2]).unwrap_or(0);
286                            let lo = hex(line[i + 3]).unwrap_or(0);
287                            self.scratch.push(hi * 16 + lo);
288                            i += 3;
289                        }
290                        Some(b'\\') if i + 1 < line.len() => {
291                            i += 1;
292                            self.scratch.push(match line[i] {
293                                b'n' => b'\n',
294                                b'r' => b'\r',
295                                b't' => b'\t',
296                                b'b' => 0x08,
297                                b'a' => 0x07,
298                                other => other,
299                            });
300                        }
301                        Some(b'"') => {
302                            if line.get(i + 1).is_some_and(|&n| !is_space(n)) {
303                                return Err(ProtocolError::UnbalancedQuotes);
304                            }
305                            done = true;
306                        }
307                        None => return Err(ProtocolError::UnbalancedQuotes),
308                        Some(ch) => self.scratch.push(ch),
309                    }
310                } else if in_single {
311                    match c {
312                        Some(b'\\') if line.get(i + 1) == Some(&b'\'') => {
313                            i += 1;
314                            self.scratch.push(b'\'');
315                        }
316                        Some(b'\'') => {
317                            if line.get(i + 1).is_some_and(|&n| !is_space(n)) {
318                                return Err(ProtocolError::UnbalancedQuotes);
319                            }
320                            done = true;
321                        }
322                        None => return Err(ProtocolError::UnbalancedQuotes),
323                        Some(ch) => self.scratch.push(ch),
324                    }
325                } else {
326                    match c {
327                        None | Some(b' ') | Some(b'\n') | Some(b'\r') | Some(b'\t') => done = true,
328                        Some(b'"') => in_double = true,
329                        Some(b'\'') => in_single = true,
330                        Some(ch) => self.scratch.push(ch),
331                    }
332                }
333                if i < line.len() {
334                    i += 1;
335                }
336            }
337            let len = self.scratch.len() - start;
338            self.spans.push(Span {
339                start,
340                len: len as u32,
341                scratch: true,
342            });
343        }
344    }
345}
346
347/// C's `isspace`, which includes the vertical tab that Rust's does not.
348#[inline]
349const fn is_space(b: u8) -> bool {
350    matches!(b, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
351}
352
353/// The value of one hex digit.
354#[inline]
355const fn hex(b: u8) -> Option<u8> {
356    match b {
357        b'0'..=b'9' => Some(b - b'0'),
358        b'a'..=b'f' => Some(b - b'a' + 10),
359        b'A'..=b'F' => Some(b - b'A' + 10),
360        _ => None,
361    }
362}
363
364/// The CRLF terminated line starting at `from`, and the offset just past it.
365///
366/// `Ok(None)` means the line has not arrived yet. `bad` is the error to raise
367/// for a `\r` that is not followed by `\n`, which is the one place this is
368/// stricter than Redis: Redis finds the `\r`, assumes the `\n` and carries on,
369/// which desynchronises a byte later with a different message. Both ends close
370/// the connection either way, so what differs is the text and not the outcome.
371fn line_at(
372    buf: &[u8],
373    from: usize,
374    bad: ProtocolError,
375) -> Result<Option<(&[u8], usize)>, ProtocolError> {
376    let Some(off) = buf[from..].iter().position(|&b| b == b'\r') else {
377        return Ok(None);
378    };
379    let cr = from + off;
380    match buf.get(cr + 1) {
381        None => Ok(None),
382        Some(&b'\n') => Ok(Some((&buf[from..cr], cr + 2))),
383        Some(_) => Err(bad),
384    }
385}
386
387#[cfg(test)]
388mod tests {
389    use super::*;
390
391    /// Decodes one command from a complete buffer and returns its arguments as
392    /// owned bytes, which the tests can compare without holding the buffer.
393    fn one(buf: &[u8]) -> Result<(Vec<Vec<u8>>, usize), ProtocolError> {
394        let mut argv = Argv::new();
395        match argv.decode(buf, &Limits::default())? {
396            Step::Incomplete => panic!("expected a whole command in {buf:?}"),
397            Step::Command { consumed } => Ok((
398                argv.args(buf).map(<[u8]>::to_vec).collect::<Vec<_>>(),
399                consumed,
400            )),
401        }
402    }
403
404    fn words(buf: &[u8]) -> Vec<Vec<u8>> {
405        one(buf).expect("should decode").0
406    }
407
408    #[test]
409    fn a_multibulk_command_comes_out_as_its_arguments() {
410        let (args, consumed) = one(b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$1\r\nv\r\n").unwrap();
411        assert_eq!(args, vec![b"SET".to_vec(), b"k".to_vec(), b"v".to_vec()]);
412        assert_eq!(consumed, 27);
413    }
414
415    #[test]
416    fn an_empty_argument_is_an_argument() {
417        assert_eq!(
418            words(b"*2\r\n$3\r\nGET\r\n$0\r\n\r\n"),
419            vec![b"GET".to_vec(), Vec::new()]
420        );
421    }
422
423    #[test]
424    fn a_value_can_hold_anything_including_crlf() {
425        let args = words(b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$4\r\na\r\nb\r\n");
426        assert_eq!(args[2], b"a\r\nb".to_vec());
427    }
428
429    /// The pipelining case: two commands in one read, and the second one only
430    /// found because the first reported what it used.
431    #[test]
432    fn commands_come_out_one_at_a_time_from_one_buffer() {
433        let buf = b"*1\r\n$4\r\nPING\r\n*2\r\n$3\r\nGET\r\n$1\r\nk\r\n";
434        let mut argv = Argv::new();
435        let mut at = 0;
436        let mut seen: Vec<Vec<Vec<u8>>> = Vec::new();
437        loop {
438            match argv.decode(&buf[at..], &Limits::default()).unwrap() {
439                Step::Incomplete => break,
440                Step::Command { consumed } => {
441                    seen.push(argv.args(&buf[at..]).map(<[u8]>::to_vec).collect());
442                    at += consumed;
443                }
444            }
445        }
446        assert_eq!(at, buf.len());
447        assert_eq!(seen.len(), 2);
448        assert_eq!(seen[0], vec![b"PING".to_vec()]);
449        assert_eq!(seen[1], vec![b"GET".to_vec(), b"k".to_vec()]);
450    }
451
452    /// Fed one byte at a time, which is the shape a slow link produces and the
453    /// shape that finds an off by one in the resume state. Every prefix must
454    /// say incomplete and the last byte must produce exactly the same command
455    /// as the whole buffer at once.
456    #[test]
457    fn a_command_arriving_one_byte_at_a_time_decodes_once_at_the_end() {
458        let whole = b"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n";
459        let mut argv = Argv::new();
460        for n in 0..whole.len() {
461            assert_eq!(
462                argv.decode(&whole[..n], &Limits::default()).unwrap(),
463                Step::Incomplete,
464                "the first {n} bytes should not be a command"
465            );
466        }
467        let step = argv.decode(whole, &Limits::default()).unwrap();
468        assert_eq!(
469            step,
470            Step::Command {
471                consumed: whole.len()
472            }
473        );
474        assert_eq!(
475            argv.args(whole).map(<[u8]>::to_vec).collect::<Vec<_>>(),
476            vec![b"SET".to_vec(), b"hello".to_vec(), b"world".to_vec()]
477        );
478    }
479
480    /// The resume state is what stops a value that arrives in pieces from being
481    /// rescanned once per piece. This checks the state actually moves, because
482    /// a decoder that quietly restarted every time would pass every other test
483    /// here and be quadratic on a real link.
484    #[test]
485    fn a_partly_arrived_command_remembers_where_it_got_to() {
486        let mut argv = Argv::new();
487        let head = b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$10\r\nabc";
488        assert_eq!(
489            argv.decode(head, &Limits::default()).unwrap(),
490            Step::Incomplete
491        );
492        assert_eq!(argv.want, Some(1), "two of three arguments are in");
493        assert_eq!(argv.next, 20, "the third argument's body starts here");
494    }
495
496    #[test]
497    fn an_empty_command_is_consumed_and_has_no_arguments() {
498        for buf in [&b"*0\r\n"[..], b"*-1\r\n"] {
499            let (args, consumed) = one(buf).unwrap();
500            assert!(args.is_empty(), "{buf:?}");
501            assert_eq!(consumed, buf.len());
502        }
503    }
504
505    #[test]
506    fn an_inline_command_is_split_on_whitespace() {
507        assert_eq!(words(b"PING\r\n"), vec![b"PING".to_vec()]);
508        assert_eq!(
509            words(b"SET  key   value\n"),
510            vec![b"SET".to_vec(), b"key".to_vec(), b"value".to_vec()]
511        );
512        assert!(words(b"\r\n").is_empty());
513        assert!(words(b"   \n").is_empty());
514    }
515
516    #[test]
517    fn inline_quotes_and_escapes_follow_redis() {
518        assert_eq!(
519            words(b"SET k \"a b\"\r\n"),
520            vec![b"SET".to_vec(), b"k".to_vec(), b"a b".to_vec()]
521        );
522        assert_eq!(words(b"ECHO \"\\x41\\x42\"\r\n")[1], b"AB".to_vec());
523        assert_eq!(words(b"ECHO \"a\\nb\"\r\n")[1], b"a\nb".to_vec());
524        assert_eq!(words(b"ECHO 'it\\'s'\r\n")[1], b"it's".to_vec());
525        assert_eq!(words(b"ECHO \"\"\r\n")[1], Vec::<u8>::new());
526    }
527
528    #[test]
529    fn an_unclosed_or_misplaced_quote_is_an_error() {
530        for bad in [
531            &b"ECHO \"abc\r\n"[..],
532            b"ECHO 'abc\r\n",
533            b"ECHO \"abc\"d\r\n",
534            b"ECHO 'abc'd\r\n",
535        ] {
536            let mut argv = Argv::new();
537            assert_eq!(
538                argv.decode(bad, &Limits::default()),
539                Err(ProtocolError::UnbalancedQuotes),
540                "{bad:?}"
541            );
542        }
543    }
544
545    #[test]
546    fn the_wrong_type_byte_where_an_argument_belongs_names_the_byte() {
547        let mut argv = Argv::new();
548        assert_eq!(
549            argv.decode(b"*1\r\n+OK\r\n", &Limits::default()),
550            Err(ProtocolError::ExpectedDollar(b'+'))
551        );
552    }
553
554    #[test]
555    fn lengths_that_are_not_lengths_are_refused() {
556        let cases: &[(&[u8], ProtocolError)] = &[
557            (b"*x\r\n", ProtocolError::InvalidMultibulkLength),
558            (b"*\r\n", ProtocolError::InvalidMultibulkLength),
559            (b"*01\r\n", ProtocolError::InvalidMultibulkLength),
560            (
561                b"*99999999999999999999\r\n",
562                ProtocolError::InvalidMultibulkLength,
563            ),
564            (b"*2\r\n$x\r\n", ProtocolError::InvalidBulkLength),
565            (b"*2\r\n$-1\r\n", ProtocolError::InvalidBulkLength),
566        ];
567        for &(buf, want) in cases {
568            let mut argv = Argv::new();
569            assert_eq!(argv.decode(buf, &Limits::default()), Err(want), "{buf:?}");
570        }
571    }
572
573    /// A count of two billion must be refused before anything is reserved for
574    /// it. If this ever regresses the symptom is not a wrong answer, it is the
575    /// process disappearing.
576    #[test]
577    fn an_enormous_count_is_refused_rather_than_reserved() {
578        let mut argv = Argv::new();
579        assert_eq!(
580            argv.decode(b"*2000000000\r\n", &Limits::default()),
581            Err(ProtocolError::InvalidMultibulkLength)
582        );
583        assert_eq!(
584            argv.spans.capacity(),
585            0,
586            "nothing should have been reserved"
587        );
588    }
589
590    #[test]
591    fn a_bulk_past_the_limit_is_refused() {
592        let limits = Limits {
593            max_bulk: 16,
594            ..Limits::default()
595        };
596        let mut argv = Argv::new();
597        assert_eq!(
598            argv.decode(b"*1\r\n$17\r\n", &limits),
599            Err(ProtocolError::InvalidBulkLength)
600        );
601        // One under the limit is still a matter of waiting for the body.
602        let mut argv = Argv::new();
603        assert_eq!(argv.decode(b"*1\r\n$16\r\n", &limits), Ok(Step::Incomplete));
604    }
605
606    #[test]
607    fn a_line_that_never_ends_is_refused_rather_than_buffered_forever() {
608        let limits = Limits {
609            max_inline: 8,
610            ..Limits::default()
611        };
612        let mut argv = Argv::new();
613        assert_eq!(
614            argv.decode(b"*123456789", &limits),
615            Err(ProtocolError::TooBigMbulkCount)
616        );
617        let mut argv = Argv::new();
618        assert_eq!(
619            argv.decode(b"*1\r\n$123456789", &limits),
620            Err(ProtocolError::TooBigBulkCount)
621        );
622        let mut argv = Argv::new();
623        assert_eq!(
624            argv.decode(b"PING PING PING", &limits),
625            Err(ProtocolError::TooBigInline)
626        );
627    }
628
629    #[test]
630    fn a_carriage_return_with_no_newline_after_it_is_a_protocol_error() {
631        let mut argv = Argv::new();
632        assert_eq!(
633            argv.decode(b"*1\rx", &Limits::default()),
634            Err(ProtocolError::InvalidMultibulkLength)
635        );
636    }
637
638    #[test]
639    fn a_reset_forgets_a_half_read_command() {
640        let mut argv = Argv::new();
641        assert_eq!(
642            argv.decode(b"*2\r\n$3\r\nGET\r\n", &Limits::default())
643                .unwrap(),
644            Step::Incomplete
645        );
646        assert_eq!(argv.want, Some(1));
647        argv.reset();
648        assert_eq!(argv.want, None);
649        assert_eq!(argv.next, 0);
650        // And the next command on the fresh buffer decodes from the start.
651        assert_eq!(
652            argv.decode(b"*1\r\n$4\r\nPING\r\n", &Limits::default())
653                .unwrap(),
654            Step::Command { consumed: 14 }
655        );
656    }
657
658    /// Once the spans have been read the connection reuses the buffer, so a
659    /// decoder that kept stale spans would hand the next command's caller the
660    /// previous command's bytes.
661    #[test]
662    fn arguments_do_not_survive_into_the_next_command() {
663        let mut argv = Argv::new();
664        argv.decode(b"*2\r\n$3\r\nGET\r\n$1\r\nk\r\n", &Limits::default())
665            .unwrap();
666        assert_eq!(argv.len(), 2);
667        argv.decode(b"*1\r\n$4\r\nPING\r\n", &Limits::default())
668            .unwrap();
669        assert_eq!(argv.len(), 1);
670        assert_eq!(argv.arg(b"*1\r\n$4\r\nPING\r\n", 1), None);
671    }
672}