use crate::digest::Digest;
use crate::directory::{Directory, Placement};
use crate::error::{Error, Result};
use crate::layout::{HEADER_SIZE, MAGIC, MIN_SIZE};
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],
directory: Directory<'a>,
}
impl<'a> Container<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self> {
Self::open(bytes, Directory::parse)
}
pub fn parse_without_root_digest(bytes: &'a [u8]) -> Result<Self> {
Self::open(bytes, Directory::parse_without_root_digest)
}
fn open(
bytes: &'a [u8],
parse: fn(&[u8], &'a [u8], Placement) -> Result<Directory<'a>>,
) -> 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,
});
}
let file_len = bytes.len() as u64;
let trailer_at =
usize::try_from(Placement::trailer_at(file_len)?).map_err(|_| Error::TooLarge {
what: "the trailer offset",
needed: file_len,
})?;
let placement = Placement::read(&bytes[trailer_at..], file_len)?;
let start = usize::try_from(placement.footer_at()).map_err(|_| Error::TooLarge {
what: "the footer offset",
needed: placement.footer_at(),
})?;
let end = start
.checked_add(placement.footer_len())
.ok_or(Error::TooLarge {
what: "the footer",
needed: u64::MAX,
})?;
let footer = bytes.get(start..end).ok_or(Error::Truncated {
what: "the footer",
needed: end as u64,
available: file_len,
})?;
Ok(Self {
bytes,
directory: parse(&bytes[..HEADER_SIZE], footer, placement)?,
})
}
#[must_use]
pub const fn directory(&self) -> &Directory<'a> {
&self.directory
}
#[must_use]
pub const fn header(&self) -> FileHeader {
self.directory.header()
}
#[must_use]
pub const fn root_digest(&self) -> Digest {
self.directory.root_digest()
}
#[must_use]
pub const fn dataset(&self) -> &Dataset {
self.directory.dataset()
}
#[must_use]
pub const fn schema(&self) -> Option<&Schema<'a>> {
self.directory.schema()
}
#[must_use]
pub const fn decoder(&self) -> Option<&DecoderRef<'a>> {
self.directory.decoder()
}
#[must_use]
pub fn sections(&self) -> &[Section] {
self.directory.sections()
}
#[must_use]
pub fn section(&self, id: u32) -> Option<&Section> {
self.directory.section(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]> {
self.directory
.decoder_section()
.map(|section| self.section_bytes(section))
}
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(())
}
}