1use crate::argv::{Argv, Command};
9use crate::error::ProtocolError;
10
11pub const MAX_MULTIBULK_LEN: usize = 1024 * 1024;
19
20pub const MAX_BULK_LEN: usize = 512 * 1024 * 1024;
26
27pub fn parse_command(buf: &[u8]) -> Result<Option<(Command, usize)>, ProtocolError> {
38 let mut argv = Argv::default();
39 match parse_command_into(buf, &mut argv)? {
40 Some(consumed) => Ok(Some((argv, consumed))),
41 None => Ok(None),
42 }
43}
44
45pub fn parse_command_into(buf: &[u8], dst: &mut Argv) -> Result<Option<usize>, ProtocolError> {
54 dst.clear();
55 if buf.is_empty() {
56 return Ok(None);
57 }
58 if buf[0] == b'*' {
59 parse_multibulk_into(buf, dst)
60 } else {
61 parse_inline_into(buf, dst)
62 }
63}
64
65#[allow(clippy::unnecessary_wraps)]
70fn parse_inline_into(buf: &[u8], dst: &mut Argv) -> Result<Option<usize>, ProtocolError> {
71 let Some(eol) = find_crlf(buf, 0) else {
72 return Ok(None);
73 };
74 let line = &buf[..eol];
75 for tok in line
76 .split(u8::is_ascii_whitespace)
77 .filter(|s| !s.is_empty())
78 {
79 dst.push(tok);
80 }
81 Ok(Some(eol + 2))
82}
83
84pub(crate) fn validate_multibulk_frame(
88 buf: &[u8],
89 start_pos: usize,
90 count: usize,
91) -> Result<Option<(usize, usize)>, ProtocolError> {
92 let mut pos = start_pos;
93 let mut total = 0usize;
94 for _ in 0..count {
95 if pos >= buf.len() {
96 return Ok(None);
97 }
98 if buf[pos] != b'$' {
99 return Err(ProtocolError::Malformed("expected bulk string"));
100 }
101 let Some(len_end) = find_crlf(buf, pos + 1) else {
102 return Ok(None);
103 };
104 let len = parse_int(&buf[pos + 1..len_end])
105 .ok_or(ProtocolError::Malformed("bad bulk length"))?;
106 if len < 0 {
107 return Err(ProtocolError::Malformed("negative bulk length in request"));
108 }
109 let len = len as usize;
110 if len > MAX_BULK_LEN {
111 return Err(ProtocolError::Malformed("bulk length exceeds proto-max-bulk-len"));
112 }
113 let data_end = len_end + 2 + len;
114 if buf.len() < data_end + 2 {
115 return Ok(None);
116 }
117 if &buf[data_end..data_end + 2] != b"\r\n" {
118 return Err(ProtocolError::Malformed("bulk string not CRLF-terminated"));
119 }
120 total += len;
121 pos = data_end + 2;
122 }
123 Ok(Some((pos, total)))
124}
125
126fn copy_multibulk_args(buf: &[u8], start_pos: usize, count: usize, dst: &mut Argv) {
129 let mut p = start_pos;
130 for _ in 0..count {
131 let len_end = find_crlf(buf, p + 1).expect("validated in pass 1");
132 let len = parse_int(&buf[p + 1..len_end]).expect("validated in pass 1") as usize;
133 let data_start = len_end + 2;
134 dst.push(&buf[data_start..data_start + len]);
135 p = data_start + len + 2;
136 }
137}
138
139fn parse_multibulk_into(buf: &[u8], dst: &mut Argv) -> Result<Option<usize>, ProtocolError> {
140 let Some(hdr_end) = find_crlf(buf, 1) else {
141 return Ok(None);
142 };
143 let count =
144 parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad multibulk count"))?;
145 if count < 0 {
146 return Ok(Some(hdr_end + 2));
148 }
149 let count = count as usize;
150 if count > MAX_MULTIBULK_LEN {
151 return Err(ProtocolError::Malformed("multibulk count exceeds limit"));
152 }
153 let start = hdr_end + 2;
154
155 let Some((end_pos, total)) = validate_multibulk_frame(buf, start, count)? else {
156 return Ok(None);
157 };
158
159 dst.reserve_for(count, total);
162 copy_multibulk_args(buf, start, count, dst);
163 Ok(Some(end_pos))
164}
165
166#[inline]
177pub(crate) fn parse_bulk_len(
178 buf: &[u8],
179 pos: usize,
180) -> Result<Option<(usize, usize)>, ProtocolError> {
181 let mut q = pos + 1;
182 let neg = match buf.get(q) {
183 None => return Ok(None),
184 Some(b'-') => {
185 q += 1;
186 true
187 }
188 Some(b'+') => {
189 q += 1;
190 false
191 }
192 _ => false,
193 };
194 let digits_start = q;
195 let mut acc: i64 = 0;
196 loop {
197 match buf.get(q) {
198 None => return Ok(None),
199 Some(&b) if b.is_ascii_digit() => {
200 acc = acc
201 .checked_mul(10)
202 .and_then(|a| a.checked_add(i64::from(b - b'0')))
203 .ok_or(ProtocolError::Malformed("bad bulk length"))?;
204 q += 1;
205 }
206 Some(b'\r') => break,
207 Some(_) => return Err(ProtocolError::Malformed("bad bulk length")),
208 }
209 }
210 if q == digits_start {
211 return Err(ProtocolError::Malformed("bad bulk length"));
212 }
213 match buf.get(q + 1) {
214 None => return Ok(None),
215 Some(b'\n') => {}
216 Some(_) => return Err(ProtocolError::Malformed("bad bulk length")),
217 }
218 if neg {
219 return Err(ProtocolError::Malformed("negative bulk length in request"));
220 }
221 let len = acc as usize;
222 if len > MAX_BULK_LEN {
223 return Err(ProtocolError::Malformed("bulk length exceeds proto-max-bulk-len"));
224 }
225 Ok(Some((len, q + 2)))
226}
227
228#[inline]
238pub(crate) fn find_crlf(buf: &[u8], start: usize) -> Option<usize> {
239 kevy_bytes::find_crlf(buf, start)
240}
241
242#[inline]
244pub(crate) fn parse_int(bytes: &[u8]) -> Option<i64> {
245 if bytes.is_empty() {
246 return None;
247 }
248 let (neg, digits) = match bytes[0] {
249 b'-' => (true, &bytes[1..]),
250 b'+' => (false, &bytes[1..]),
251 _ => (false, bytes),
252 };
253 if digits.is_empty() {
254 return None;
255 }
256 let mut acc: i64 = 0;
257 for &b in digits {
258 if !b.is_ascii_digit() {
259 return None;
260 }
261 acc = acc.checked_mul(10)?.checked_add(i64::from(b - b'0'))?;
262 }
263 Some(if neg { -acc } else { acc })
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use crate::encode_command;
270 use crate::parse_command_borrowed;
271
272 #[test]
276 fn absurd_multibulk_count_rejected_not_preallocated() {
277 let frame = b"*9999999999\r\n";
278 assert!(parse_command(frame).is_err(), "owned parser must reject");
279 assert!(parse_command_borrowed(frame).is_err(), "borrowed parser must reject");
280 let over = format!("*{}\r\n", MAX_MULTIBULK_LEN + 1);
283 assert!(parse_command(over.as_bytes()).is_err());
284 let at = format!("*{}\r\n", MAX_MULTIBULK_LEN);
285 assert!(matches!(parse_command(at.as_bytes()), Ok(None)));
286 }
287
288 #[test]
292 fn oversized_bulk_length_rejected() {
293 let over = format!("*1\r\n${}\r\n", MAX_BULK_LEN + 1);
294 assert!(parse_command(over.as_bytes()).is_err());
295 assert!(parse_command_borrowed(over.as_bytes()).is_err());
296 let ok = b"*1\r\n$5\r\n";
298 assert!(matches!(parse_command(ok), Ok(None)));
299 }
300
301 #[test]
305 fn find_crlf_at_every_offset() {
306 for off in 0..40 {
307 let mut buf = vec![b'a'; 60];
308 buf[off] = b'\r';
309 buf[off + 1] = b'\n';
310 assert_eq!(find_crlf(&buf, 0), Some(off), "off={off}");
311 }
312 }
313
314 #[test]
315 fn find_crlf_skips_lone_cr() {
316 let mut buf = vec![b'a'; 32];
318 buf[3] = b'\r';
319 buf[4] = b'b'; buf[20] = b'\r';
321 buf[21] = b'\n';
322 assert_eq!(find_crlf(&buf, 0), Some(20));
323 }
324
325 #[test]
326 fn find_crlf_none_when_absent() {
327 let buf = vec![b'a'; 32];
328 assert_eq!(find_crlf(&buf, 0), None);
329 let buf = b"";
330 assert_eq!(find_crlf(buf, 0), None);
331 let buf = b"\r"; assert_eq!(find_crlf(buf, 0), None);
333 }
334
335 #[test]
336 fn find_crlf_at_buffer_end() {
337 let buf = b"abcdefghij\r\n"; assert_eq!(find_crlf(buf, 0), Some(10));
339 assert_eq!(find_crlf(buf, 11), None);
341 }
342
343 #[test]
344 fn find_crlf_with_many_lone_crs() {
345 let mut buf = Vec::new();
348 for _ in 0..7 {
349 buf.push(b'\r');
350 buf.push(b'x'); }
352 buf.extend_from_slice(b"\r\n");
353 assert_eq!(find_crlf(&buf, 0), Some(14));
355 }
356
357 #[test]
358 fn find_crlf_from_nonzero_start() {
359 let buf = b"\r\n\r\n\r\n";
360 assert_eq!(find_crlf(buf, 0), Some(0));
362 assert_eq!(find_crlf(buf, 2), Some(2));
364 assert_eq!(find_crlf(buf, 4), Some(4));
365 }
366
367 #[test]
368 fn parse_multibulk_ping() {
369 let (cmd, used) = parse_command(b"*1\r\n$4\r\nPING\r\n").unwrap().unwrap();
370 assert_eq!(cmd, vec![b"PING".to_vec()]);
371 assert_eq!(used, 14);
372 }
373
374 #[test]
375 fn parse_multibulk_echo() {
376 let frame = b"*2\r\n$4\r\nECHO\r\n$5\r\nhello\r\n";
377 let (cmd, used) = parse_command(frame).unwrap().unwrap();
378 assert_eq!(cmd, vec![b"ECHO".to_vec(), b"hello".to_vec()]);
379 assert_eq!(used, frame.len());
380 }
381
382 #[test]
383 fn parse_incomplete_returns_none() {
384 assert_eq!(parse_command(b"*1\r\n$4\r\nPI").unwrap(), None);
385 assert_eq!(parse_command(b"*2\r\n$4\r\nECHO\r\n").unwrap(), None);
386 assert_eq!(parse_command(b"").unwrap(), None);
387 }
388
389 #[test]
390 fn parse_inline_command() {
391 let (cmd, used) = parse_command(b"PING\r\n").unwrap().unwrap();
392 assert_eq!(cmd, vec![b"PING".to_vec()]);
393 assert_eq!(used, 6);
394 let (cmd, _) = parse_command(b"ECHO hi there\r\n").unwrap().unwrap();
395 assert_eq!(
396 cmd,
397 vec![b"ECHO".to_vec(), b"hi".to_vec(), b"there".to_vec()]
398 );
399 }
400
401 #[test]
402 fn parse_malformed_errors() {
403 assert!(parse_command(b"*1\r\n+OK\r\n").is_err());
404 assert!(parse_command(b"*x\r\n").is_err());
405 }
406
407 #[test]
408 fn round_trip_command() {
409 let mut buf = Vec::new();
410 encode_command(&mut buf, &[b"SET".to_vec(), b"k".to_vec(), b"v".to_vec()]);
411 let (cmd, used) = parse_command(&buf).unwrap().unwrap();
412 assert_eq!(cmd, vec![b"SET".to_vec(), b"k".to_vec(), b"v".to_vec()]);
413 assert_eq!(used, buf.len());
414 }
415
416}