use crate::{
ClientError, Error, ErrorKind, Result,
resp::{
BIG_NUMBER_TAG, BOOL_TAG, BULK_ERROR_TAG, BULK_STRING_TAG, DOUBLE_TAG, INTEGER_TAG,
NULL_TAG, SIMPLE_ERROR_TAG, SIMPLE_STRING_TAG, VERBATIM_STRING_TAG,
},
};
use memchr::memchr;
use std::ops::Range;
const NO_BULK_LIMIT: usize = usize::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ScalarKind {
SimpleString,
Error,
Integer,
Double,
BulkString,
Boolean,
Null,
}
struct ScalarLayout {
kind: ScalarKind,
value: Range<usize>,
end: usize,
}
#[inline]
fn check_bulk_len(len: i64, max_bulk_length: usize) -> Result<()> {
if len.cast_unsigned() > max_bulk_length as u64 {
return Err(Error::from(ClientError::BulkLengthTooLarge));
}
Ok(())
}
#[inline(always)]
#[expect(
clippy::arithmetic_side_effects,
reason = "the two sums a server-announced length drives are `checked_add`; the \
remaining one steps over a fixed prefix the caller has already \
bounded by that length."
)]
fn bulk_payload(
data: &[u8],
after: usize,
len: i64,
skip: usize,
max_bulk_length: usize,
malformed: ClientError,
) -> Result<(Range<usize>, usize)> {
check_bulk_len(len, max_bulk_length)?;
let len = usize::try_from(len).map_err(|_| Error::from(malformed.clone()))?;
let payload_end = after
.checked_add(len)
.ok_or_else(|| Error::from(malformed.clone()))?;
let end = payload_end
.checked_add(2)
.ok_or_else(|| Error::from(malformed.clone()))?;
if slice(data, payload_end..end)? != b"\r\n" {
return Err(malformed.into());
}
Ok((after + skip..payload_end, end))
}
#[inline]
#[expect(
clippy::arithmetic_side_effects,
reason = "`pos` indexes `data` — the read on the line below proves it — so \
stepping past the tag byte stays inside `usize`. The announced \
length is added under `checked_add` further down."
)]
pub(crate) fn bulk_value_end(data: &[u8], pos: usize, max_bulk_length: usize) -> Option<usize> {
let tag = *data.get(pos)?;
if tag != b'$' && tag != b'=' {
return None;
}
let (len, after) = parse_int_at(data, pos + 1).ok()?;
if len < 0 {
return None;
}
check_bulk_len(len, max_bulk_length).ok()?;
after
.checked_add(usize::try_from(len).ok()?)?
.checked_add(2)
}
#[inline(always)]
fn slice(data: &[u8], range: Range<usize>) -> Result<&[u8]> {
data.get(range).ok_or_else(|| Error::from(ErrorKind::EOF))
}
#[inline]
#[expect(
clippy::arithmetic_side_effects,
reason = "`i` is a `memchr` hit inside `rem`, itself a suffix of `data`, so \
both `i + 1` and `from + i` are offsets into a slice — bounded by \
`isize::MAX`."
)]
fn find_crlf(data: &[u8], from: usize) -> Result<usize> {
let rem = data.get(from..).ok_or_else(|| ErrorKind::EOF)?;
let i = memchr(b'\r', rem).ok_or_else(|| ErrorKind::EOF)?;
if rem.get(i + 1) != Some(&b'\n') {
return Err(Error::from(ErrorKind::EOF));
}
Ok(from + i)
}
#[inline(always)]
fn crlf_at<const FRAME: bool>(data: &[u8], from: usize) -> Result<usize> {
if !FRAME {
return find_crlf(data, from);
}
let cr = data.len().checked_sub(2).ok_or_else(|| ErrorKind::EOF)?;
if cr < from {
return Err(Error::from(ErrorKind::EOF));
}
debug_assert_eq!(
Some(cr),
find_crlf(data, from).ok(),
"a frame's own bytes must end at its scalar's terminator"
);
Ok(cr)
}
#[inline]
#[expect(
clippy::arithmetic_side_effects,
reason = "`i` only advances over bytes `digits.get(i)` returned, so it stays \
an offset into a slice and `from + i + 2` cannot leave `usize`. \
`digit - b'0'` is inside the `b'0'..=b'9'` arm. The accumulation \
itself — the one operation here that a hostile length drives — is \
already `checked_mul` / `checked_sub`."
)]
pub(crate) fn parse_int_at(data: &[u8], from: usize) -> Result<(i64, usize)> {
let digits = data.get(from..).ok_or_else(|| ErrorKind::EOF)?;
let mut i = 0;
let negative = if let Some(&b'-') = digits.first() {
i += 1;
true
} else {
false
};
let mut n = 0i64;
while let Some(&digit) = digits.get(i) {
match digit {
b'0'..=b'9' => {
n = n
.checked_mul(10)
.and_then(|n| n.checked_sub(i64::from(digit - b'0')))
.ok_or_else(|| Error::from(ClientError::CannotParseInteger))?;
i += 1;
}
b'\r' => match digits.get(i + 1) {
Some(&b'\n') => {
let value = if negative {
n
} else {
n.checked_neg()
.ok_or_else(|| Error::from(ClientError::CannotParseInteger))?
};
return Ok((value, from + i + 2));
}
Some(_) => return Err(Error::from(ClientError::CannotParseInteger)),
None => return Err(Error::from(ErrorKind::EOF)),
},
_ => return Err(Error::from(ClientError::CannotParseInteger)),
}
}
Err(Error::from(ErrorKind::EOF))
}
#[inline(always)]
#[expect(
clippy::arithmetic_side_effects,
reason = "every sum here steps over a fixed number of bytes from an offset \
already known to index `data`: `at` is read on the first line, and \
`cr` is where a terminator was found. The three length-prefixed tags \
add their announced length inside `bulk_payload` instead."
)]
fn scalar_layout<const FRAME: bool>(
data: &[u8],
at: usize,
max_bulk_length: usize,
) -> Result<ScalarLayout> {
let tag = *data.get(at).ok_or_else(|| ErrorKind::EOF)?;
let start = at + 1;
match tag {
SIMPLE_STRING_TAG => {
let cr = crlf_at::<FRAME>(data, start)?;
Ok(ScalarLayout {
kind: ScalarKind::SimpleString,
value: start..cr,
end: cr + 2,
})
}
SIMPLE_ERROR_TAG => {
let cr = crlf_at::<FRAME>(data, start)?;
Ok(ScalarLayout {
kind: ScalarKind::Error,
value: start..cr,
end: cr + 2,
})
}
INTEGER_TAG => {
let cr = crlf_at::<FRAME>(data, start)?;
Ok(ScalarLayout {
kind: ScalarKind::Integer,
value: start..cr,
end: cr + 2,
})
}
DOUBLE_TAG => {
let cr = crlf_at::<FRAME>(data, start)?;
Ok(ScalarLayout {
kind: ScalarKind::Double,
value: start..cr,
end: cr + 2,
})
}
BIG_NUMBER_TAG => {
let cr = crlf_at::<FRAME>(data, start)?;
Ok(ScalarLayout {
kind: ScalarKind::BulkString,
value: start..cr,
end: cr + 2,
})
}
NULL_TAG => {
let cr = crlf_at::<FRAME>(data, start)?;
Ok(ScalarLayout {
kind: ScalarKind::Null,
value: start..start,
end: cr + 2,
})
}
BOOL_TAG => {
match slice(data, start..start + 3)? {
[b't' | b'f', b'\r', b'\n'] => {}
_ => return Err(Error::from(ClientError::CannotParseBoolean)),
}
Ok(ScalarLayout {
kind: ScalarKind::Boolean,
value: start..start + 1,
end: start + 3,
})
}
BULK_STRING_TAG => {
let (len, after) = parse_int_at(data, start)?;
if len == -1 {
return Ok(ScalarLayout {
kind: ScalarKind::Null,
value: after..after,
end: after,
});
}
if len < 0 {
return Err(Error::from(ClientError::CannotParseBulkString));
}
let (value, end) = bulk_payload(
data,
after,
len,
0,
max_bulk_length,
ClientError::CannotParseBulkString,
)?;
Ok(ScalarLayout {
kind: ScalarKind::BulkString,
value,
end,
})
}
VERBATIM_STRING_TAG => {
let (len, after) = parse_int_at(data, start)?;
if len == -1 {
return Ok(ScalarLayout {
kind: ScalarKind::Null,
value: after..after,
end: after,
});
}
if len < 4 {
return Err(Error::from(ClientError::VerbatimStringTooShort));
}
let (value, end) = bulk_payload(
data,
after,
len,
4,
max_bulk_length,
ClientError::CannotParseVerbatimString,
)?;
Ok(ScalarLayout {
kind: ScalarKind::BulkString,
value,
end,
})
}
BULK_ERROR_TAG => {
let (len, after) = parse_int_at(data, start)?;
if len < 0 {
return Err(Error::from(ClientError::CannotParseBulkError));
}
let (value, end) = bulk_payload(
data,
after,
len,
0,
max_bulk_length,
ClientError::CannotParseBulkError,
)?;
Ok(ScalarLayout {
kind: ScalarKind::Error,
value,
end,
})
}
_ => Err(Error::from(ClientError::UnknownRespTag(tag as char))),
}
}
#[inline(always)]
pub(crate) fn scalar_end(data: &[u8], at: usize, max_bulk_length: usize) -> Result<usize> {
Ok(scalar_layout::<false>(data, at, max_bulk_length)?.end)
}
#[inline(always)]
pub(crate) fn scalar_value(data: &[u8], at: usize) -> Result<(ScalarKind, Range<usize>)> {
let layout = scalar_layout::<false>(data, at, NO_BULK_LIMIT)?;
Ok((layout.kind, layout.value))
}
#[inline(always)]
pub(crate) fn scalar_span(data: &[u8], at: usize) -> Result<Range<usize>> {
Ok(at..scalar_layout::<false>(data, at, NO_BULK_LIMIT)?.end)
}
#[inline(always)]
pub(crate) fn frame_scalar_value(data: &[u8]) -> Result<(ScalarKind, Range<usize>)> {
let layout = scalar_layout::<true>(data, 0, NO_BULK_LIMIT)?;
debug_assert_eq!(
layout.end,
data.len(),
"a frame's own bytes must hold nothing but its scalar"
);
Ok((layout.kind, layout.value))
}