use iris_abi::{Reader, Tag};
use crate::digest::Digest;
use crate::error::{Error, Result};
use crate::layout::{
DecoderLocation, FORMAT_MAJOR, HEADER_SIZE, MAGIC, MIN_SIZE, TRAILER_SIZE, tag,
};
use crate::meta::{Dataset, DecoderRef, Schema, Section};
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct FileHeader {
pub major: u16,
pub minor: u16,
}
#[derive(Clone, Debug)]
pub struct Container<'a> {
bytes: &'a [u8],
header: FileHeader,
footer_range: (usize, usize),
root: Digest,
dataset: Dataset,
schema: Option<Schema<'a>>,
decoder: Option<DecoderRef<'a>>,
sections: Vec<Section>,
}
impl<'a> Container<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self> {
let container = Self::parse_without_root_digest(bytes)?;
let actual = container.compute_root();
if actual != container.root {
return Err(Error::DigestMismatch {
what: "footer".to_owned(),
expected: container.root.to_string(),
actual: actual.to_string(),
});
}
Ok(container)
}
pub fn parse_without_root_digest(bytes: &'a [u8]) -> Result<Self> {
if bytes.len() < MIN_SIZE {
let head = &bytes[..bytes.len().min(MAGIC.len())];
if !MAGIC.starts_with(head) {
return Err(Error::NotAContainer {
found: head.to_vec(),
expected: MAGIC.to_vec(),
});
}
return Err(Error::Truncated {
what: "the container",
needed: MIN_SIZE as u64,
available: bytes.len() as u64,
});
}
if bytes[..MAGIC.len()] != MAGIC {
return Err(Error::NotAContainer {
found: bytes[..MAGIC.len()].to_vec(),
expected: MAGIC.to_vec(),
});
}
let header = Self::parse_header(bytes)?;
let (footer_offset, footer_len, root) = Self::parse_trailer(bytes)?;
let trailer_at = bytes.len() - TRAILER_SIZE;
let footer_end = footer_offset
.checked_add(footer_len)
.ok_or(Error::Truncated {
what: "the footer",
needed: u64::MAX,
available: bytes.len() as u64,
})?;
if footer_offset < HEADER_SIZE as u64 || footer_end > trailer_at as u64 {
return Err(Error::Truncated {
what: "the footer",
needed: footer_end,
available: trailer_at as u64,
});
}
let start = usize::try_from(footer_offset).map_err(|_| Error::TooLarge {
what: "the footer offset",
needed: footer_offset,
})?;
let end = usize::try_from(footer_end).map_err(|_| Error::TooLarge {
what: "the footer",
needed: footer_end,
})?;
let mut container = Self {
bytes,
header,
footer_range: (start, end),
root,
dataset: Dataset {
rows: 0,
name: String::new(),
},
schema: None,
decoder: None,
sections: Vec::new(),
};
container.parse_footer(&bytes[start..end])?;
container.check_sections(footer_offset)?;
Ok(container)
}
fn parse_header(bytes: &[u8]) -> Result<FileHeader> {
let major = u16::from_le_bytes([bytes[8], bytes[9]]);
let minor = u16::from_le_bytes([bytes[10], bytes[11]]);
if major != FORMAT_MAJOR {
return Err(Error::UnsupportedFormat {
major,
minor,
supported_major: FORMAT_MAJOR,
});
}
let flags = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
if flags != 0 {
return Err(Error::Reserved { what: "header" });
}
Ok(FileHeader { major, minor })
}
fn parse_trailer(bytes: &[u8]) -> Result<(u64, u64, Digest)> {
let at = bytes.len() - TRAILER_SIZE;
let t = &bytes[at..];
if t[48..56] != MAGIC {
return Err(Error::Truncated {
what: "the container, which does not end with the magic",
needed: bytes.len() as u64,
available: bytes.len() as u64,
});
}
let footer_offset = u64::from_le_bytes(t[0..8].try_into().expect("eight bytes"));
let footer_len = u64::from(u32::from_le_bytes(t[8..12].try_into().expect("four bytes")));
let reserved = u32::from_le_bytes(t[12..16].try_into().expect("four bytes"));
if reserved != 0 {
return Err(Error::Reserved { what: "trailer" });
}
let mut root = [0u8; 32];
root.copy_from_slice(&t[16..48]);
Ok((footer_offset, footer_len, Digest(root)))
}
fn parse_footer(&mut self, footer: &'a [u8]) -> Result<()> {
let mut seen_dataset = false;
let mut p = Reader::new(footer);
while !p.is_empty() {
let (header, mut body) = p.record()?;
match header.tag {
tag::DATASET => {
Self::expect_version(header.tag, header.version, Dataset::VERSION)?;
if seen_dataset {
return Err(Error::RepeatedRecord(tag::DATASET));
}
self.dataset = Dataset::decode(&mut body)?;
seen_dataset = true;
}
tag::SCHEMA => {
Self::expect_version(header.tag, header.version, Schema::VERSION)?;
if self.schema.is_some() {
return Err(Error::RepeatedRecord(tag::SCHEMA));
}
self.schema = Some(Schema::decode(&mut body)?);
}
tag::DECODER => {
Self::expect_version(header.tag, header.version, DecoderRef::VERSION)?;
if self.decoder.is_some() {
return Err(Error::RepeatedRecord(tag::DECODER));
}
self.decoder = Some(DecoderRef::decode(&mut body)?);
}
tag::SECTION => {
Self::expect_version(header.tag, header.version, Section::VERSION)?;
self.sections.push(Section::decode(&mut body)?);
}
_ => {}
}
}
if !seen_dataset {
return Err(Error::MissingRecord(tag::DATASET));
}
Ok(())
}
fn expect_version(tag: Tag, found: u16, supported: u16) -> Result<()> {
if found > supported {
return Err(Error::UnsupportedRecord {
tag,
version: found,
});
}
Ok(())
}
fn check_sections(&self, footer_offset: u64) -> Result<()> {
for (i, section) in self.sections.iter().enumerate() {
let end = section.end().ok_or(Error::SectionOutOfBounds {
id: section.id,
offset: section.offset,
end: u64::MAX,
file_len: self.bytes.len() as u64,
})?;
if section.offset < HEADER_SIZE as u64 || end > footer_offset {
return Err(Error::SectionOutOfBounds {
id: section.id,
offset: section.offset,
end,
file_len: self.bytes.len() as u64,
});
}
if self.sections[..i].iter().any(|s| s.id == section.id) {
return Err(Error::DuplicateSection { id: section.id });
}
}
Ok(())
}
fn compute_root(&self) -> Digest {
let mut hasher = blake3::Hasher::new();
hasher.update(&self.bytes[..HEADER_SIZE]);
hasher.update(&self.bytes[self.footer_range.0..self.footer_range.1]);
Digest(*hasher.finalize().as_bytes())
}
#[must_use]
pub const fn header(&self) -> FileHeader {
self.header
}
#[must_use]
pub const fn root_digest(&self) -> Digest {
self.root
}
#[must_use]
pub const fn dataset(&self) -> &Dataset {
&self.dataset
}
#[must_use]
pub const fn schema(&self) -> Option<&Schema<'a>> {
self.schema.as_ref()
}
#[must_use]
pub const fn decoder(&self) -> Option<&DecoderRef<'a>> {
self.decoder.as_ref()
}
#[must_use]
pub fn sections(&self) -> &[Section] {
&self.sections
}
#[must_use]
pub fn section(&self, id: u32) -> Option<&Section> {
self.sections.iter().find(|s| s.id == id)
}
#[must_use]
pub fn section_bytes(&self, section: &Section) -> &'a [u8] {
let start = usize::try_from(section.offset).unwrap_or(usize::MAX);
let end = usize::try_from(section.end().unwrap_or(u64::MAX)).unwrap_or(usize::MAX);
self.bytes.get(start..end).unwrap_or(&[])
}
#[must_use]
pub fn decoder_bytes(&self) -> Option<&'a [u8]> {
let decoder = self.decoder.as_ref()?;
let DecoderLocation::Embedded { section } = decoder.location else {
return None;
};
self.section(section).map(|s| self.section_bytes(s))
}
pub fn verify(&self) -> Result<()> {
for section in &self.sections {
let actual = Digest::of(self.section_bytes(section));
if actual != section.digest {
return Err(Error::DigestMismatch {
what: format!("section {}", section.id),
expected: section.digest.to_string(),
actual: actual.to_string(),
});
}
}
Ok(())
}
}