vole_document/container/
header.rs1use crate::error::{Error, Result};
9use crate::integrity::crc32c;
10use crate::{EXACTNESS_PROFILE_EXACT_BYTES, SOURCE_FORMAT_OPAQUE};
11
12pub const HEADER_LEN: usize = 64;
14
15pub const MAGIC: [u8; 8] = *b"VOLDOC\x1A\x00";
17
18pub const FORMAT_MAJOR: u16 = 0;
20pub const FORMAT_MINOR: u16 = 1;
22
23pub const SUPPORTED_MANDATORY_FEATURES: u32 = 0;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct Header {
29 pub major: u16,
31 pub minor: u16,
33 pub mandatory_features: u32,
35 pub optional_features: u32,
37 pub exactness_profile: u8,
39 pub source_format: u8,
41 pub universe_id: [u8; 16],
43 pub declared_source_len: u64,
45}
46
47impl Header {
48 pub fn new(
50 universe_id: [u8; 16],
51 declared_source_len: u64,
52 exactness_profile: u8,
53 source_format: u8,
54 ) -> Self {
55 Header {
56 major: FORMAT_MAJOR,
57 minor: FORMAT_MINOR,
58 mandatory_features: 0,
59 optional_features: 0,
60 exactness_profile,
61 source_format,
62 universe_id,
63 declared_source_len,
64 }
65 }
66
67 pub fn encode(&self) -> [u8; HEADER_LEN] {
69 let mut b = [0u8; HEADER_LEN];
70 b[0..8].copy_from_slice(&MAGIC);
71 b[8..10].copy_from_slice(&self.major.to_le_bytes());
72 b[10..12].copy_from_slice(&self.minor.to_le_bytes());
73 b[12..16].copy_from_slice(&self.mandatory_features.to_le_bytes());
74 b[16..20].copy_from_slice(&self.optional_features.to_le_bytes());
75 b[20] = self.exactness_profile;
76 b[21] = self.source_format;
77 b[24..40].copy_from_slice(&self.universe_id);
79 b[40..48].copy_from_slice(&self.declared_source_len.to_le_bytes());
80 let crc = crc32c(&b[0..60]);
82 b[60..64].copy_from_slice(&crc.to_le_bytes());
83 b
84 }
85
86 pub fn decode(bytes: &[u8]) -> Result<Header> {
88 if bytes.len() < HEADER_LEN {
89 return Err(Error::invalid_container(format!(
90 "input is {} bytes; need at least {HEADER_LEN} for a header",
91 bytes.len()
92 )));
93 }
94 let b = &bytes[0..HEADER_LEN];
95 if b[0..8] != MAGIC {
96 return Err(Error::invalid_container("bad container magic"));
97 }
98 let want_crc = u32::from_le_bytes([b[60], b[61], b[62], b[63]]);
99 let got_crc = crc32c(&b[0..60]);
100 if want_crc != got_crc {
101 return Err(Error::invalid_container(format!(
102 "header CRC32C mismatch: declared {want_crc:#010x}, computed {got_crc:#010x}"
103 )));
104 }
105 if b[22] != 0 || b[23] != 0 {
106 return Err(Error::invalid_container("header reserved_a must be zero"));
107 }
108 if b[48..60].iter().any(|&x| x != 0) {
109 return Err(Error::invalid_container("header reserved_b must be zero"));
110 }
111
112 let major = u16::from_le_bytes([b[8], b[9]]);
113 let minor = u16::from_le_bytes([b[10], b[11]]);
114 if major != FORMAT_MAJOR || minor > FORMAT_MINOR {
115 return Err(Error::unsupported_version(format!(
116 "container version {major}.{minor} is not supported by this build ({FORMAT_MAJOR}.{FORMAT_MINOR})"
117 )));
118 }
119
120 let mandatory_features = u32::from_le_bytes([b[12], b[13], b[14], b[15]]);
121 let unsupported = mandatory_features & !SUPPORTED_MANDATORY_FEATURES;
122 if unsupported != 0 {
123 return Err(Error::unsupported_feature(format!(
124 "descriptor requires unsupported mandatory feature bits {unsupported:#010x}"
125 )));
126 }
127
128 let exactness_profile = b[20];
129 if exactness_profile != EXACTNESS_PROFILE_EXACT_BYTES {
130 return Err(Error::unsupported_feature(format!(
131 "exactness profile {exactness_profile} is not the normative EXACT_BYTES profile"
132 )));
133 }
134 let source_format = b[21];
135
136 let mut universe_id = [0u8; 16];
137 universe_id.copy_from_slice(&b[24..40]);
138 let declared_source_len =
139 u64::from_le_bytes([b[40], b[41], b[42], b[43], b[44], b[45], b[46], b[47]]);
140
141 Ok(Header {
142 major,
143 minor,
144 mandatory_features,
145 optional_features: u32::from_le_bytes([b[16], b[17], b[18], b[19]]),
146 exactness_profile,
147 source_format,
148 universe_id,
149 declared_source_len,
150 })
151 }
152
153 pub fn is_opaque(&self) -> bool {
155 self.source_format == SOURCE_FORMAT_OPAQUE
156 }
157
158 pub fn source_format_supported(&self) -> bool {
163 matches!(self.source_format, 0 | 1)
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn roundtrip() {
173 let h = Header::new(
174 [7u8; 16],
175 1234,
176 EXACTNESS_PROFILE_EXACT_BYTES,
177 SOURCE_FORMAT_OPAQUE,
178 );
179 let enc = h.encode();
180 assert_eq!(enc.len(), HEADER_LEN);
181 let dec = Header::decode(&enc).unwrap();
182 assert_eq!(h, dec);
183 }
184
185 #[test]
186 fn rejects_bad_magic() {
187 let mut enc = Header::new([0u8; 16], 0, 0, 0).encode();
188 enc[0] = b'X';
189 match Header::decode(&enc) {
190 Err(e) => assert_eq!(e.class(), crate::ErrorClass::InvalidContainer),
191 Ok(_) => panic!("expected failure"),
192 }
193 }
194
195 #[test]
196 fn detects_corruption_via_crc() {
197 let mut enc = Header::new([0u8; 16], 5, 0, 0).encode();
198 enc[41] ^= 0x01; let e = Header::decode(&enc).unwrap_err();
200 assert_eq!(e.class(), crate::ErrorClass::InvalidContainer);
201 }
202
203 #[test]
204 fn rejects_truncated() {
205 let e = Header::decode(&[0u8; 10]).unwrap_err();
206 assert_eq!(e.class(), crate::ErrorClass::InvalidContainer);
207 }
208
209 #[test]
210 fn rejects_unknown_mandatory_feature() {
211 let mut h = Header::new([0u8; 16], 0, 0, 0);
212 h.mandatory_features = 0x8000_0000;
213 let e = Header::decode(&h.encode()).unwrap_err();
214 assert_eq!(e.class(), crate::ErrorClass::UnsupportedFeature);
215 }
216
217 #[test]
218 fn rejects_future_major_version() {
219 let mut h = Header::new([0u8; 16], 0, 0, 0);
220 h.major = 9;
221 let e = Header::decode(&h.encode()).unwrap_err();
222 assert_eq!(e.class(), crate::ErrorClass::UnsupportedVersion);
223 }
224}