Skip to main content

datacard_rs/
generic.rs

1//! Generic card implementation
2//!
3//! Works with any CardFormat implementation.
4
5use crate::checksum::calculate_crc32;
6use crate::error::{CardError, Result};
7use crate::format::CardFormat;
8use crate::header::FLAG_HAS_CHECKSUM;
9use std::fs;
10use std::io::{Cursor, Read, Write};
11use std::marker::PhantomData;
12use std::path::{Path, PathBuf};
13
14/// Generic card header (8 bytes)
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct GenericHeader {
17    pub magic: [u8; 4],
18    pub major: u8,
19    pub minor: u8,
20    pub flags: u16,
21}
22
23impl GenericHeader {
24    /// Create header for a specific format
25    pub fn for_format<F: CardFormat>() -> Self {
26        Self {
27            magic: F::MAGIC,
28            major: F::VERSION_MAJOR,
29            minor: F::VERSION_MINOR,
30            flags: 0,
31        }
32    }
33
34    /// Create header with checksum flag
35    pub fn for_format_with_checksum<F: CardFormat>() -> Self {
36        Self {
37            magic: F::MAGIC,
38            major: F::VERSION_MAJOR,
39            minor: F::VERSION_MINOR,
40            flags: FLAG_HAS_CHECKSUM,
41        }
42    }
43
44    /// Check if checksum flag is set
45    pub fn has_checksum(&self) -> bool {
46        self.flags & FLAG_HAS_CHECKSUM != 0
47    }
48
49    /// Write header to writer
50    pub fn write_to<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
51        writer.write_all(&self.magic)?;
52        writer.write_all(&[self.major])?;
53        writer.write_all(&[self.minor])?;
54        writer.write_all(&self.flags.to_le_bytes())?;
55        Ok(())
56    }
57
58    /// Get header as bytes
59    pub fn to_bytes(&self) -> Vec<u8> {
60        let mut bytes = Vec::with_capacity(8);
61        bytes.extend_from_slice(&self.magic);
62        bytes.push(self.major);
63        bytes.push(self.minor);
64        bytes.extend_from_slice(&self.flags.to_le_bytes());
65        bytes
66    }
67
68    /// Read header from reader
69    pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
70        let mut magic = [0u8; 4];
71        reader.read_exact(&mut magic)?;
72
73        let mut version = [0u8; 2];
74        reader.read_exact(&mut version)?;
75
76        let mut flags_bytes = [0u8; 2];
77        reader.read_exact(&mut flags_bytes)?;
78
79        Ok(Self {
80            magic,
81            major: version[0],
82            minor: version[1],
83            flags: u16::from_le_bytes(flags_bytes),
84        })
85    }
86
87    /// Validate header against a format
88    pub fn validate<F: CardFormat>(&self) -> Result<()> {
89        F::validate_magic(&self.magic)?;
90        F::validate_version(self.major, self.minor)?;
91        Ok(())
92    }
93}
94
95/// Generic card metadata (JSON-serializable)
96#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
97pub struct GenericMetadata {
98    /// Payload identifier
99    pub id: String,
100
101    /// Payload size in bytes
102    pub payload_size: u64,
103
104    /// Optional: original size before any transformation
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub original_size: Option<u64>,
107
108    /// Format-specific extension data
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub ext: Option<serde_json::Value>,
111}
112
113impl GenericMetadata {
114    /// Create minimal metadata
115    pub fn new(id: impl Into<String>, payload_size: u64) -> Self {
116        Self {
117            id: id.into(),
118            payload_size,
119            original_size: None,
120            ext: None,
121        }
122    }
123
124    /// Builder: set original size
125    pub fn with_original_size(mut self, size: u64) -> Self {
126        self.original_size = Some(size);
127        self
128    }
129
130    /// Builder: set extension data
131    pub fn with_ext(mut self, ext: serde_json::Value) -> Self {
132        self.ext = Some(ext);
133        self
134    }
135
136    /// Serialize to JSON bytes
137    pub fn to_json(&self) -> Result<Vec<u8>> {
138        Ok(serde_json::to_vec(self)?)
139    }
140
141    /// Deserialize from JSON bytes
142    pub fn from_json(bytes: &[u8]) -> Result<Self> {
143        Ok(serde_json::from_slice(bytes)?)
144    }
145}
146
147/// Generic card that works with any CardFormat
148#[derive(Debug, Clone)]
149pub struct GenericCard<F: CardFormat> {
150    /// Card header
151    pub header: GenericHeader,
152
153    /// Card metadata
154    pub metadata: GenericMetadata,
155
156    /// Raw payload bytes
157    pub payload: Vec<u8>,
158
159    /// Format marker
160    _format: PhantomData<F>,
161}
162
163impl<F: CardFormat> GenericCard<F> {
164    /// Create a new card with payload
165    pub fn new(id: impl Into<String>, payload: Vec<u8>) -> Self {
166        let metadata = GenericMetadata::new(id, payload.len() as u64);
167        Self {
168            header: GenericHeader::for_format::<F>(),
169            metadata,
170            payload,
171            _format: PhantomData,
172        }
173    }
174
175    /// Create a new card with checksum
176    pub fn new_with_checksum(id: impl Into<String>, payload: Vec<u8>) -> Self {
177        let metadata = GenericMetadata::new(id, payload.len() as u64);
178        Self {
179            header: GenericHeader::for_format_with_checksum::<F>(),
180            metadata,
181            payload,
182            _format: PhantomData,
183        }
184    }
185
186    /// Create from metadata and payload
187    pub fn from_parts(metadata: GenericMetadata, payload: Vec<u8>) -> Result<Self> {
188        if payload.len() as u64 != metadata.payload_size {
189            return Err(CardError::PayloadSizeMismatch {
190                expected: metadata.payload_size,
191                actual: payload.len(),
192            });
193        }
194
195        Ok(Self {
196            header: GenericHeader::for_format::<F>(),
197            metadata,
198            payload,
199            _format: PhantomData,
200        })
201    }
202
203    /// Load card from file
204    ///
205    /// The `.card` extension is added automatically - do NOT include it in the path.
206    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
207        let path = Self::enforce_card_extension(path.as_ref())?;
208        let data = fs::read(&path)?;
209        Self::from_bytes(&data)
210    }
211
212    /// Save card to file
213    ///
214    /// The `.card` extension is added automatically - do NOT include it in the path.
215    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
216        let path = Self::enforce_card_extension(path.as_ref())?;
217        let bytes = self.to_bytes()?;
218        fs::write(&path, bytes)?;
219        Ok(())
220    }
221
222    /// Enforce .card extension - reject paths that already have it, add it automatically
223    fn enforce_card_extension(path: &Path) -> Result<PathBuf> {
224        // Reject if path already ends with .card
225        if let Some(ext) = path.extension() {
226            if ext == "card" {
227                return Err(CardError::InvalidFormat(
228                    "Do not include .card extension in path - it is added automatically".to_string()
229                ));
230            }
231        }
232
233        // Add .card extension
234        let mut path_buf = path.to_path_buf();
235        let mut new_name = path_buf
236            .file_name()
237            .map(|s| s.to_os_string())
238            .unwrap_or_default();
239        new_name.push(".card");
240        path_buf.set_file_name(new_name);
241
242        Ok(path_buf)
243    }
244
245    /// Calculate CRC32 checksum
246    pub fn calculate_checksum(&self) -> u32 {
247        let header_bytes = self.header.to_bytes();
248        let meta_json = self.metadata.to_json().unwrap();
249        let meta_len_bytes = (meta_json.len() as u32).to_le_bytes();
250
251        calculate_crc32(&[&header_bytes, &meta_len_bytes, &meta_json, &self.payload])
252    }
253
254    /// Serialize to bytes
255    pub fn to_bytes(&self) -> Result<Vec<u8>> {
256        let mut buffer = Vec::new();
257
258        // Header
259        self.header.write_to(&mut buffer)?;
260
261        // Metadata
262        let meta_json = self.metadata.to_json()?;
263        if meta_json.len() > 65536 {
264            return Err(CardError::MetadataTooLarge(meta_json.len()));
265        }
266        buffer.write_all(&(meta_json.len() as u32).to_le_bytes())?;
267        buffer.write_all(&meta_json)?;
268
269        // Payload
270        buffer.write_all(&self.payload)?;
271
272        // Checksum if enabled
273        if self.header.has_checksum() {
274            let checksum = self.calculate_checksum();
275            buffer.write_all(&checksum.to_le_bytes())?;
276        }
277
278        Ok(buffer)
279    }
280
281    /// Deserialize from bytes
282    pub fn from_bytes(data: &[u8]) -> Result<Self> {
283        let mut cursor = Cursor::new(data);
284
285        // Read and validate header
286        let header = GenericHeader::read_from(&mut cursor)?;
287        header.validate::<F>()?;
288
289        // Read metadata
290        let mut meta_len_bytes = [0u8; 4];
291        cursor.read_exact(&mut meta_len_bytes)?;
292        let meta_len = u32::from_le_bytes(meta_len_bytes) as usize;
293
294        let mut meta_json = vec![0u8; meta_len];
295        cursor.read_exact(&mut meta_json)?;
296        let metadata = GenericMetadata::from_json(&meta_json)?;
297
298        // Read payload
299        let payload_len = metadata.payload_size as usize;
300        let mut payload = vec![0u8; payload_len];
301        cursor.read_exact(&mut payload)?;
302
303        // Format-specific payload validation
304        F::validate_payload(&payload)?;
305
306        // Validate checksum if present
307        if header.has_checksum() {
308            let mut checksum_bytes = [0u8; 4];
309            cursor.read_exact(&mut checksum_bytes)?;
310            let stored_checksum = u32::from_le_bytes(checksum_bytes);
311
312            let card = Self {
313                header,
314                metadata,
315                payload,
316                _format: PhantomData,
317            };
318
319            let calculated = card.calculate_checksum();
320            if calculated != stored_checksum {
321                return Err(CardError::ChecksumMismatch {
322                    expected: stored_checksum,
323                    actual: calculated,
324                });
325            }
326
327            Ok(card)
328        } else {
329            Ok(Self {
330                header,
331                metadata,
332                payload,
333                _format: PhantomData,
334            })
335        }
336    }
337
338    /// Get payload reference
339    pub fn payload(&self) -> &[u8] {
340        &self.payload
341    }
342
343    /// Get ID
344    pub fn id(&self) -> &str {
345        &self.metadata.id
346    }
347
348    /// Check if card has checksum
349    pub fn has_checksum(&self) -> bool {
350        self.header.has_checksum()
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    /// Test format
359    struct TestFormat;
360
361    impl CardFormat for TestFormat {
362        const MAGIC: [u8; 4] = *b"TEST";
363        const VERSION_MAJOR: u8 = 1;
364        const VERSION_MINOR: u8 = 0;
365
366        fn format_name() -> &'static str {
367            "Test"
368        }
369    }
370
371    #[test]
372    fn test_generic_card_roundtrip() {
373        let payload = vec![1, 2, 3, 4, 5];
374        let card: GenericCard<TestFormat> = GenericCard::new("test::roundtrip", payload.clone());
375
376        let bytes = card.to_bytes().unwrap();
377        let loaded: GenericCard<TestFormat> = GenericCard::from_bytes(&bytes).unwrap();
378
379        assert_eq!(loaded.id(), "test::roundtrip");
380        assert_eq!(loaded.payload(), &payload);
381    }
382
383    #[test]
384    fn test_generic_card_with_checksum() {
385        let payload = vec![1, 2, 3, 4, 5];
386        let card: GenericCard<TestFormat> =
387            GenericCard::new_with_checksum("test::checksum", payload.clone());
388
389        assert!(card.has_checksum());
390
391        let bytes = card.to_bytes().unwrap();
392        let loaded: GenericCard<TestFormat> = GenericCard::from_bytes(&bytes).unwrap();
393
394        assert!(loaded.has_checksum());
395        assert_eq!(loaded.payload(), &payload);
396    }
397
398    #[test]
399    fn test_wrong_magic_fails() {
400        struct OtherFormat;
401        impl CardFormat for OtherFormat {
402            const MAGIC: [u8; 4] = *b"OTHE";
403            const VERSION_MAJOR: u8 = 1;
404            const VERSION_MINOR: u8 = 0;
405            fn format_name() -> &'static str {
406                "Other"
407            }
408        }
409
410        let card: GenericCard<TestFormat> = GenericCard::new("test", vec![1, 2, 3]);
411        let bytes = card.to_bytes().unwrap();
412
413        // Try to load as OtherFormat - should fail
414        let result: Result<GenericCard<OtherFormat>> = GenericCard::from_bytes(&bytes);
415        assert!(matches!(result, Err(CardError::InvalidMagic(_))));
416    }
417}