use crate::replica::DecodedFrame;
use kevy_resp::{Argv, ArgvView, ProtocolError, parse_command_into};
pub use crate::wire_snapshot::{
SNAPSHOT_CHUNK_MAX, SNAPSHOT_LINE_MAX, SnapshotMarker, decode_replconf_ack,
decode_snapshot_chunk, decode_snapshot_marker, encode_ping, encode_replconf_ack,
encode_snapshot_begin, encode_snapshot_chunk, encode_snapshot_end,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum WireError {
Truncated,
BadEnvelope,
BadOffset,
NegativeOffset(i64),
BadPayload(ProtocolError),
}
impl std::fmt::Display for WireError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Truncated => write!(f, "wire frame truncated"),
Self::BadEnvelope => write!(f, "wire envelope not *2"),
Self::BadOffset => write!(f, "wire offset element not RESP integer"),
Self::NegativeOffset(n) => write!(f, "wire offset is negative: {n}"),
Self::BadPayload(e) => write!(f, "wire inner payload malformed: {e}"),
}
}
}
impl std::error::Error for WireError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::BadPayload(e) => Some(e),
_ => None,
}
}
}
pub fn encode_frame<A: ArgvView + ?Sized>(offset: u64, argv: &A) -> Vec<u8> {
debug_assert!(
i64::try_from(offset).is_ok(),
"replication offset {offset} exceeds i64::MAX — wire envelope cannot encode",
);
let est = 32 + argv_byte_estimate_view(argv);
let mut out = Vec::with_capacity(est);
out.extend_from_slice(b"*2\r\n");
out.push(b':');
push_u64(&mut out, offset);
out.extend_from_slice(b"\r\n");
let n = argv.len();
out.push(b'*');
push_u64(&mut out, n as u64);
out.extend_from_slice(b"\r\n");
for i in 0..n {
let arg = &argv[i];
out.push(b'$');
push_u64(&mut out, arg.len() as u64);
out.extend_from_slice(b"\r\n");
out.extend_from_slice(arg);
out.extend_from_slice(b"\r\n");
}
out
}
pub fn decode_frame(buf: &[u8]) -> Result<(DecodedFrame, usize), WireError> {
let after_env = parse_envelope_header(buf)?;
let (offset, after_offset) = parse_offset_line(buf, after_env)?;
let inner = &buf[after_offset..];
let mut argv = Argv::default();
let consumed_inner = match parse_command_into(inner, &mut argv) {
Ok(Some(n)) => n,
Ok(None) => return Err(WireError::Truncated),
Err(e) => return Err(WireError::BadPayload(e)),
};
Ok((DecodedFrame { offset, argv }, after_offset + consumed_inner))
}
fn parse_envelope_header(buf: &[u8]) -> Result<usize, WireError> {
if buf.len() < 4 {
return Err(WireError::Truncated);
}
if buf[0] != b'*' {
return Err(WireError::BadEnvelope);
}
let eol = find_crlf(buf, 1).ok_or(WireError::Truncated)?;
let count = parse_decimal(&buf[1..eol]).ok_or(WireError::BadEnvelope)?;
if count != 2 {
return Err(WireError::BadEnvelope);
}
Ok(eol + 2)
}
fn parse_offset_line(buf: &[u8], start: usize) -> Result<(u64, usize), WireError> {
if start >= buf.len() {
return Err(WireError::Truncated);
}
if buf[start] != b':' {
return Err(WireError::BadOffset);
}
let eol = find_crlf(buf, start + 1).ok_or(WireError::Truncated)?;
let raw = &buf[start + 1..eol];
let signed = parse_signed_decimal(raw).ok_or(WireError::BadOffset)?;
if signed < 0 {
return Err(WireError::NegativeOffset(signed));
}
Ok((signed as u64, eol + 2))
}
pub(crate) fn find_crlf(buf: &[u8], from: usize) -> Option<usize> {
let mut i = from;
while i + 1 < buf.len() {
if buf[i] == b'\r' && buf[i + 1] == b'\n' {
return Some(i);
}
i += 1;
}
None
}
pub(crate) fn parse_decimal(bytes: &[u8]) -> Option<u64> {
if bytes.is_empty() {
return None;
}
let mut n: u64 = 0;
for &b in bytes {
if !b.is_ascii_digit() {
return None;
}
n = n.checked_mul(10)?.checked_add(u64::from(b - b'0'))?;
}
Some(n)
}
fn parse_signed_decimal(bytes: &[u8]) -> Option<i64> {
if bytes.is_empty() {
return None;
}
let (neg, digits) = match bytes[0] {
b'-' => (true, &bytes[1..]),
b'+' => (false, &bytes[1..]),
_ => (false, bytes),
};
let n = parse_decimal(digits)?;
if neg {
if n > (i64::MAX as u64) + 1 {
return None;
}
if n == (i64::MAX as u64) + 1 {
return Some(i64::MIN);
}
Some(-(n as i64))
} else {
if n > i64::MAX as u64 {
return None;
}
Some(n as i64)
}
}
pub(crate) fn push_u64(out: &mut Vec<u8>, n: u64) {
if n == 0 {
out.push(b'0');
return;
}
let mut tmp = [0u8; 20]; let mut i = tmp.len();
let mut v = n;
while v != 0 {
i -= 1;
tmp[i] = b'0' + (v % 10) as u8;
v /= 10;
}
out.extend_from_slice(&tmp[i..]);
}
fn argv_byte_estimate_view<A: ArgvView + ?Sized>(argv: &A) -> usize {
let mut bytes = 0usize;
for i in 0..argv.len() {
bytes += argv[i].len();
}
bytes + argv.len() * 10
}
#[cfg(test)]
#[path = "wire_tests.rs"]
mod tests;