use crate::api::errors::{Error, Result};
const MAGIC: &[u8; 8] = b"holtckp1";
const HEADER_LEN: usize = 8 + 8 + 4;
#[derive(Debug, Clone)]
pub struct CheckpointImage {
bytes: Vec<u8>,
}
impl CheckpointImage {
#[must_use]
pub fn from_bytes(bytes: Vec<u8>) -> Self {
Self { bytes }
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
pub fn applied_index(&self) -> Result<u64> {
Ok(parse_header(&self.bytes)?.0)
}
pub fn validate(&self) -> Result<u64> {
Ok(decode(&self.bytes)?.applied_index)
}
pub(crate) fn from_raw(bytes: Vec<u8>) -> Self {
Self { bytes }
}
}
pub(crate) fn begin(applied_index: u64, family_count: u32) -> Vec<u8> {
let mut buf = Vec::with_capacity(HEADER_LEN);
buf.extend_from_slice(MAGIC);
buf.extend_from_slice(&applied_index.to_le_bytes());
buf.extend_from_slice(&family_count.to_le_bytes());
buf
}
pub(crate) fn put_kv(block: &mut Vec<u8>, key: &[u8], value: &[u8]) {
block.extend_from_slice(&(key.len() as u32).to_le_bytes());
block.extend_from_slice(key);
block.extend_from_slice(&(value.len() as u32).to_le_bytes());
block.extend_from_slice(value);
}
pub(crate) fn put_family(buf: &mut Vec<u8>, name: &[u8], block: &[u8]) {
buf.extend_from_slice(&(name.len() as u32).to_le_bytes());
buf.extend_from_slice(name);
buf.extend_from_slice(&(block.len() as u32).to_le_bytes());
buf.extend_from_slice(block);
}
fn corrupt(what: &'static str) -> Error {
Error::node_corrupt(what)
}
fn parse_header(bytes: &[u8]) -> Result<(u64, u32)> {
if bytes.len() < HEADER_LEN || &bytes[0..8] != MAGIC {
return Err(corrupt("checkpoint image: bad magic or truncated header"));
}
let applied_index = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
let family_count = u32::from_le_bytes(bytes[16..20].try_into().unwrap());
Ok((applied_index, family_count))
}
fn take<'a>(bytes: &'a [u8], off: &mut usize) -> Result<&'a [u8]> {
let start = *off;
if start + 4 > bytes.len() {
return Err(corrupt("checkpoint image: truncated length"));
}
let len = u32::from_le_bytes(bytes[start..start + 4].try_into().unwrap()) as usize;
let data_start = start + 4;
let data_end = data_start
.checked_add(len)
.filter(|&e| e <= bytes.len())
.ok_or_else(|| corrupt("checkpoint image: truncated body"))?;
*off = data_end;
Ok(&bytes[data_start..data_end])
}
pub(crate) type Kv<'a> = (&'a [u8], &'a [u8]);
pub(crate) type Family<'a> = (&'a [u8], Vec<Kv<'a>>);
pub(crate) struct Decoded<'a> {
pub applied_index: u64,
pub families: Vec<Family<'a>>,
}
pub(crate) fn decode(bytes: &[u8]) -> Result<Decoded<'_>> {
let (applied_index, family_count) = parse_header(bytes)?;
let mut off = HEADER_LEN;
let mut families = Vec::with_capacity(family_count as usize);
for _ in 0..family_count {
let name = take(bytes, &mut off)?;
let block = take(bytes, &mut off)?;
let mut kv = Vec::new();
let mut boff = 0usize;
while boff < block.len() {
let key = take(block, &mut boff)?;
let value = take(block, &mut boff)?;
kv.push((key, value));
}
families.push((name, kv));
}
if off != bytes.len() {
return Err(corrupt("checkpoint image: trailing bytes"));
}
Ok(Decoded {
applied_index,
families,
})
}