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