use crate::wire::{WireError, find_crlf, parse_decimal, push_u64};
pub const SNAPSHOT_CHUNK_MAX: usize = 64 * 1024;
pub const SNAPSHOT_LINE_MAX: usize = 256;
#[derive(Debug, PartialEq, Eq)]
pub enum SnapshotMarker {
Begin,
End(u64),
}
pub fn encode_snapshot_begin() -> Vec<u8> {
b"+SNAPSHOT\r\n".to_vec()
}
pub fn encode_snapshot_chunk(bytes: &[u8]) -> Vec<u8> {
debug_assert!(
bytes.len() <= SNAPSHOT_CHUNK_MAX,
"snapshot chunk {} > cap {}",
bytes.len(),
SNAPSHOT_CHUNK_MAX,
);
let mut out = Vec::with_capacity(16 + bytes.len());
out.push(b'$');
push_u64(&mut out, bytes.len() as u64);
out.extend_from_slice(b"\r\n");
out.extend_from_slice(bytes);
out.extend_from_slice(b"\r\n");
out
}
pub fn encode_snapshot_end(ack_offset: u64) -> Vec<u8> {
let mut out = Vec::with_capacity(32);
out.extend_from_slice(b"+SNAPSHOT_END ");
push_u64(&mut out, ack_offset);
out.extend_from_slice(b"\r\n");
out
}
pub fn decode_snapshot_marker(buf: &[u8]) -> Result<Option<(SnapshotMarker, usize)>, WireError> {
if buf.is_empty() {
return Err(WireError::Truncated);
}
if buf[0] != b'+' {
return Ok(None);
}
let Some(eol) = find_crlf(buf, 1) else {
return if buf.len() > SNAPSHOT_LINE_MAX {
Err(WireError::BadEnvelope)
} else {
Err(WireError::Truncated)
};
};
if eol > SNAPSHOT_LINE_MAX {
return Err(WireError::BadEnvelope);
}
let line = &buf[1..eol];
if line == b"SNAPSHOT" {
return Ok(Some((SnapshotMarker::Begin, eol + 2)));
}
if let Some(rest) = line.strip_prefix(b"SNAPSHOT_END ") {
let offset = parse_decimal(rest).ok_or(WireError::BadEnvelope)?;
return Ok(Some((SnapshotMarker::End(offset), eol + 2)));
}
Err(WireError::BadEnvelope)
}
pub fn decode_snapshot_chunk(buf: &[u8]) -> Result<(&[u8], usize), WireError> {
if buf.is_empty() {
return Err(WireError::Truncated);
}
if buf[0] != b'$' {
return Err(WireError::BadEnvelope);
}
let len_eol = find_crlf(buf, 1).ok_or(WireError::Truncated)?;
let len = parse_decimal(&buf[1..len_eol]).ok_or(WireError::BadEnvelope)?;
let len = usize::try_from(len).map_err(|_| WireError::BadEnvelope)?;
if len > SNAPSHOT_CHUNK_MAX {
return Err(WireError::BadEnvelope);
}
let data_start = len_eol + 2;
let data_end = data_start + len;
if buf.len() < data_end + 2 {
return Err(WireError::Truncated);
}
if &buf[data_end..data_end + 2] != b"\r\n" {
return Err(WireError::BadEnvelope);
}
Ok((&buf[data_start..data_end], data_end + 2))
}