kevy_resp/
request_borrowed.rs1use crate::argv_borrowed::ArgvBorrowed;
7use crate::error::ProtocolError;
8use crate::request::{find_crlf, parse_bulk_len, parse_int};
9
10#[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#[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#[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 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 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 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 assert!(parse_command_borrowed(b"*1\r\n$99999999999999999999\r\n").is_err());
178 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 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 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 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 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}