use crate::error::Result;
use crate::CardError;
use std::io::{Read, Write};
pub const MAGIC: &[u8; 4] = b"CARD";
pub const VERSION_MAJOR: u8 = 1;
pub const VERSION_MINOR: u8 = 0;
pub const FLAG_HAS_CHECKSUM: u16 = 0x01;
pub const FLAG_HAS_TIMESTAMP: u16 = 0x02;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CardHeader {
pub magic: [u8; 4],
pub major: u8,
pub minor: u8,
pub flags: u16,
}
impl CardHeader {
pub fn new() -> Self {
Self {
magic: *MAGIC,
major: VERSION_MAJOR,
minor: VERSION_MINOR,
flags: 0,
}
}
pub fn with_flags(flags: u16) -> Self {
Self {
magic: *MAGIC,
major: VERSION_MAJOR,
minor: VERSION_MINOR,
flags,
}
}
pub fn has_checksum(&self) -> bool {
self.flags & FLAG_HAS_CHECKSUM != 0
}
pub fn has_timestamp(&self) -> bool {
self.flags & FLAG_HAS_TIMESTAMP != 0
}
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(())
}
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
}
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),
})
}
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());
header.magic = *b"FAKE";
assert!(header.validate().is_err());
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);
}
}