streaming_crypto/core_api/headers/decode.rs
1// ## 📂 File: `src/headers/decode.rs`
2//! src/headers/decode.rs
3//!
4//! Header decoding utilities.
5//!
6//! Design notes:
7//! - Deserializes a fixed 80‑byte buffer into a `HeaderV1` struct.
8//! - Field order must match `encode.rs` exactly for ABI stability.
9//! - Validation is performed after decoding to reject malformed or incompatible streams.
10//! - Treat header as authoritative source for strategy and chunk sizing.
11
12use crate::headers::types::{HeaderV1, HeaderError};
13
14/// Deserialize an 80‑byte little‑endian header into `HeaderV1`.
15///
16/// # Returns
17/// - `Ok(HeaderV1)` if decoding and validation succeed.
18/// - `Err(HeaderError)` if buffer length mismatches or validation fails.
19///
20/// # Notes
21/// - Field order must match the struct layout in `encode.rs`.
22/// - Uses helper functions for compact little‑endian reads.
23/// - Debug assertion ensures exactly 80 bytes are consumed.
24#[inline]
25pub fn decode_header_le(buf: &[u8]) -> Result<HeaderV1, HeaderError> {
26 // Ensure buffer has at least HEADER_LEN_V1 bytes.
27 if buf.len() < HeaderV1::LEN {
28 return Err(HeaderError::BufferTooShort { have: buf.len(), need: HeaderV1::LEN });
29 }
30
31 // Cursor helpers
32 let mut i = 0usize;
33 #[inline] fn get_u16(buf: &[u8], i: &mut usize) -> u16 { let v = u16::from_le_bytes(buf[*i..*i+2].try_into().unwrap()); *i += 2; v }
34 #[inline] fn get_u32(buf: &[u8], i: &mut usize) -> u32 { let v = u32::from_le_bytes(buf[*i..*i+4].try_into().unwrap()); *i += 4; v }
35 #[inline] fn get_u64(buf: &[u8], i: &mut usize) -> u64 { let v = u64::from_le_bytes(buf[*i..*i+8].try_into().unwrap()); *i += 8; v }
36 #[inline] fn get_bytes<const N: usize>(buf: &[u8], i: &mut usize) -> [u8; N] {
37 let mut dst = [0u8; N]; dst.copy_from_slice(&buf[*i..*i+N]); *i += N; dst
38 }
39
40 // Initialize header with defaults.
41 let mut h = HeaderV1::default();
42
43 // Field order must match encode.rs layout.
44 h.magic = get_bytes::<4>(buf, &mut i); // 0..4 magic number
45 h.version = get_u16(buf, &mut i); // 4..6 version
46 h.alg_profile = get_u16(buf, &mut i); // 6..8 algorithm profile
47 h.cipher = get_u16(buf, &mut i); // 8..10 cipher suite
48 h.hkdf_prf = get_u16(buf, &mut i); // 10..12 HKDF PRF
49 h.compression = get_u16(buf, &mut i); // 12..14 compression codec
50 h.strategy = get_u16(buf, &mut i); // 14..16 strategy
51 h.aad_domain = get_u16(buf, &mut i); // 16..18 AAD domain
52 h.flags = get_u16(buf, &mut i); // 18..20 flags bitmask
53 h.chunk_size = get_u32(buf, &mut i); // 20..24 chunk size
54 h.plaintext_size = get_u64(buf, &mut i); // 24..32 total plaintext size
55 h.crc32 = get_u32(buf, &mut i); // 32..36 CRC32 checksum
56 h.dict_id = get_u32(buf, &mut i); // 36..40 dictionary ID
57 h.salt = get_bytes::<16>(buf, &mut i); // 40..56 salt (16 bytes)
58 h.key_id = get_u32(buf, &mut i); // 56..60 key identifier
59 h.parallel_hint = get_u32(buf, &mut i); // 60..64 parallelization hint
60 h.enc_time_ns = get_u64(buf, &mut i); // 64..72 encryption timestamp (ns)
61 h.reserved = get_bytes::<8>(buf, &mut i); // 72..80 reserved bytes
62
63 // Sanity check: ensure we consumed exactly HEADER_LEN_V1 bytes.
64 if i != HeaderV1::LEN {
65 return Err(HeaderError::BufferTooShort { have: i, need: HeaderV1::LEN });
66 }
67
68 // ✅ CRC32 validation: compute over first 32 bytes (0..32)
69 let computed_crc = crc32fast::hash(&buf[0..32]);
70 if h.crc32 != computed_crc {
71 return Err(HeaderError::InvalidCrc32 { have: h.crc32 as usize, need: computed_crc as usize });
72 }
73
74 // Validate decoded header fields.
75 h.validate()?;
76
77 Ok(h)
78}