1use 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#[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 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 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 pub fn has_checksum(&self) -> bool {
46 self.flags & FLAG_HAS_CHECKSUM != 0
47 }
48
49 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 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 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 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#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
97pub struct GenericMetadata {
98 pub id: String,
100
101 pub payload_size: u64,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub original_size: Option<u64>,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub ext: Option<serde_json::Value>,
111}
112
113impl GenericMetadata {
114 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 pub fn with_original_size(mut self, size: u64) -> Self {
126 self.original_size = Some(size);
127 self
128 }
129
130 pub fn with_ext(mut self, ext: serde_json::Value) -> Self {
132 self.ext = Some(ext);
133 self
134 }
135
136 pub fn to_json(&self) -> Result<Vec<u8>> {
138 Ok(serde_json::to_vec(self)?)
139 }
140
141 pub fn from_json(bytes: &[u8]) -> Result<Self> {
143 Ok(serde_json::from_slice(bytes)?)
144 }
145}
146
147#[derive(Debug, Clone)]
149pub struct GenericCard<F: CardFormat> {
150 pub header: GenericHeader,
152
153 pub metadata: GenericMetadata,
155
156 pub payload: Vec<u8>,
158
159 _format: PhantomData<F>,
161}
162
163impl<F: CardFormat> GenericCard<F> {
164 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 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 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 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 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 fn enforce_card_extension(path: &Path) -> Result<PathBuf> {
224 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 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 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 pub fn to_bytes(&self) -> Result<Vec<u8>> {
256 let mut buffer = Vec::new();
257
258 self.header.write_to(&mut buffer)?;
260
261 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 buffer.write_all(&self.payload)?;
271
272 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 pub fn from_bytes(data: &[u8]) -> Result<Self> {
283 let mut cursor = Cursor::new(data);
284
285 let header = GenericHeader::read_from(&mut cursor)?;
287 header.validate::<F>()?;
288
289 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 let payload_len = metadata.payload_size as usize;
300 let mut payload = vec![0u8; payload_len];
301 cursor.read_exact(&mut payload)?;
302
303 F::validate_payload(&payload)?;
305
306 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 pub fn payload(&self) -> &[u8] {
340 &self.payload
341 }
342
343 pub fn id(&self) -> &str {
345 &self.metadata.id
346 }
347
348 pub fn has_checksum(&self) -> bool {
350 self.header.has_checksum()
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 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 let result: Result<GenericCard<OtherFormat>> = GenericCard::from_bytes(&bytes);
415 assert!(matches!(result, Err(CardError::InvalidMagic(_))));
416 }
417}