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