datacard-rs 1.0.0

Generic binary card format library with checksums and pluggable format traits
Documentation
//! Card file header implementation

use crate::error::Result;
use crate::CardError;
use std::io::{Read, Write};

/// Magic bytes for card files ("CARD")
pub const MAGIC: &[u8; 4] = b"CARD";

/// Current major version
pub const VERSION_MAJOR: u8 = 1;

/// Current minor version
pub const VERSION_MINOR: u8 = 0;

/// Flag: Card has CRC32 checksum in footer
pub const FLAG_HAS_CHECKSUM: u16 = 0x01;

/// Flag: Metadata includes timestamp
pub const FLAG_HAS_TIMESTAMP: u16 = 0x02;

/// Card file header (8 bytes)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CardHeader {
    pub magic: [u8; 4],
    pub major: u8,
    pub minor: u8,
    pub flags: u16,
}

impl CardHeader {
    /// Create new header with current version and no flags
    pub fn new() -> Self {
        Self {
            magic: *MAGIC,
            major: VERSION_MAJOR,
            minor: VERSION_MINOR,
            flags: 0,
        }
    }

    /// Create header with specific flags
    pub fn with_flags(flags: u16) -> Self {
        Self {
            magic: *MAGIC,
            major: VERSION_MAJOR,
            minor: VERSION_MINOR,
            flags,
        }
    }

    /// Check if checksum flag is set
    pub fn has_checksum(&self) -> bool {
        self.flags & FLAG_HAS_CHECKSUM != 0
    }

    /// Check if timestamp flag is set
    pub fn has_timestamp(&self) -> bool {
        self.flags & FLAG_HAS_TIMESTAMP != 0
    }

    /// Write header to writer
    pub fn write_to<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
        writer.write_all(&self.magic)?;
        writer.write_all(&[self.major])?;
        writer.write_all(&[self.minor])?;
        writer.write_all(&self.flags.to_le_bytes())?;
        Ok(())
    }

    /// Get header as bytes
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(8);
        bytes.extend_from_slice(&self.magic);
        bytes.push(self.major);
        bytes.push(self.minor);
        bytes.extend_from_slice(&self.flags.to_le_bytes());
        bytes
    }

    /// Read header from reader
    pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
        let mut magic = [0u8; 4];
        reader.read_exact(&mut magic)?;

        let mut version = [0u8; 2];
        reader.read_exact(&mut version)?;

        let mut flags_bytes = [0u8; 2];
        reader.read_exact(&mut flags_bytes)?;

        Ok(Self {
            magic,
            major: version[0],
            minor: version[1],
            flags: u16::from_le_bytes(flags_bytes),
        })
    }

    /// Validate header
    pub fn validate(&self) -> Result<()> {
        if &self.magic != MAGIC {
            return Err(CardError::InvalidMagic(self.magic));
        }

        if self.major != VERSION_MAJOR {
            return Err(CardError::UnsupportedVersion {
                major: self.major,
                minor: self.minor,
            });
        }

        Ok(())
    }
}

impl Default for CardHeader {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Cursor;

    #[test]
    fn test_header_new() {
        let header = CardHeader::new();
        assert_eq!(header.magic, *MAGIC);
        assert_eq!(header.major, VERSION_MAJOR);
        assert_eq!(header.minor, VERSION_MINOR);
        assert_eq!(header.flags, 0);
    }

    #[test]
    fn test_header_with_flags() {
        let header = CardHeader::with_flags(FLAG_HAS_CHECKSUM | FLAG_HAS_TIMESTAMP);
        assert!(header.has_checksum());
        assert!(header.has_timestamp());
    }

    #[test]
    fn test_header_roundtrip() {
        let header = CardHeader::new();
        let mut buffer = Vec::new();
        header.write_to(&mut buffer).unwrap();

        let mut cursor = Cursor::new(&buffer);
        let loaded = CardHeader::read_from(&mut cursor).unwrap();

        assert_eq!(loaded, header);
    }

    #[test]
    fn test_header_validation() {
        let mut header = CardHeader::new();
        assert!(header.validate().is_ok());

        // Invalid magic
        header.magic = *b"FAKE";
        assert!(header.validate().is_err());

        // Invalid version
        header.magic = *MAGIC;
        header.major = 99;
        assert!(header.validate().is_err());
    }

    #[test]
    fn test_header_to_bytes() {
        let header = CardHeader::with_flags(FLAG_HAS_CHECKSUM);
        let bytes = header.to_bytes();

        assert_eq!(bytes.len(), 8);
        assert_eq!(&bytes[0..4], MAGIC);
        assert_eq!(bytes[4], VERSION_MAJOR);
        assert_eq!(bytes[5], VERSION_MINOR);
        assert_eq!(u16::from_le_bytes([bytes[6], bytes[7]]), FLAG_HAS_CHECKSUM);
    }
}