1use rustlavel_core::{Error, Result};
26
27const MAX_BULK: i64 = 512 * 1024 * 1024;
30
31const MAX_DEPTH: usize = 32;
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum Value {
37 Simple(String),
39 Error(String),
42 Integer(i64),
44 Bulk(Vec<u8>),
46 Nil,
49 Array(Vec<Value>),
51}
52
53impl Value {
54 pub fn as_str(&self) -> Option<&str> {
56 match self {
57 Value::Simple(s) => Some(s),
58 Value::Bulk(bytes) => std::str::from_utf8(bytes).ok(),
59 _ => None,
60 }
61 }
62
63 pub fn as_bytes(&self) -> Option<&[u8]> {
64 match self {
65 Value::Simple(s) => Some(s.as_bytes()),
66 Value::Bulk(bytes) => Some(bytes),
67 _ => None,
68 }
69 }
70
71 pub fn as_i64(&self) -> Option<i64> {
74 match self {
75 Value::Integer(n) => Some(*n),
76 Value::Simple(s) => s.trim().parse().ok(),
77 Value::Bulk(bytes) => std::str::from_utf8(bytes).ok()?.trim().parse().ok(),
78 _ => None,
79 }
80 }
81
82 pub fn is_nil(&self) -> bool {
83 matches!(self, Value::Nil)
84 }
85
86 pub fn into_result(self) -> Result<Value> {
88 match self {
89 Value::Error(message) => Err(Error::msg(format!("redis: {message}"))),
90 other => Ok(other),
91 }
92 }
93
94 pub fn encode(&self) -> Vec<u8> {
97 let mut out = Vec::new();
98 self.encode_into(&mut out);
99 out
100 }
101
102 fn encode_into(&self, out: &mut Vec<u8>) {
103 match self {
104 Value::Simple(s) => {
105 out.push(b'+');
106 out.extend_from_slice(s.as_bytes());
107 out.extend_from_slice(b"\r\n");
108 }
109 Value::Error(s) => {
110 out.push(b'-');
111 out.extend_from_slice(s.as_bytes());
112 out.extend_from_slice(b"\r\n");
113 }
114 Value::Integer(n) => {
115 out.push(b':');
116 out.extend_from_slice(n.to_string().as_bytes());
117 out.extend_from_slice(b"\r\n");
118 }
119 Value::Bulk(bytes) => {
120 out.push(b'$');
121 out.extend_from_slice(bytes.len().to_string().as_bytes());
122 out.extend_from_slice(b"\r\n");
123 out.extend_from_slice(bytes);
124 out.extend_from_slice(b"\r\n");
125 }
126 Value::Nil => out.extend_from_slice(b"$-1\r\n"),
127 Value::Array(items) => {
128 out.push(b'*');
129 out.extend_from_slice(items.len().to_string().as_bytes());
130 out.extend_from_slice(b"\r\n");
131 for item in items {
132 item.encode_into(out);
133 }
134 }
135 }
136 }
137}
138
139pub fn encode_command(args: &[&[u8]]) -> Vec<u8> {
144 let mut out = Vec::with_capacity(16 + args.iter().map(|a| a.len() + 16).sum::<usize>());
145 out.push(b'*');
146 out.extend_from_slice(args.len().to_string().as_bytes());
147 out.extend_from_slice(b"\r\n");
148
149 for arg in args {
150 out.push(b'$');
151 out.extend_from_slice(arg.len().to_string().as_bytes());
152 out.extend_from_slice(b"\r\n");
153 out.extend_from_slice(arg);
154 out.extend_from_slice(b"\r\n");
155 }
156 out
157}
158
159pub fn decode(input: &[u8]) -> Result<Option<(Value, usize)>> {
164 decode_at(input, 0, 0)
165}
166
167fn decode_at(input: &[u8], from: usize, depth: usize) -> Result<Option<(Value, usize)>> {
168 if depth > MAX_DEPTH {
169 return Err(protocol("reply nests deeper than this client will follow"));
170 }
171 let Some(&tag) = input.get(from) else {
172 return Ok(None);
173 };
174
175 let Some((line, after_line)) = read_line(input, from + 1)? else {
176 return Ok(None);
177 };
178
179 match tag {
180 b'+' => Ok(Some((Value::Simple(to_text(line)?), after_line))),
181 b'-' => Ok(Some((Value::Error(to_text(line)?), after_line))),
182 b':' => Ok(Some((Value::Integer(parse_int(line)?), after_line))),
183 b'$' => {
184 let length = parse_int(line)?;
185 if length < 0 {
186 return Ok(Some((Value::Nil, after_line)));
189 }
190 if length > MAX_BULK {
191 return Err(protocol(&format!("bulk reply of {length} bytes is implausible")));
192 }
193
194 let length = length as usize;
195 if input.len() < after_line + length + 2 {
197 return Ok(None);
198 }
199 let payload = input[after_line..after_line + length].to_vec();
200 Ok(Some((Value::Bulk(payload), after_line + length + 2)))
201 }
202 b'*' => {
203 let count = parse_int(line)?;
204 if count < 0 {
205 return Ok(Some((Value::Nil, after_line)));
206 }
207 if count > MAX_BULK {
208 return Err(protocol(&format!("array reply of {count} items is implausible")));
209 }
210
211 let mut items = Vec::with_capacity((count as usize).min(1024));
212 let mut cursor = after_line;
213 for _ in 0..count {
214 match decode_at(input, cursor, depth + 1)? {
215 Some((item, next)) => {
216 items.push(item);
217 cursor = next;
218 }
219 None => return Ok(None),
221 }
222 }
223 Ok(Some((Value::Array(items), cursor)))
224 }
225 other => Err(protocol(&format!(
226 "unknown RESP type byte {:?} — is this actually a Redis server?",
227 other as char
228 ))),
229 }
230}
231
232fn read_line(input: &[u8], from: usize) -> Result<Option<(&[u8], usize)>> {
235 let mut index = from;
236 while index + 1 < input.len() {
237 if input[index] == b'\r' && input[index + 1] == b'\n' {
238 return Ok(Some((&input[from..index], index + 2)));
239 }
240 index += 1;
241 }
242 Ok(None)
243}
244
245fn to_text(bytes: &[u8]) -> Result<String> {
246 std::str::from_utf8(bytes)
247 .map(str::to_string)
248 .map_err(|_| protocol("a status or error reply was not valid UTF-8"))
249}
250
251fn parse_int(bytes: &[u8]) -> Result<i64> {
252 std::str::from_utf8(bytes)
253 .ok()
254 .and_then(|text| text.trim().parse().ok())
255 .ok_or_else(|| protocol(&format!("`{}` is not a RESP integer", String::from_utf8_lossy(bytes))))
256}
257
258fn protocol(message: &str) -> Error {
259 Error::Protocol(format!("RESP: {message}"))
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 fn decoded(bytes: &[u8]) -> Value {
267 let (value, consumed) = decode(bytes).unwrap().expect("a complete reply");
268 assert_eq!(consumed, bytes.len(), "the decoder must consume exactly the reply");
269 value
270 }
271
272 #[test]
273 fn decodes_a_simple_string() {
274 assert_eq!(decoded(b"+OK\r\n"), Value::Simple("OK".into()));
275 assert_eq!(decoded(b"+PONG\r\n"), Value::Simple("PONG".into()));
276 assert_eq!(decoded(b"+\r\n"), Value::Simple(String::new()));
277 }
278
279 #[test]
280 fn decodes_an_error_and_keeps_it_out_of_the_result_type_until_asked() {
281 let value = decoded(b"-WRONGTYPE Operation against a key holding the wrong kind of value\r\n");
282 assert_eq!(
283 value,
284 Value::Error("WRONGTYPE Operation against a key holding the wrong kind of value".into())
285 );
286
287 let error = value.into_result().unwrap_err();
288 assert!(error.to_string().contains("WRONGTYPE"));
289 }
290
291 #[test]
292 fn decodes_integers_including_negative_and_zero() {
293 assert_eq!(decoded(b":0\r\n"), Value::Integer(0));
294 assert_eq!(decoded(b":1000\r\n"), Value::Integer(1000));
295 assert_eq!(decoded(b":-42\r\n"), Value::Integer(-42));
296 }
297
298 #[test]
299 fn decodes_a_bulk_string_including_empty_and_binary_payloads() {
300 assert_eq!(decoded(b"$5\r\nhello\r\n"), Value::Bulk(b"hello".to_vec()));
301 assert_eq!(decoded(b"$0\r\n\r\n"), Value::Bulk(Vec::new()));
302
303 assert_eq!(decoded(b"$7\r\na\r\nb\r\nc\r\n"), Value::Bulk(b"a\r\nb\r\nc".to_vec()));
306 }
307
308 #[test]
309 fn decodes_the_null_bulk_string_as_nil() {
310 assert_eq!(decoded(b"$-1\r\n"), Value::Nil);
311 assert!(decoded(b"$-1\r\n").is_nil());
312 assert_eq!(decoded(b"*-1\r\n"), Value::Nil, "a null array is also an absence");
313 }
314
315 #[test]
316 fn decodes_an_array_of_mixed_types() {
317 assert_eq!(
318 decoded(b"*3\r\n:1\r\n$3\r\ntwo\r\n+three\r\n"),
319 Value::Array(vec![
320 Value::Integer(1),
321 Value::Bulk(b"two".to_vec()),
322 Value::Simple("three".into()),
323 ])
324 );
325 assert_eq!(decoded(b"*0\r\n"), Value::Array(Vec::new()));
326 }
327
328 #[test]
329 fn decodes_nested_arrays() {
330 let value = decoded(b"*2\r\n*2\r\n:1\r\n:2\r\n*2\r\n$3\r\nfoo\r\n$-1\r\n");
331 assert_eq!(
332 value,
333 Value::Array(vec![
334 Value::Array(vec![Value::Integer(1), Value::Integer(2)]),
335 Value::Array(vec![Value::Bulk(b"foo".to_vec()), Value::Nil]),
336 ])
337 );
338 }
339
340 #[test]
341 fn a_partial_reply_asks_for_more_bytes_instead_of_failing() {
342 let complete: &[u8] = b"*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n";
343 for cut in 1..complete.len() {
344 assert_eq!(
345 decode(&complete[..cut]).unwrap(),
346 None,
347 "a {cut}-byte prefix must not decode"
348 );
349 }
350 assert!(decode(complete).unwrap().is_some());
351 }
352
353 #[test]
354 fn decoding_stops_at_the_end_of_one_reply_when_two_are_pipelined() {
355 let buffer: &[u8] = b"+OK\r\n:7\r\n";
356 let (first, consumed) = decode(buffer).unwrap().unwrap();
357
358 assert_eq!(first, Value::Simple("OK".into()));
359 assert_eq!(consumed, 5);
360
361 let (second, _) = decode(&buffer[consumed..]).unwrap().unwrap();
362 assert_eq!(second, Value::Integer(7));
363 }
364
365 #[test]
366 fn every_value_survives_an_encode_decode_round_trip() {
367 let values = [
368 Value::Simple("OK".into()),
369 Value::Error("ERR no such key".into()),
370 Value::Integer(-9_000_000_000),
371 Value::Bulk(b"payload with \r\n inside".to_vec()),
372 Value::Bulk(vec![0x00, 0xff, 0x7f]),
373 Value::Nil,
374 Value::Array(vec![]),
375 Value::Array(vec![
376 Value::Integer(1),
377 Value::Nil,
378 Value::Array(vec![Value::Bulk(b"deep".to_vec())]),
379 ]),
380 ];
381
382 for value in values {
383 let bytes = value.encode();
384 let (back, consumed) = decode(&bytes).unwrap().expect("a complete reply");
385 assert_eq!(back, value);
386 assert_eq!(consumed, bytes.len());
387 }
388 }
389
390 #[test]
391 fn a_command_is_encoded_as_an_array_of_bulk_strings() {
392 assert_eq!(
393 encode_command(&[b"SET", b"name", b"ada"]),
394 b"*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$3\r\nada\r\n".to_vec()
395 );
396 assert_eq!(encode_command(&[b"PING"]), b"*1\r\n$4\r\nPING\r\n".to_vec());
397 }
398
399 #[test]
400 fn an_argument_full_of_protocol_syntax_stays_one_argument() {
401 let hostile = b"value\r\nFLUSHALL\r\n";
403 let encoded = encode_command(&[b"SET", b"key", hostile]);
404
405 let (decoded, _) = decode(&encoded).unwrap().unwrap();
406 let Value::Array(parts) = decoded else { panic!("a command is an array") };
407
408 assert_eq!(parts.len(), 3, "the injected newline must not create a fourth argument");
409 assert_eq!(parts[2], Value::Bulk(hostile.to_vec()));
410 }
411
412 #[test]
413 fn a_reply_that_is_not_resp_at_all_is_reported_as_a_protocol_error() {
414 let error = decode(b"HTTP/1.1 200 OK\r\n").unwrap_err();
415 assert!(error.to_string().contains("Redis server"), "got: {error}");
416 }
417
418 #[test]
419 fn an_implausible_length_is_refused_before_anything_is_allocated() {
420 assert!(decode(b"$999999999999\r\n").is_err());
421 assert!(decode(b"*999999999999\r\n").is_err());
422 }
423
424 #[test]
425 fn a_length_header_that_is_not_a_number_is_a_protocol_error() {
426 assert!(decode(b"$abc\r\n").is_err());
427 assert!(decode(b":not-an-integer\r\n").is_err());
428 }
429
430 #[test]
431 fn values_read_as_text_and_numbers_whatever_form_they_arrived_in() {
432 assert_eq!(Value::Bulk(b"42".to_vec()).as_i64(), Some(42));
433 assert_eq!(Value::Integer(42).as_i64(), Some(42));
434 assert_eq!(Value::Simple("42".into()).as_i64(), Some(42));
435 assert_eq!(Value::Nil.as_i64(), None);
436 assert_eq!(Value::Bulk(b"hello".to_vec()).as_str(), Some("hello"));
437 assert_eq!(Value::Bulk(vec![0xff]).as_str(), None, "invalid UTF-8 is not text");
438 }
439}