tempest-core 0.0.2

Core utilities and primitives for TempestDB
Documentation
use crc64::crc64;

use crate::utils::HexU64;

use super::config::JournalError;

pub const JOURNAL_MAGIC_NUM: &[u8; 8] = b"TMPSJRNL";

pub(super) const JOURNAL_HEADER_SIZE: usize = 24;

#[derive(Debug)]
pub(super) struct JournalHeader {
    pub(super) filenum: u64,
}

impl JournalHeader {
    pub(super) fn new(filenum: u64) -> Self {
        Self { filenum }
    }

    pub(super) fn encode(&self) -> [u8; JOURNAL_HEADER_SIZE] {
        let mut buf = [0u8; JOURNAL_HEADER_SIZE];
        buf[0..8].copy_from_slice(JOURNAL_MAGIC_NUM);
        buf[8..16].copy_from_slice(&self.filenum.to_le_bytes());
        let checksum = crc64(0, &buf[0..16]);
        buf[16..24].copy_from_slice(&checksum.to_le_bytes());
        buf
    }

    /// A helper function for `decode`, that converts the slice into a fixed length slice.
    /// The length of `buf` must be equal to `EDIT_PREFIX_SIZE`.
    ///
    /// # Panics
    ///
    /// Panics if `buf.len() != EDIT_PREFIX_SIZE`.
    pub(super) fn decode_from_slice(buf: &[u8]) -> Result<Self, JournalError> {
        assert_eq!(
            buf.len(),
            JOURNAL_HEADER_SIZE,
            "could not decode JournalHeader: invalid slice length"
        );
        Self::decode(buf.try_into().unwrap())
    }

    fn decode(buf: [u8; JOURNAL_HEADER_SIZE]) -> Result<Self, JournalError> {
        // validate magic num
        let magic_bytes = &buf[0..8];
        if magic_bytes != JOURNAL_MAGIC_NUM {
            return Err(JournalError::InvalidMagic);
        }

        // validate checksum
        let stored_checksum = u64::from_le_bytes(buf[16..24].try_into().unwrap());
        let computed_checksum = crc64(0, &buf[0..16]);
        if stored_checksum != computed_checksum {
            return Err(JournalError::Checksum);
        }

        // retrieve filenum
        let filenum = u64::from_le_bytes(buf[8..16].try_into().unwrap());
        Ok(Self { filenum })
    }
}

pub(super) const EDIT_PREFIX_SIZE: usize = 12;

#[derive(Debug, Clone, Copy)]
pub struct EditPrefix {
    #[debug("{:?}", HexU64(*checksum))]
    checksum: u64,
    len: u32,
}

impl EditPrefix {
    /// Creates a new `EditPrefix` for `data`, computing the checksum and length for framing it.
    /// The length of `data` may not be larger than `u32::MAX`.
    ///
    /// # Panics
    ///
    /// Panics if `data.len() > u32::MAX`.
    pub fn new(data: &[u8]) -> Self {
        assert!(
            data.len() <= u32::MAX as usize,
            "journal edits may not be larger than u32::MAX bytes"
        );
        let checksum = crc64(0, data);
        let len = data.len() as u32;
        Self { checksum, len }
    }

    /// Encode this into bytes.
    pub fn encode(&self) -> [u8; EDIT_PREFIX_SIZE] {
        let mut buf = [0u8; EDIT_PREFIX_SIZE];
        buf[0..8].copy_from_slice(&self.checksum.to_le_bytes());
        buf[8..12].copy_from_slice(&self.len.to_le_bytes());
        buf
    }

    /// A helper function for `decode`, that converts the slice into a fixed length slice.
    /// The length of `buf` must be equal to `EDIT_PREFIX_SIZE`.
    ///
    /// # Panics
    ///
    /// Panics if `buf.len() != EDIT_PREFIX_SIZE`.
    pub fn decode_from_slice(buf: &[u8]) -> Self {
        assert_eq!(
            buf.len(),
            EDIT_PREFIX_SIZE,
            "could not decode EditPrefix: invalid slice length"
        );
        Self::decode(buf.try_into().unwrap())
    }

    pub fn decode(buf: &[u8; EDIT_PREFIX_SIZE]) -> Self {
        let checksum = u64::from_le_bytes(buf[0..8].try_into().unwrap());
        let len = u32::from_le_bytes(buf[8..12].try_into().unwrap());
        Self { checksum, len }
    }

    /// Returns the length of data this is framing.
    pub const fn len(&self) -> u32 {
        self.len
    }

    /// Checks if the data is valid, by comparing the checksum result with the stored checksum.
    /// The length of `data` must be equal to the stored length.
    ///
    /// # Panics
    ///
    /// Panics if `data.len() != self.len`.
    pub fn is_valid(&self, data: &[u8]) -> bool {
        assert_eq!(data.len(), self.len as usize);
        let computed = crc64(0, data);
        computed == self.checksum
    }
}