Skip to main content

kevy_resp/
request_borrowed.rs

1//! Borrowed-buffer request parser: zero-copy alternative to [`parse_command`].
2//!
3//! Records each arg as a `(start, end)` range into the caller's input buffer
4//! rather than copying its bytes. Pairs with [`crate::ArgvBorrowed`].
5
6use crate::argv_borrowed::ArgvBorrowed;
7use crate::error::ProtocolError;
8use crate::request::{find_crlf, parse_bulk_len, parse_int};
9
10/// Parse one command from the front of `buf`, recording each arg as a
11/// `(start, end)` range into `buf` rather than copying its bytes.
12///
13/// The returned [`ArgvBorrowed`] is a zero-copy view: `get(i)` slices directly
14/// into `buf`. Use this on the local single-shard hot path; call
15/// [`ArgvBorrowed::into_owned`] before storing an argv past the buffer's
16/// lifetime (cross-shard dispatch, MULTI queue, AOF logging).
17///
18/// Return shape matches [`crate::parse_command`]: `Ok(Some((argv, consumed)))`,
19/// `Ok(None)` if more bytes are needed, `Err` on malformed input.
20#[inline]
21pub fn parse_command_borrowed(
22    buf: &[u8],
23) -> Result<Option<(ArgvBorrowed<'_>, usize)>, ProtocolError> {
24    if buf.is_empty() {
25        return Ok(None);
26    }
27    if buf[0] == b'*' {
28        parse_multibulk_borrowed(buf)
29    } else {
30        parse_inline_borrowed(buf)
31    }
32}
33
34// See note on `parse_inline_into`: signature symmetry with
35// `parse_multibulk_borrowed` is the point; the inline path itself can't fail.
36#[allow(clippy::unnecessary_wraps)]
37fn parse_inline_borrowed(
38    buf: &[u8],
39) -> Result<Option<(ArgvBorrowed<'_>, usize)>, ProtocolError> {
40    let Some(eol) = find_crlf(buf, 0) else {
41        return Ok(None);
42    };
43    let mut argv = ArgvBorrowed::new(buf);
44    let line = &buf[..eol];
45    let mut i = 0;
46    while i < line.len() {
47        if line[i].is_ascii_whitespace() {
48            i += 1;
49            continue;
50        }
51        let start = i;
52        while i < line.len() && !line[i].is_ascii_whitespace() {
53            i += 1;
54        }
55        argv.push_range(start, i);
56    }
57    Ok(Some((argv, eol + 2)))
58}
59
60/// Single-pass multibulk parse: each arg's header is validated and its
61/// `(start, end)` range recorded in the same walk. The old two-pass shape
62/// (`validate_multibulk_frame`, then re-scan every header to record the
63/// ranges) re-paid `find_crlf` + `parse_int` per arg — measurably ~half
64/// the whole parse cost at the 8-shard bench corner. On `Ok(None)` /
65/// `Err` the partially-built argv is simply dropped (a range vec; no
66/// bytes were copied).
67#[inline]
68fn parse_multibulk_borrowed(
69    buf: &[u8],
70) -> Result<Option<(ArgvBorrowed<'_>, usize)>, ProtocolError> {
71    let Some(hdr_end) = find_crlf(buf, 1) else {
72        return Ok(None);
73    };
74    let count =
75        parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad multibulk count"))?;
76    if count < 0 {
77        return Ok(Some((ArgvBorrowed::new(buf), hdr_end + 2)));
78    }
79    let count = count as usize;
80
81    let mut argv = ArgvBorrowed::with_capacity(buf, count);
82    let mut p = hdr_end + 2;
83    for _ in 0..count {
84        match buf.get(p) {
85            None => return Ok(None),
86            Some(b'$') => {}
87            Some(_) => return Err(ProtocolError::Malformed("expected bulk string")),
88        }
89        let Some((len, data_start)) = parse_bulk_len(buf, p)? else {
90            return Ok(None);
91        };
92        let data_end = data_start + len;
93        if buf.len() < data_end + 2 {
94            return Ok(None);
95        }
96        if &buf[data_end..data_end + 2] != b"\r\n" {
97            return Err(ProtocolError::Malformed("bulk string not CRLF-terminated"));
98        }
99        argv.push_range(data_start, data_end);
100        p = data_end + 2;
101    }
102    Ok(Some((argv, p)))
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::{encode_command, parse_command};
109
110    #[test]
111    fn borrowed_multibulk_ping() {
112        let frame = b"*1\r\n$4\r\nPING\r\n";
113        let (argv, used) = parse_command_borrowed(frame).unwrap().unwrap();
114        assert_eq!(argv.len(), 1);
115        assert_eq!(argv.first(), Some(b"PING" as &[u8]));
116        assert_eq!(used, frame.len());
117        // The arg slice points back into the original buffer (zero copy).
118        assert_eq!(argv.get(0).unwrap().as_ptr(), frame[8..].as_ptr());
119    }
120
121    #[test]
122    fn borrowed_multibulk_echo_zero_copy() {
123        let frame = b"*2\r\n$4\r\nECHO\r\n$5\r\nhello\r\n";
124        let (argv, used) = parse_command_borrowed(frame).unwrap().unwrap();
125        assert_eq!(argv, vec![b"ECHO".to_vec(), b"hello".to_vec()]);
126        assert_eq!(used, frame.len());
127        // Each arg slice's pointer falls inside the input buffer — proves
128        // the borrowed parser never copied the bytes elsewhere.
129        let base = frame.as_ptr() as usize;
130        let end = base + frame.len();
131        for i in 0..argv.len() {
132            let slice = argv.get(i).unwrap();
133            let p = slice.as_ptr() as usize;
134            assert!(
135                p >= base && p + slice.len() <= end,
136                "arg {i} not borrowed from buf"
137            );
138        }
139    }
140
141    #[test]
142    fn borrowed_incomplete_returns_none() {
143        assert!(parse_command_borrowed(b"*1\r\n$4\r\nPI").unwrap().is_none());
144        assert!(
145            parse_command_borrowed(b"*2\r\n$4\r\nECHO\r\n")
146                .unwrap()
147                .is_none()
148        );
149        assert!(parse_command_borrowed(b"").unwrap().is_none());
150    }
151
152    #[test]
153    fn borrowed_inline_command() {
154        let frame = b"PING\r\n";
155        let (argv, used) = parse_command_borrowed(frame).unwrap().unwrap();
156        assert_eq!(argv, vec![b"PING".to_vec()]);
157        assert_eq!(used, frame.len());
158
159        let frame = b"ECHO  hi there\r\n";
160        let (argv, _) = parse_command_borrowed(frame).unwrap().unwrap();
161        assert_eq!(
162            argv,
163            vec![b"ECHO".to_vec(), b"hi".to_vec(), b"there".to_vec()]
164        );
165    }
166
167    #[test]
168    fn borrowed_malformed_errors() {
169        assert!(parse_command_borrowed(b"*1\r\n+OK\r\n").is_err());
170        assert!(parse_command_borrowed(b"*x\r\n").is_err());
171        // Bulk-header malformations through the fused single-pass walk.
172        assert!(parse_command_borrowed(b"*1\r\n$x\r\n").is_err());
173        assert!(parse_command_borrowed(b"*1\r\n$\r\n").is_err());
174        assert!(parse_command_borrowed(b"*1\r\n$-1\r\n").is_err());
175        assert!(parse_command_borrowed(b"*1\r\n$3\rXabc\r\n").is_err());
176        // 20 nines overflows i64 → malformed, not a hang.
177        assert!(parse_command_borrowed(b"*1\r\n$99999999999999999999\r\n").is_err());
178        // Bulk data not CRLF-terminated.
179        assert!(parse_command_borrowed(b"*1\r\n$3\r\nabcXX").is_err());
180    }
181
182    #[test]
183    fn borrowed_incomplete_at_every_prefix_returns_none() {
184        // The single-pass parser must report Ok(None) — never Err, never
185        // Some — for every strict prefix of a valid frame (a split TCP
186        // read can land at any byte).
187        let frame = b"*3\r\n$3\r\nSET\r\n$16\r\nkey:000000000001\r\n$3\r\nxxx\r\n";
188        for cut in 0..frame.len() {
189            let r = parse_command_borrowed(&frame[..cut]);
190            assert!(
191                matches!(r, Ok(None)),
192                "prefix len {cut} gave {:?}",
193                r.map(|o| o.map(|(_, used)| used))
194            );
195        }
196        let (argv, used) = parse_command_borrowed(frame).unwrap().unwrap();
197        assert_eq!(used, frame.len());
198        assert_eq!(argv.len(), 3);
199        assert_eq!(argv.get(1), Some(b"key:000000000001" as &[u8]));
200    }
201
202    #[test]
203    fn borrowed_plus_sign_bulk_len_matches_parse_int_semantics() {
204        // parse_int accepts a leading '+'; the fused header parser keeps
205        // that acceptance so the two parsers agree on every frame.
206        let frame = b"*1\r\n$+4\r\nPING\r\n";
207        let (argv, used) = parse_command_borrowed(frame).unwrap().unwrap();
208        assert_eq!(argv, vec![b"PING".to_vec()]);
209        assert_eq!(used, frame.len());
210    }
211
212    #[test]
213    fn borrowed_null_array_yields_empty_argv() {
214        let frame = b"*-1\r\n";
215        let (argv, used) = parse_command_borrowed(frame).unwrap().unwrap();
216        assert!(argv.is_empty());
217        assert_eq!(used, frame.len());
218    }
219
220    #[test]
221    fn borrowed_into_owned_matches_parse_command() {
222        // For every well-formed frame, parse_command_borrowed(buf).into_owned()
223        // must equal parse_command(buf).0 — the materialised argv.
224        let frames: &[&[u8]] = &[
225            b"*1\r\n$4\r\nPING\r\n",
226            b"*2\r\n$4\r\nECHO\r\n$5\r\nhello\r\n",
227            b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$5\r\nvalue\r\n",
228            b"PING\r\n",
229            b"ECHO  hi there\r\n",
230        ];
231        for frame in frames {
232            let (owned, owned_used) = parse_command(frame).unwrap().unwrap();
233            let (borrowed, b_used) = parse_command_borrowed(frame).unwrap().unwrap();
234            assert_eq!(owned_used, b_used, "consumed mismatch for {frame:?}");
235            let materialised = borrowed.into_owned();
236            assert_eq!(owned, materialised, "argv mismatch for {frame:?}");
237        }
238    }
239
240    #[test]
241    fn borrowed_round_trip_command() {
242        let mut buf = Vec::new();
243        encode_command(&mut buf, &[b"SET".to_vec(), b"k".to_vec(), b"v".to_vec()]);
244        let (argv, used) = parse_command_borrowed(&buf).unwrap().unwrap();
245        assert_eq!(argv, vec![b"SET".to_vec(), b"k".to_vec(), b"v".to_vec()]);
246        assert_eq!(used, buf.len());
247    }
248
249    #[test]
250    fn borrowed_handles_split_buffer_after_consumed() {
251        // After consuming one frame, the next call sees only the remainder —
252        // the borrow scopes don't leak across calls.
253        let mut stream = Vec::new();
254        encode_command(&mut stream, &[b"PING".to_vec()]);
255        encode_command(&mut stream, &[b"ECHO".to_vec(), b"hi".to_vec()]);
256        let (a, used) = parse_command_borrowed(&stream).unwrap().unwrap();
257        assert_eq!(a, vec![b"PING".to_vec()]);
258        let (b, used2) = parse_command_borrowed(&stream[used..]).unwrap().unwrap();
259        assert_eq!(b, vec![b"ECHO".to_vec(), b"hi".to_vec()]);
260        assert_eq!(used + used2, stream.len());
261    }
262}