use crate::wal::{WalEntry, WalError};
pub const FRAME_LEN_SIZE: usize = 4;
#[must_use]
pub fn wrap(entry_bytes: &[u8]) -> Vec<u8> {
let len = u32::try_from(entry_bytes.len()).unwrap_or(u32::MAX);
let mut frame = Vec::with_capacity(FRAME_LEN_SIZE + entry_bytes.len());
frame.extend_from_slice(&len.to_le_bytes());
frame.extend_from_slice(entry_bytes);
frame
}
#[must_use]
pub fn frame_entry(entry: &WalEntry) -> Vec<u8> {
wrap(&entry.serialise())
}
pub fn decode_entries(bytes: &[u8]) -> Result<Vec<WalEntry>, WalError> {
let mut cursor = FrameCursor::new(bytes);
let mut entries = Vec::new();
while !cursor.is_finished() {
let entry_bytes = cursor.read_frame()?;
entries.push(WalEntry::deserialise(entry_bytes)?);
}
Ok(entries)
}
struct FrameCursor<'a> {
bytes: &'a [u8],
offset: usize,
}
impl<'a> FrameCursor<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self { bytes, offset: 0 }
}
const fn is_finished(&self) -> bool {
self.offset == self.bytes.len()
}
fn read_frame(&mut self) -> Result<&'a [u8], WalError> {
let len = usize::try_from(self.read_u32()?).map_err(|_| WalError::LengthOverflow)?;
self.read_bytes(len)
}
fn read_u32(&mut self) -> Result<u32, WalError> {
let bytes = self.read_bytes(FRAME_LEN_SIZE)?;
let array: [u8; FRAME_LEN_SIZE] = bytes.try_into().map_err(|_| WalError::Truncated)?;
Ok(u32::from_le_bytes(array))
}
fn read_bytes(&mut self, len: usize) -> Result<&'a [u8], WalError> {
let end = self
.offset
.checked_add(len)
.ok_or(WalError::LengthOverflow)?;
let slice = self
.bytes
.get(self.offset..end)
.ok_or(WalError::Truncated)?;
self.offset = end;
Ok(slice)
}
}
#[cfg(test)]
mod tests {
use super::{decode_entries, frame_entry};
use crate::wal::WalEntry;
#[test]
fn frame_entry_matches_native_frame_layout() {
let entry = WalEntry::put(b"key".to_vec(), b"value".to_vec());
let entry_bytes = entry.serialise();
let framed = frame_entry(&entry);
let mut expected = (entry_bytes.len() as u32).to_le_bytes().to_vec();
expected.extend_from_slice(&entry_bytes);
assert_eq!(framed, expected);
assert_eq!(&framed[..4], &(entry_bytes.len() as u32).to_le_bytes());
}
#[test]
fn decode_entries_round_trips_a_single_put() -> Result<(), crate::wal::WalError> {
let entry = WalEntry::put(b"alpha".to_vec(), b"beta".to_vec());
let framed = frame_entry(&entry);
let decoded = decode_entries(&framed)?;
assert_eq!(decoded, vec![entry]);
Ok(())
}
#[test]
fn decode_entries_round_trips_mixed_sequence() -> Result<(), crate::wal::WalError> {
let entries = vec![
WalEntry::put(b"a".to_vec(), b"1".to_vec()),
WalEntry::delete(b"b".to_vec()),
WalEntry::put(b"c".to_vec(), b"3".to_vec()),
];
let mut framed = Vec::new();
for entry in &entries {
framed.extend_from_slice(&frame_entry(entry));
}
let decoded = decode_entries(&framed)?;
assert_eq!(decoded, entries);
Ok(())
}
#[test]
fn decode_entries_rejects_corrupted_checksum() {
let entry = WalEntry::put(b"k".to_vec(), b"v".to_vec());
let mut framed = frame_entry(&entry);
if let Some(byte) = framed.last_mut() {
*byte ^= 0xff;
}
assert!(matches!(
decode_entries(&framed),
Err(crate::wal::WalError::ChecksumMismatch { .. })
));
}
#[test]
fn decode_entries_rejects_truncated_frame() {
let entry = WalEntry::put(b"k".to_vec(), b"v".to_vec());
let framed = frame_entry(&entry);
let truncated = &framed[..framed.len() - 1];
assert!(matches!(
decode_entries(truncated),
Err(crate::wal::WalError::Truncated)
));
}
}