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 FEATURE_DEFLATE_REPLAY: u32 = 1 << 0;
26
27pub const FEATURE_EXTERNAL_OBJECTS: u32 = 1 << 1;
36
37pub const FEATURE_OBSERVATION_INDEX: u32 = 1 << 0;
44
45pub const FEATURE_SEEK_DIRECTORY: u32 = 1 << 1;
53
54pub const FEATURE_CHECKPOINTS: u32 = 1 << 2;
62
63#[cfg(all(feature = "deflate-replay", feature = "store"))]
70pub const SUPPORTED_MANDATORY_FEATURES: u32 = FEATURE_DEFLATE_REPLAY | FEATURE_EXTERNAL_OBJECTS;
71#[cfg(all(feature = "deflate-replay", not(feature = "store")))]
73pub const SUPPORTED_MANDATORY_FEATURES: u32 = FEATURE_DEFLATE_REPLAY;
74#[cfg(all(not(feature = "deflate-replay"), feature = "store"))]
76pub const SUPPORTED_MANDATORY_FEATURES: u32 = FEATURE_EXTERNAL_OBJECTS;
77#[cfg(all(not(feature = "deflate-replay"), not(feature = "store")))]
79pub const SUPPORTED_MANDATORY_FEATURES: u32 = 0;
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct Header {
84 pub major: u16,
86 pub minor: u16,
88 pub mandatory_features: u32,
90 pub optional_features: u32,
92 pub exactness_profile: u8,
94 pub source_format: u8,
96 pub universe_id: [u8; 16],
98 pub declared_source_len: u64,
100}
101
102impl Header {
103 pub fn new(
105 universe_id: [u8; 16],
106 declared_source_len: u64,
107 exactness_profile: u8,
108 source_format: u8,
109 ) -> Self {
110 Header {
111 major: FORMAT_MAJOR,
112 minor: FORMAT_MINOR,
113 mandatory_features: 0,
114 optional_features: 0,
115 exactness_profile,
116 source_format,
117 universe_id,
118 declared_source_len,
119 }
120 }
121
122 pub fn encode(&self) -> [u8; HEADER_LEN] {
124 let mut b = [0u8; HEADER_LEN];
125 b[0..8].copy_from_slice(&MAGIC);
126 b[8..10].copy_from_slice(&self.major.to_le_bytes());
127 b[10..12].copy_from_slice(&self.minor.to_le_bytes());
128 b[12..16].copy_from_slice(&self.mandatory_features.to_le_bytes());
129 b[16..20].copy_from_slice(&self.optional_features.to_le_bytes());
130 b[20] = self.exactness_profile;
131 b[21] = self.source_format;
132 b[24..40].copy_from_slice(&self.universe_id);
134 b[40..48].copy_from_slice(&self.declared_source_len.to_le_bytes());
135 let crc = crc32c(&b[0..60]);
137 b[60..64].copy_from_slice(&crc.to_le_bytes());
138 b
139 }
140
141 pub fn decode(bytes: &[u8]) -> Result<Header> {
143 if bytes.len() < HEADER_LEN {
144 return Err(Error::invalid_container(format!(
145 "input is {} bytes; need at least {HEADER_LEN} for a header",
146 bytes.len()
147 )));
148 }
149 let b = &bytes[0..HEADER_LEN];
150 if b[0..8] != MAGIC {
151 return Err(Error::invalid_container("bad container magic"));
152 }
153 let want_crc = u32::from_le_bytes([b[60], b[61], b[62], b[63]]);
154 let got_crc = crc32c(&b[0..60]);
155 if want_crc != got_crc {
156 return Err(Error::invalid_container(format!(
157 "header CRC32C mismatch: declared {want_crc:#010x}, computed {got_crc:#010x}"
158 )));
159 }
160 if b[22] != 0 || b[23] != 0 {
161 return Err(Error::invalid_container("header reserved_a must be zero"));
162 }
163 if b[48..60].iter().any(|&x| x != 0) {
164 return Err(Error::invalid_container("header reserved_b must be zero"));
165 }
166
167 let major = u16::from_le_bytes([b[8], b[9]]);
168 let minor = u16::from_le_bytes([b[10], b[11]]);
169 if major != FORMAT_MAJOR || minor > FORMAT_MINOR {
170 return Err(Error::unsupported_version(format!(
171 "container version {major}.{minor} is not supported by this build ({FORMAT_MAJOR}.{FORMAT_MINOR})"
172 )));
173 }
174
175 let mandatory_features = u32::from_le_bytes([b[12], b[13], b[14], b[15]]);
176 let unsupported = mandatory_features & !SUPPORTED_MANDATORY_FEATURES;
177 if unsupported != 0 {
178 return Err(Error::unsupported_feature(format!(
179 "descriptor requires unsupported mandatory feature bits {unsupported:#010x}"
180 )));
181 }
182
183 let exactness_profile = b[20];
184 if exactness_profile != EXACTNESS_PROFILE_EXACT_BYTES {
185 return Err(Error::unsupported_feature(format!(
186 "exactness profile {exactness_profile} is not the normative EXACT_BYTES profile"
187 )));
188 }
189 let source_format = b[21];
190
191 let mut universe_id = [0u8; 16];
192 universe_id.copy_from_slice(&b[24..40]);
193 let declared_source_len =
194 u64::from_le_bytes([b[40], b[41], b[42], b[43], b[44], b[45], b[46], b[47]]);
195
196 Ok(Header {
197 major,
198 minor,
199 mandatory_features,
200 optional_features: u32::from_le_bytes([b[16], b[17], b[18], b[19]]),
201 exactness_profile,
202 source_format,
203 universe_id,
204 declared_source_len,
205 })
206 }
207
208 pub fn is_opaque(&self) -> bool {
210 self.source_format == SOURCE_FORMAT_OPAQUE
211 }
212
213 pub fn source_format_supported(&self) -> bool {
218 matches!(self.source_format, 0 | 1)
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn roundtrip() {
228 let h = Header::new(
229 [7u8; 16],
230 1234,
231 EXACTNESS_PROFILE_EXACT_BYTES,
232 SOURCE_FORMAT_OPAQUE,
233 );
234 let enc = h.encode();
235 assert_eq!(enc.len(), HEADER_LEN);
236 let dec = Header::decode(&enc).unwrap();
237 assert_eq!(h, dec);
238 }
239
240 #[test]
241 fn rejects_bad_magic() {
242 let mut enc = Header::new([0u8; 16], 0, 0, 0).encode();
243 enc[0] = b'X';
244 match Header::decode(&enc) {
245 Err(e) => assert_eq!(e.class(), crate::ErrorClass::InvalidContainer),
246 Ok(_) => panic!("expected failure"),
247 }
248 }
249
250 #[test]
251 fn detects_corruption_via_crc() {
252 let mut enc = Header::new([0u8; 16], 5, 0, 0).encode();
253 enc[41] ^= 0x01; let e = Header::decode(&enc).unwrap_err();
255 assert_eq!(e.class(), crate::ErrorClass::InvalidContainer);
256 }
257
258 #[test]
259 fn rejects_truncated() {
260 let e = Header::decode(&[0u8; 10]).unwrap_err();
261 assert_eq!(e.class(), crate::ErrorClass::InvalidContainer);
262 }
263
264 #[test]
265 fn rejects_unknown_mandatory_feature() {
266 let mut h = Header::new([0u8; 16], 0, 0, 0);
267 h.mandatory_features = 0x8000_0000;
268 let e = Header::decode(&h.encode()).unwrap_err();
269 assert_eq!(e.class(), crate::ErrorClass::UnsupportedFeature);
270 }
271
272 #[test]
273 fn rejects_future_major_version() {
274 let mut h = Header::new([0u8; 16], 0, 0, 0);
275 h.major = 9;
276 let e = Header::decode(&h.encode()).unwrap_err();
277 assert_eq!(e.class(), crate::ErrorClass::UnsupportedVersion);
278 }
279}