Skip to content

Commit ecaa42d

Browse files
authored
fix decode array (#8)
1 parent cce08bb commit ecaa42d

File tree

2 files changed

+90
-4
lines changed

2 files changed

+90
-4
lines changed

src/codec.rs

+89-3
Original file line numberDiff line numberDiff line change
@@ -603,15 +603,64 @@ fn decode_bytes(buf: &mut BytesMut, idx: usize) -> DecodeResult {
603603
}
604604
}
605605

606+
fn is_array_ready_to_decode(
607+
buf: &mut BytesMut,
608+
idx: usize,
609+
array_size: usize,
610+
) -> Result<(bool, usize), Error> {
611+
let mut items: usize = 0;
612+
let mut pos = idx;
613+
614+
// counting beginning of array items in buffer by `\r\n<type>`
615+
loop {
616+
let Some(new_pos) = buf[pos..].windows(2).position(|w| w.starts_with(b"\r\n")) else {
617+
break;
618+
};
619+
620+
if pos + new_pos + 2 >= buf.len() {
621+
break;
622+
}
623+
pos += new_pos + 2;
624+
625+
items += match &buf[pos] {
626+
// check nested array and calc it as item
627+
b'*' => match decode_length(buf, pos) {
628+
Ok(Some((_, -1))) => 1,
629+
Ok(Some((p, size))) if size >= 0 => {
630+
let (ready, end_of_scan) = is_array_ready_to_decode(buf, p, size as usize)?;
631+
// nested array isn't ready
632+
if !ready {
633+
return Ok((false, end_of_scan));
634+
}
635+
pos = end_of_scan;
636+
1
637+
}
638+
Ok(Some((_, size))) => {
639+
return Err(Error::Parse(format!("Invalid array size: {}", size)))
640+
}
641+
_ => 0,
642+
},
643+
// array item found
644+
b'$' | b':' | b'+' | b'-' => 1,
645+
_ => 0,
646+
};
647+
648+
if array_size <= items {
649+
return Ok((true, pos));
650+
}
651+
}
652+
653+
Ok((array_size <= items, pos))
654+
}
655+
606656
fn decode_array(buf: &mut BytesMut, idx: usize) -> DecodeResult {
607657
match decode_length(buf, idx)? {
608658
Some((pos, -1)) => Ok(Some((pos, Response::Nil))),
609659
Some((pos, size)) if size >= 0 => {
610660
let size = size as usize;
611661

612-
// ensure all array items present in buffer
613-
let items = buf[idx..].windows(2).filter(|w| w == b"\r\n").count() / 2;
614-
if items < size {
662+
let (is_ready, _) = is_array_ready_to_decode(buf, idx, size)?;
663+
if !is_ready {
615664
return Ok(None);
616665
}
617666

@@ -767,6 +816,43 @@ mod tests {
767816

768817
let deserialized = codec.decode(&mut bytes).unwrap().unwrap();
769818
assert_eq!(deserialized, resp);
819+
820+
// $ echo -e "mget key1 key2\r\nquit" | curl -s telnet://localhost:6379 | python -c "import sys; print(repr(sys.stdin.read()))"
821+
// '*2\r\n$-1\r\n$-1\r\n+OK\r\n'
822+
let mut bytes = BytesMut::copy_from_slice(b"*2\r\n$-1\r\n$-1\r\n");
823+
let result = codec.decode(&mut bytes).unwrap();
824+
825+
assert_eq!(
826+
result,
827+
Some(Response::Array(vec![Response::Nil, Response::Nil]))
828+
);
829+
830+
// uncomplete nested array data
831+
// [[['a']]]
832+
// $ echo -e 'eval "return {{{\'a\'}}}" 0\r\nquit' | curl -s telnet://localhost:6379 | python -c "import sys; print(repr(sys.stdin.read()))"
833+
// '*1\r\n*1\r\n*1\r\n$1\r\na\r\n+OK\r\n'
834+
let mut bytes = BytesMut::copy_from_slice(b"*1\r\n*1\r\n*1\r\n");
835+
let result = codec.decode(&mut bytes).unwrap();
836+
assert_eq!(result, None);
837+
838+
// receiving remain parts
839+
bytes.extend_from_slice(b"$1\r\na");
840+
let result = codec.decode(&mut bytes).unwrap();
841+
assert_eq!(result, None);
842+
843+
bytes.extend_from_slice(b"\r");
844+
let result = codec.decode(&mut bytes).unwrap();
845+
assert_eq!(result, None);
846+
847+
bytes.extend_from_slice(b"\n");
848+
let result = codec.decode(&mut bytes).unwrap();
849+
850+
assert_eq!(
851+
result,
852+
Some(Response::Array(vec![Response::Array(vec![
853+
Response::Array(vec![Response::Bytes(Bytes::from_static(b"a"))])
854+
])]))
855+
);
770856
}
771857

772858
#[test]

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
//! let key = gen_random_key();
1818
//!
1919
//! // create list with one value
20-
//! redis.exec(cmd::LPush(&key, "value"));
20+
//! redis.exec(cmd::LPush(&key, "value")).await?;
2121
//!
2222
//! // get value by index
2323
//! let value = redis.exec(cmd::LIndex(&key, 0)).await?;

0 commit comments

Comments
 (0)