lxdb-format 0.1.0

Binary format records and sections for LXDB datasets.
Documentation
use crate::{FormatError, magic::MAGIC, version::Version};

/// Number of bytes occupied by the binary LXDB header.
pub const HEADER_SIZE: usize = 8;

/// Header stored at the beginning of every LXDB dataset.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Header {
    version: Version,
}

impl Header {
    pub const SIZE: usize = HEADER_SIZE;

    pub const fn new(version: Version) -> Self {
        Self { version }
    }

    pub const fn current() -> Self {
        Self::new(Version::CURRENT)
    }

    pub const fn version(self) -> Version {
        self.version
    }

    pub fn encode(self) -> [u8; HEADER_SIZE] {
        let mut bytes = [0_u8; HEADER_SIZE];

        bytes[0..4].copy_from_slice(&MAGIC);
        bytes[4..6].copy_from_slice(&self.version.major().to_le_bytes());
        bytes[6..8].copy_from_slice(&self.version.minor().to_le_bytes());

        bytes
    }

    pub fn decode(bytes: &[u8]) -> Result<Self, FormatError> {
        if bytes.len() != Self::SIZE {
            return Err(FormatError::UnexpectedRecordSize {
                record: "LXDB header",
                expected: Self::SIZE,
                found: bytes.len(),
            });
        }

        if bytes[0..4] != MAGIC {
            return Err(FormatError::InvalidHeader);
        }

        let major = u16::from_le_bytes(
            bytes[4..6].try_into().expect("major version must occupy two bytes"),
        );

        let minor = u16::from_le_bytes(
            bytes[6..8].try_into().expect("minor version must occupy two bytes"),
        );

        Ok(Self::new(Version::new(major, minor)))
    }
}

impl Default for Header {
    fn default() -> Self {
        Self::current()
    }
}

#[cfg(test)]
mod tests {
    use super::{HEADER_SIZE, Header};
    use crate::magic::MAGIC;

    #[test]
    fn encodes_current_header() {
        let bytes = Header::current().encode();

        assert_eq!(bytes.len(), HEADER_SIZE);
        assert_eq!(&bytes[0..4], &MAGIC);
        assert_eq!(u16::from_le_bytes([bytes[4], bytes[5]]), 0);
        assert_eq!(u16::from_le_bytes([bytes[6], bytes[7]]), 1);
    }

    #[test]
    fn encodes_custom_version() {
        let bytes = Header::new(crate::version::Version::new(2, 7)).encode();

        assert_eq!(u16::from_le_bytes([bytes[4], bytes[5]]), 2);
        assert_eq!(u16::from_le_bytes([bytes[6], bytes[7]]), 7);
    }
}