Skip to main content

datacard_rs/
header.rs

1//! Card file header implementation
2
3use crate::error::Result;
4use crate::CardError;
5use std::io::{Read, Write};
6
7/// Magic bytes for card files ("CARD")
8pub const MAGIC: &[u8; 4] = b"CARD";
9
10/// Current major version
11pub const VERSION_MAJOR: u8 = 1;
12
13/// Current minor version
14pub const VERSION_MINOR: u8 = 0;
15
16/// Flag: Card has CRC32 checksum in footer
17pub const FLAG_HAS_CHECKSUM: u16 = 0x01;
18
19/// Flag: Metadata includes timestamp
20pub const FLAG_HAS_TIMESTAMP: u16 = 0x02;
21
22/// Card file header (8 bytes)
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct CardHeader {
25    pub magic: [u8; 4],
26    pub major: u8,
27    pub minor: u8,
28    pub flags: u16,
29}
30
31impl CardHeader {
32    /// Create new header with current version and no flags
33    pub fn new() -> Self {
34        Self {
35            magic: *MAGIC,
36            major: VERSION_MAJOR,
37            minor: VERSION_MINOR,
38            flags: 0,
39        }
40    }
41
42    /// Create header with specific flags
43    pub fn with_flags(flags: u16) -> Self {
44        Self {
45            magic: *MAGIC,
46            major: VERSION_MAJOR,
47            minor: VERSION_MINOR,
48            flags,
49        }
50    }
51
52    /// Check if checksum flag is set
53    pub fn has_checksum(&self) -> bool {
54        self.flags & FLAG_HAS_CHECKSUM != 0
55    }
56
57    /// Check if timestamp flag is set
58    pub fn has_timestamp(&self) -> bool {
59        self.flags & FLAG_HAS_TIMESTAMP != 0
60    }
61
62    /// Write header to writer
63    pub fn write_to<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
64        writer.write_all(&self.magic)?;
65        writer.write_all(&[self.major])?;
66        writer.write_all(&[self.minor])?;
67        writer.write_all(&self.flags.to_le_bytes())?;
68        Ok(())
69    }
70
71    /// Get header as bytes
72    pub fn to_bytes(&self) -> Vec<u8> {
73        let mut bytes = Vec::with_capacity(8);
74        bytes.extend_from_slice(&self.magic);
75        bytes.push(self.major);
76        bytes.push(self.minor);
77        bytes.extend_from_slice(&self.flags.to_le_bytes());
78        bytes
79    }
80
81    /// Read header from reader
82    pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
83        let mut magic = [0u8; 4];
84        reader.read_exact(&mut magic)?;
85
86        let mut version = [0u8; 2];
87        reader.read_exact(&mut version)?;
88
89        let mut flags_bytes = [0u8; 2];
90        reader.read_exact(&mut flags_bytes)?;
91
92        Ok(Self {
93            magic,
94            major: version[0],
95            minor: version[1],
96            flags: u16::from_le_bytes(flags_bytes),
97        })
98    }
99
100    /// Validate header
101    pub fn validate(&self) -> Result<()> {
102        if &self.magic != MAGIC {
103            return Err(CardError::InvalidMagic(self.magic));
104        }
105
106        if self.major != VERSION_MAJOR {
107            return Err(CardError::UnsupportedVersion {
108                major: self.major,
109                minor: self.minor,
110            });
111        }
112
113        Ok(())
114    }
115}
116
117impl Default for CardHeader {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use std::io::Cursor;
127
128    #[test]
129    fn test_header_new() {
130        let header = CardHeader::new();
131        assert_eq!(header.magic, *MAGIC);
132        assert_eq!(header.major, VERSION_MAJOR);
133        assert_eq!(header.minor, VERSION_MINOR);
134        assert_eq!(header.flags, 0);
135    }
136
137    #[test]
138    fn test_header_with_flags() {
139        let header = CardHeader::with_flags(FLAG_HAS_CHECKSUM | FLAG_HAS_TIMESTAMP);
140        assert!(header.has_checksum());
141        assert!(header.has_timestamp());
142    }
143
144    #[test]
145    fn test_header_roundtrip() {
146        let header = CardHeader::new();
147        let mut buffer = Vec::new();
148        header.write_to(&mut buffer).unwrap();
149
150        let mut cursor = Cursor::new(&buffer);
151        let loaded = CardHeader::read_from(&mut cursor).unwrap();
152
153        assert_eq!(loaded, header);
154    }
155
156    #[test]
157    fn test_header_validation() {
158        let mut header = CardHeader::new();
159        assert!(header.validate().is_ok());
160
161        // Invalid magic
162        header.magic = *b"FAKE";
163        assert!(header.validate().is_err());
164
165        // Invalid version
166        header.magic = *MAGIC;
167        header.major = 99;
168        assert!(header.validate().is_err());
169    }
170
171    #[test]
172    fn test_header_to_bytes() {
173        let header = CardHeader::with_flags(FLAG_HAS_CHECKSUM);
174        let bytes = header.to_bytes();
175
176        assert_eq!(bytes.len(), 8);
177        assert_eq!(&bytes[0..4], MAGIC);
178        assert_eq!(bytes[4], VERSION_MAJOR);
179        assert_eq!(bytes[5], VERSION_MINOR);
180        assert_eq!(u16::from_le_bytes([bytes[6], bytes[7]]), FLAG_HAS_CHECKSUM);
181    }
182}