use crate::error::ProtocolError;
use crate::reply::Reply;
use crate::request::{find_crlf, parse_int};
pub fn parse_reply(buf: &[u8]) -> Result<Option<(Reply, usize)>, ProtocolError> {
parse_with(buf, &mut DoubleText(None))
}
pub fn parse_reply_keeping_double_text(buf: &[u8]) -> Result<Option<TextedReply>, ProtocolError> {
let mut texts = Vec::new();
let parsed = parse_with(buf, &mut DoubleText(Some(&mut texts)))?;
Ok(parsed.map(|(reply, used)| (reply, used, texts)))
}
pub type TextedReply = (Reply, usize, Vec<Vec<u8>>);
struct DoubleText<'a>(Option<&'a mut Vec<Vec<u8>>>);
fn parse_with(
buf: &[u8],
dt: &mut DoubleText<'_>,
) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(&tag) = buf.first() else {
return Ok(None);
};
match tag {
b'+' => Ok(reply_line(buf).map(|(b, used)| (Reply::Simple(b.to_vec()), used))),
b'-' => Ok(reply_line(buf).map(|(b, used)| (Reply::Error(b.to_vec()), used))),
b':' => match reply_line(buf) {
None => Ok(None),
Some((b, used)) => {
let n = parse_int(b).ok_or(ProtocolError::Malformed("bad integer reply"))?;
Ok(Some((Reply::Int(n), used)))
}
},
b'$' => parse_bulk_reply(buf),
b'*' => parse_array_reply(buf, false, dt),
b'%' => parse_map_reply(buf, dt),
b'~' => parse_set_reply(buf, dt),
b',' => parse_double_reply(buf, dt),
b'#' => parse_boolean_reply(buf),
b'=' => parse_verbatim_reply(buf),
b'(' => match reply_line(buf) {
None => Ok(None),
Some((b, used)) => Ok(Some((Reply::BigNumber(b.to_vec()), used))),
},
b'_' => parse_null_reply(buf),
b'>' => parse_array_reply(buf, true, dt),
b'!' => parse_blob_error_reply(buf),
b'|' => parse_attributed_reply(buf, dt),
_ => Err(ProtocolError::Malformed("unknown reply type")),
}
}
fn reply_line(buf: &[u8]) -> Option<(&[u8], usize)> {
find_crlf(buf, 1).map(|eol| (&buf[1..eol], eol + 2))
}
fn parse_bulk_reply(buf: &[u8]) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(hdr_end) = find_crlf(buf, 1) else {
return Ok(None);
};
let len = parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad bulk length"))?;
if len < 0 {
return Ok(Some((Reply::Nil, hdr_end + 2)));
}
let data_start = hdr_end + 2;
let data_end = data_start + len as usize;
if buf.len() < data_end + 2 {
return Ok(None);
}
payload_end(buf, data_end, "bulk payload not followed by CRLF")?;
Ok(Some((Reply::Bulk(buf[data_start..data_end].to_vec()), data_end + 2)))
}
fn parse_array_reply(
buf: &[u8],
push: bool,
dt: &mut DoubleText<'_>,
) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(hdr_end) = find_crlf(buf, 1) else {
return Ok(None);
};
let count = parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad array length"))?;
if count < 0 {
if push {
return Err(ProtocolError::Malformed("push frame cannot be null"));
}
return Ok(Some((Reply::Nil, hdr_end + 2)));
}
let mut pos = hdr_end + 2;
let cap = (count as usize).min(buf.len().saturating_sub(pos));
let mut items = Vec::with_capacity(cap);
for _ in 0..count {
match parse_with(&buf[pos..], dt)? {
None => return Ok(None),
Some((r, used)) => {
items.push(r);
pos += used;
}
}
}
let reply = if push { Reply::Push(items) } else { Reply::Array(items) };
Ok(Some((reply, pos)))
}
fn parse_map_reply(
buf: &[u8],
dt: &mut DoubleText<'_>,
) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(hdr_end) = find_crlf(buf, 1) else {
return Ok(None);
};
let count = parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad map length"))?;
if count < 0 {
return Err(ProtocolError::Malformed("map length cannot be negative"));
}
let mut pos = hdr_end + 2;
let cap = (count as usize).min(buf.len().saturating_sub(pos) / 2);
let mut pairs: Vec<(Reply, Reply)> = Vec::with_capacity(cap);
for _ in 0..count {
let Some((k, used_k)) = parse_with(&buf[pos..], dt)? else {
return Ok(None);
};
pos += used_k;
let Some((v, used_v)) = parse_with(&buf[pos..], dt)? else {
return Ok(None);
};
pos += used_v;
pairs.push((k, v));
}
Ok(Some((Reply::Map(pairs), pos)))
}
fn parse_set_reply(
buf: &[u8],
dt: &mut DoubleText<'_>,
) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(hdr_end) = find_crlf(buf, 1) else {
return Ok(None);
};
let count = parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad set length"))?;
if count < 0 {
return Err(ProtocolError::Malformed("set length cannot be negative"));
}
let mut pos = hdr_end + 2;
let cap = (count as usize).min(buf.len().saturating_sub(pos));
let mut items = Vec::with_capacity(cap);
for _ in 0..count {
match parse_with(&buf[pos..], dt)? {
None => return Ok(None),
Some((r, used)) => {
items.push(r);
pos += used;
}
}
}
Ok(Some((Reply::Set(items), pos)))
}
fn parse_double_reply(
buf: &[u8],
dt: &mut DoubleText<'_>,
) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some((bytes, used)) = reply_line(buf) else {
return Ok(None);
};
let s = std::str::from_utf8(bytes).map_err(|_| ProtocolError::Malformed("bad double utf8"))?;
let v: f64 = s.parse().map_err(|_| ProtocolError::Malformed("bad double"))?;
if let Some(texts) = dt.0.as_mut() {
texts.push(bytes.to_vec());
}
Ok(Some((Reply::Double(v), used)))
}
fn parse_boolean_reply(buf: &[u8]) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some((bytes, used)) = reply_line(buf) else {
return Ok(None);
};
let v = match bytes {
b"t" => true,
b"f" => false,
_ => return Err(ProtocolError::Malformed("bad boolean payload")),
};
Ok(Some((Reply::Boolean(v), used)))
}
fn parse_verbatim_reply(buf: &[u8]) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(hdr_end) = find_crlf(buf, 1) else {
return Ok(None);
};
let len = parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad verbatim length"))?;
if len < 4 {
return Err(ProtocolError::Malformed("verbatim length < 4 (fmt + ':')"));
}
let data_start = hdr_end + 2;
let data_end = data_start + len as usize;
if buf.len() < data_end + 2 {
return Ok(None);
}
payload_end(buf, data_end, "verbatim payload not followed by CRLF")?;
let body = &buf[data_start..data_end];
if body[3] != b':' {
return Err(ProtocolError::Malformed("verbatim missing fmt:data separator"));
}
let mut fmt = [0u8; 3];
fmt.copy_from_slice(&body[..3]);
let data = body[4..].to_vec();
Ok(Some((Reply::Verbatim { fmt, data }, data_end + 2)))
}
fn parse_null_reply(buf: &[u8]) -> Result<Option<(Reply, usize)>, ProtocolError> {
if buf.len() < 3 {
return Ok(None);
}
if &buf[..3] != b"_\r\n" {
return Err(ProtocolError::Malformed("bad null payload"));
}
Ok(Some((Reply::Null, 3)))
}
fn parse_blob_error_reply(buf: &[u8]) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(hdr_end) = find_crlf(buf, 1) else {
return Ok(None);
};
let len =
parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad blob error length"))?;
if len < 0 {
return Err(ProtocolError::Malformed("blob error length cannot be negative"));
}
let data_start = hdr_end + 2;
let data_end = data_start + len as usize;
if buf.len() < data_end + 2 {
return Ok(None);
}
payload_end(buf, data_end, "blob error payload not followed by CRLF")?;
Ok(Some((Reply::BlobError(buf[data_start..data_end].to_vec()), data_end + 2)))
}
fn payload_end(buf: &[u8], data_end: usize, what: &'static str) -> Result<(), ProtocolError> {
if &buf[data_end..data_end + 2] == b"\r\n" {
Ok(())
} else {
Err(ProtocolError::Malformed(what))
}
}
fn parse_attributed_reply(
buf: &[u8],
dt: &mut DoubleText<'_>,
) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some((_attrs, used_attrs)) = parse_map_reply(buf, dt)? else {
return Ok(None);
};
match parse_with(&buf[used_attrs..], dt)? {
None => Ok(None),
Some((r, used)) => Ok(Some((r, used_attrs + used))),
}
}
#[cfg(test)]
#[path = "reply_parse_tests.rs"]
mod tests;