use tessarikv_constants::MAX_LOG_PAYLOAD_BYTES;
use super::{FRAME_HEADER_BYTES, checksum};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EndKind {
Clean,
Torn,
Foreign,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LogEnd {
pub offset: u64,
pub kind: EndKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogRead<'a> {
Frame(&'a [u8]),
End(LogEnd),
}
#[derive(Debug, Clone)]
pub struct FrameReader<'a> {
bytes: &'a [u8],
log_number: u64,
offset: usize,
}
impl<'a> FrameReader<'a> {
pub fn new(bytes: &'a [u8], log_number: u64) -> Self {
Self {
bytes,
log_number,
offset: 0,
}
}
pub fn next_frame(&mut self) -> LogRead<'a> {
match self.frame_at(self.offset) {
Ok((payload, next)) => {
self.offset = next;
LogRead::Frame(payload)
}
Err(kind) => LogRead::End(LogEnd {
offset: u64::try_from(self.offset).unwrap_or(u64::MAX),
kind,
}),
}
}
fn frame_at(&self, start: usize) -> Result<(&'a [u8], usize), EndKind> {
let rest = self.bytes.get(start..).unwrap_or_default();
let Some(header) = rest.get(..FRAME_HEADER_BYTES) else {
return Err(if rest.iter().all(|byte| *byte == 0) {
EndKind::Clean
} else {
EndKind::Torn
});
};
if header.iter().all(|byte| *byte == 0) {
return Err(EndKind::Clean);
}
let stored = u32::from_le_bytes(field(header, 0)?);
let payload_len =
usize::try_from(u32::from_le_bytes(field(header, 4)?)).map_err(|_| EndKind::Torn)?;
let log_number = u64::from_le_bytes(field(header, 8)?);
if payload_len > MAX_LOG_PAYLOAD_BYTES {
return Err(EndKind::Torn);
}
let payload = rest
.get(FRAME_HEADER_BYTES..)
.and_then(|body| body.get(..payload_len))
.ok_or(EndKind::Torn)?;
let fields = header.get(4..).ok_or(EndKind::Torn)?;
if checksum(fields, payload) != stored {
return Err(EndKind::Torn);
}
if log_number != self.log_number {
return Err(EndKind::Foreign);
}
let next = start
.checked_add(FRAME_HEADER_BYTES)
.and_then(|end| end.checked_add(payload_len))
.ok_or(EndKind::Torn)?;
Ok((payload, next))
}
}
fn field<const N: usize>(header: &[u8], at: usize) -> Result<[u8; N], EndKind> {
header
.get(at..)
.and_then(|rest| rest.get(..N))
.and_then(|bytes| bytes.try_into().ok())
.ok_or(EndKind::Torn)
}