1use iris_abi::{Reader, Tag};
18
19use crate::digest::Digest;
20use crate::error::{Error, Result};
21use crate::layout::{
22 DecoderLocation, FORMAT_MAJOR, HEADER_SIZE, MAGIC, MIN_SIZE, TRAILER_SIZE, tag,
23};
24use crate::meta::{Dataset, DecoderRef, Schema, Section};
25
26#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
28pub struct FileHeader {
29 pub major: u16,
31 pub minor: u16,
33}
34
35#[derive(Clone, Debug)]
41pub struct Container<'a> {
42 bytes: &'a [u8],
43 header: FileHeader,
44 footer_range: (usize, usize),
45 root: Digest,
46 dataset: Dataset,
47 schema: Option<Schema<'a>>,
48 decoder: Option<DecoderRef<'a>>,
49 sections: Vec<Section>,
50}
51
52impl<'a> Container<'a> {
53 pub fn parse(bytes: &'a [u8]) -> Result<Self> {
64 let container = Self::parse_without_root_digest(bytes)?;
65 let actual = container.compute_root();
66 if actual != container.root {
67 return Err(Error::DigestMismatch {
68 what: "footer".to_owned(),
69 expected: container.root.to_string(),
70 actual: actual.to_string(),
71 });
72 }
73 Ok(container)
74 }
75
76 pub fn parse_without_root_digest(bytes: &'a [u8]) -> Result<Self> {
87 if bytes.len() < MIN_SIZE {
88 let head = &bytes[..bytes.len().min(MAGIC.len())];
89 if !MAGIC.starts_with(head) {
90 return Err(Error::NotAContainer {
91 found: head.to_vec(),
92 expected: MAGIC.to_vec(),
93 });
94 }
95 return Err(Error::Truncated {
96 what: "the container",
97 needed: MIN_SIZE as u64,
98 available: bytes.len() as u64,
99 });
100 }
101
102 if bytes[..MAGIC.len()] != MAGIC {
103 return Err(Error::NotAContainer {
104 found: bytes[..MAGIC.len()].to_vec(),
105 expected: MAGIC.to_vec(),
106 });
107 }
108
109 let header = Self::parse_header(bytes)?;
110 let (footer_offset, footer_len, root) = Self::parse_trailer(bytes)?;
111
112 let trailer_at = bytes.len() - TRAILER_SIZE;
116 let footer_end = footer_offset
117 .checked_add(footer_len)
118 .ok_or(Error::Truncated {
119 what: "the footer",
120 needed: u64::MAX,
121 available: bytes.len() as u64,
122 })?;
123 if footer_offset < HEADER_SIZE as u64 || footer_end > trailer_at as u64 {
124 return Err(Error::Truncated {
125 what: "the footer",
126 needed: footer_end,
127 available: trailer_at as u64,
128 });
129 }
130 let start = usize::try_from(footer_offset).map_err(|_| Error::TooLarge {
131 what: "the footer offset",
132 needed: footer_offset,
133 })?;
134 let end = usize::try_from(footer_end).map_err(|_| Error::TooLarge {
135 what: "the footer",
136 needed: footer_end,
137 })?;
138
139 let mut container = Self {
140 bytes,
141 header,
142 footer_range: (start, end),
143 root,
144 dataset: Dataset {
145 rows: 0,
146 name: String::new(),
147 },
148 schema: None,
149 decoder: None,
150 sections: Vec::new(),
151 };
152 container.parse_footer(&bytes[start..end])?;
153 container.check_sections(footer_offset)?;
154 Ok(container)
155 }
156
157 fn parse_header(bytes: &[u8]) -> Result<FileHeader> {
158 let major = u16::from_le_bytes([bytes[8], bytes[9]]);
159 let minor = u16::from_le_bytes([bytes[10], bytes[11]]);
160 if major != FORMAT_MAJOR {
161 return Err(Error::UnsupportedFormat {
162 major,
163 minor,
164 supported_major: FORMAT_MAJOR,
165 });
166 }
167 let flags = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
168 if flags != 0 {
169 return Err(Error::Reserved { what: "header" });
170 }
171 Ok(FileHeader { major, minor })
172 }
173
174 fn parse_trailer(bytes: &[u8]) -> Result<(u64, u64, Digest)> {
175 let at = bytes.len() - TRAILER_SIZE;
176 let t = &bytes[at..];
177 if t[48..56] != MAGIC {
178 return Err(Error::Truncated {
179 what: "the container, which does not end with the magic",
180 needed: bytes.len() as u64,
181 available: bytes.len() as u64,
182 });
183 }
184 let footer_offset = u64::from_le_bytes(t[0..8].try_into().expect("eight bytes"));
185 let footer_len = u64::from(u32::from_le_bytes(t[8..12].try_into().expect("four bytes")));
186 let reserved = u32::from_le_bytes(t[12..16].try_into().expect("four bytes"));
187 if reserved != 0 {
188 return Err(Error::Reserved { what: "trailer" });
189 }
190 let mut root = [0u8; 32];
191 root.copy_from_slice(&t[16..48]);
192 Ok((footer_offset, footer_len, Digest(root)))
193 }
194
195 fn parse_footer(&mut self, footer: &'a [u8]) -> Result<()> {
196 let mut seen_dataset = false;
197 let mut p = Reader::new(footer);
198 while !p.is_empty() {
199 let (header, mut body) = p.record()?;
200 match header.tag {
201 tag::DATASET => {
202 Self::expect_version(header.tag, header.version, Dataset::VERSION)?;
203 if seen_dataset {
204 return Err(Error::RepeatedRecord(tag::DATASET));
205 }
206 self.dataset = Dataset::decode(&mut body)?;
207 seen_dataset = true;
208 }
209 tag::SCHEMA => {
210 Self::expect_version(header.tag, header.version, Schema::VERSION)?;
211 if self.schema.is_some() {
212 return Err(Error::RepeatedRecord(tag::SCHEMA));
213 }
214 self.schema = Some(Schema::decode(&mut body)?);
215 }
216 tag::DECODER => {
217 Self::expect_version(header.tag, header.version, DecoderRef::VERSION)?;
218 if self.decoder.is_some() {
219 return Err(Error::RepeatedRecord(tag::DECODER));
220 }
221 self.decoder = Some(DecoderRef::decode(&mut body)?);
222 }
223 tag::SECTION => {
224 Self::expect_version(header.tag, header.version, Section::VERSION)?;
225 self.sections.push(Section::decode(&mut body)?);
226 }
227 _ => {}
231 }
232 }
233 if !seen_dataset {
234 return Err(Error::MissingRecord(tag::DATASET));
235 }
236 Ok(())
237 }
238
239 fn expect_version(tag: Tag, found: u16, supported: u16) -> Result<()> {
240 if found > supported {
241 return Err(Error::UnsupportedRecord {
242 tag,
243 version: found,
244 });
245 }
246 Ok(())
247 }
248
249 fn check_sections(&self, footer_offset: u64) -> Result<()> {
250 for (i, section) in self.sections.iter().enumerate() {
251 let end = section.end().ok_or(Error::SectionOutOfBounds {
252 id: section.id,
253 offset: section.offset,
254 end: u64::MAX,
255 file_len: self.bytes.len() as u64,
256 })?;
257 if section.offset < HEADER_SIZE as u64 || end > footer_offset {
258 return Err(Error::SectionOutOfBounds {
259 id: section.id,
260 offset: section.offset,
261 end,
262 file_len: self.bytes.len() as u64,
263 });
264 }
265 if self.sections[..i].iter().any(|s| s.id == section.id) {
266 return Err(Error::DuplicateSection { id: section.id });
267 }
268 }
269 Ok(())
270 }
271
272 fn compute_root(&self) -> Digest {
273 let mut hasher = blake3::Hasher::new();
274 hasher.update(&self.bytes[..HEADER_SIZE]);
275 hasher.update(&self.bytes[self.footer_range.0..self.footer_range.1]);
276 Digest(*hasher.finalize().as_bytes())
277 }
278
279 #[must_use]
281 pub const fn header(&self) -> FileHeader {
282 self.header
283 }
284
285 #[must_use]
287 pub const fn root_digest(&self) -> Digest {
288 self.root
289 }
290
291 #[must_use]
293 pub const fn dataset(&self) -> &Dataset {
294 &self.dataset
295 }
296
297 #[must_use]
299 pub const fn schema(&self) -> Option<&Schema<'a>> {
300 self.schema.as_ref()
301 }
302
303 #[must_use]
305 pub const fn decoder(&self) -> Option<&DecoderRef<'a>> {
306 self.decoder.as_ref()
307 }
308
309 #[must_use]
311 pub fn sections(&self) -> &[Section] {
312 &self.sections
313 }
314
315 #[must_use]
317 pub fn section(&self, id: u32) -> Option<&Section> {
318 self.sections.iter().find(|s| s.id == id)
319 }
320
321 #[must_use]
327 pub fn section_bytes(&self, section: &Section) -> &'a [u8] {
328 let start = usize::try_from(section.offset).unwrap_or(usize::MAX);
329 let end = usize::try_from(section.end().unwrap_or(u64::MAX)).unwrap_or(usize::MAX);
330 self.bytes.get(start..end).unwrap_or(&[])
331 }
332
333 #[must_use]
336 pub fn decoder_bytes(&self) -> Option<&'a [u8]> {
337 let decoder = self.decoder.as_ref()?;
338 let DecoderLocation::Embedded { section } = decoder.location else {
339 return None;
340 };
341 self.section(section).map(|s| self.section_bytes(s))
342 }
343
344 pub fn verify(&self) -> Result<()> {
354 for section in &self.sections {
355 let actual = Digest::of(self.section_bytes(section));
356 if actual != section.digest {
357 return Err(Error::DigestMismatch {
358 what: format!("section {}", section.id),
359 expected: section.digest.to_string(),
360 actual: actual.to_string(),
361 });
362 }
363 }
364 Ok(())
365 }
366}