use std::io::{Read, Seek, SeekFrom};
use std::ops::Range;
use crate::descriptor::{DESCRIPTOR_LEN, GroupDescriptor};
use crate::error::ParseError;
use crate::header::{HEADER_LEN, Header};
use crate::layout::{Layout, TileLoc};
#[derive(Debug, Clone)]
pub struct FrontMatter {
pub header: Header,
pub layout: Layout,
offsets: Vec<u64>,
}
pub fn required_len(prefix: &[u8]) -> Result<usize, ParseError> {
if prefix.len() < HEADER_LEN {
return Ok(HEADER_LEN);
}
let header = Header::parse(prefix)?;
let descriptors_end = header.descriptors_end();
if prefix.len() < descriptors_end {
return Ok(descriptors_end);
}
let groups = parse_descriptors(header, prefix)?;
let layout = Layout::new(header, groups)?;
front_matter_len_usize(&layout)
}
impl FrontMatter {
pub fn parse(prefix: &[u8]) -> Result<FrontMatter, ParseError> {
let header = Header::parse(prefix)?;
let descriptors_end = header.descriptors_end();
if prefix.len() < descriptors_end {
return Err(ParseError::Truncated { needed: descriptors_end });
}
let groups = parse_descriptors(header, prefix)?;
let layout = Layout::new(header, groups)?;
let fm_len = front_matter_len_usize(&layout)?;
if prefix.len() < fm_len {
return Err(ParseError::Truncated { needed: fm_len });
}
let count = layout.total_tiles() as usize + 1;
let mut offsets = Vec::with_capacity(count);
let mut pos = descriptors_end;
for _ in 0..count {
let bytes: [u8; 8] = prefix[pos..pos + 8].try_into().unwrap();
offsets.push(u64::from_le_bytes(bytes));
pos += 8;
}
if offsets[0] != fm_len as u64 {
return Err(ParseError::BadIndex("first offset does not equal front-matter length"));
}
for w in offsets.windows(2) {
if w[1] < w[0] {
return Err(ParseError::BadIndex("offsets are not non-decreasing"));
}
}
Ok(FrontMatter { header, layout, offsets })
}
pub fn file_len(&self) -> u64 {
*self.offsets.last().unwrap()
}
pub fn tile_range(&self, loc: TileLoc) -> Option<Range<u64>> {
let ordinal = self.layout.tile_ordinal(loc)?;
self.ordinal_range(ordinal)
}
pub fn ordinal_range(&self, ordinal: usize) -> Option<Range<u64>> {
let start = self.offsets[ordinal];
let end = self.offsets[ordinal + 1];
if start == end { None } else { Some(start..end) }
}
pub fn level_range(&self, g: usize, level: u8) -> Option<Range<u64>> {
let run = self.layout.level_run(g, level)?;
Some(self.offsets[run.start]..self.offsets[run.end])
}
pub fn has_tile(&self, loc: TileLoc) -> bool {
self.tile_range(loc).is_some()
}
pub fn offsets(&self) -> &[u64] {
&self.offsets
}
}
#[derive(Debug, Clone)]
pub struct Prelude {
pub header: Header,
pub layout: Layout,
}
impl Prelude {
pub fn parse(prefix: &[u8]) -> Result<Prelude, ParseError> {
let header = Header::parse(prefix)?;
let descriptors_end = header.descriptors_end();
if prefix.len() < descriptors_end {
return Err(ParseError::Truncated { needed: descriptors_end });
}
let groups = parse_descriptors(header, prefix)?;
let layout = Layout::new(header, groups)?;
Ok(Prelude { header, layout })
}
pub fn front_matter_len(&self) -> u64 {
self.layout.front_matter_len()
}
pub fn index_entry_span(&self, ordinal: usize) -> Option<Range<u64>> {
if (ordinal as u64) >= self.layout.total_tiles() {
return None;
}
let at = self.header.descriptors_end() as u64 + ordinal as u64 * 8;
Some(at..at + 16)
}
pub fn index_entry_span_for(&self, loc: TileLoc) -> Option<Range<u64>> {
self.index_entry_span(self.layout.tile_ordinal(loc)?)
}
pub fn tile_range_from_entry(&self, entry: &[u8; 16], file_len: Option<u64>) -> Result<Option<Range<u64>>, ParseError> {
let start = u64::from_le_bytes(entry[..8].try_into().unwrap());
let end = u64::from_le_bytes(entry[8..].try_into().unwrap());
if end < start {
return Err(ParseError::BadIndex("entry runs backwards"));
}
if start < self.front_matter_len() {
return Err(ParseError::BadIndex("entry starts inside the front matter"));
}
if let Some(len) = file_len
&& end > len
{
return Err(ParseError::BadIndex("entry runs past the end of the file"));
}
Ok(if start == end { None } else { Some(start..end) })
}
}
pub struct SparseReader<R: Read + Seek> {
prelude: Prelude,
len: u64,
src: R,
}
impl<R: Read + Seek> SparseReader<R> {
pub fn new(mut src: R) -> Result<SparseReader<R>, ParseError> {
let len = src
.seek(SeekFrom::End(0))
.map_err(|_| ParseError::Truncated { needed: HEADER_LEN })?;
let mut buf = vec![0u8; HEADER_LEN];
read_exact_at(&mut src, 0, &mut buf)?;
loop {
match Prelude::parse(&buf) {
Ok(prelude) => return Ok(SparseReader { prelude, len, src }),
Err(ParseError::Truncated { needed }) => {
buf.resize(needed, 0);
read_exact_at(&mut src, 0, &mut buf)?;
}
Err(e) => return Err(e),
}
}
}
pub fn prelude(&self) -> &Prelude {
&self.prelude
}
pub fn read_tile(&mut self, loc: TileLoc) -> Result<Option<Vec<u8>>, ParseError> {
let Some(ordinal) = self.prelude.layout.tile_ordinal(loc) else {
return Ok(None);
};
self.read_tile_by_ordinal(ordinal)
}
pub fn read_tile_by_ordinal(&mut self, ordinal: usize) -> Result<Option<Vec<u8>>, ParseError> {
let Some(span) = self.prelude.index_entry_span(ordinal) else {
return Ok(None);
};
let mut entry = [0u8; 16];
read_exact_at(&mut self.src, span.start, &mut entry)?;
let Some(range) = self.prelude.tile_range_from_entry(&entry, Some(self.len))? else {
return Ok(None);
};
let mut buf = vec![0u8; (range.end - range.start) as usize];
read_exact_at(&mut self.src, range.start, &mut buf)?;
Ok(Some(buf))
}
}
#[derive(Debug, Clone)]
pub struct TilepackView<'a> {
pub fm: FrontMatter,
data: &'a [u8],
}
impl<'a> TilepackView<'a> {
pub fn new(data: &'a [u8]) -> Result<TilepackView<'a>, ParseError> {
let fm = FrontMatter::parse(data)?;
if (data.len() as u64) < fm.file_len() {
return Err(ParseError::Truncated {
needed: fm.file_len() as usize,
});
}
Ok(TilepackView { fm, data })
}
pub fn tile(&self, loc: TileLoc) -> Option<&'a [u8]> {
let r = self.fm.tile_range(loc)?;
Some(&self.data[r.start as usize..r.end as usize])
}
pub fn tile_by_ordinal(&self, ordinal: usize) -> Option<&'a [u8]> {
let r = self.fm.ordinal_range(ordinal)?;
Some(&self.data[r.start as usize..r.end as usize])
}
}
pub struct TilepackReader<R: Read + Seek> {
pub fm: FrontMatter,
src: R,
}
impl<R: Read + Seek> TilepackReader<R> {
pub fn new(mut src: R) -> Result<TilepackReader<R>, ParseError> {
let mut buf = vec![0u8; HEADER_LEN];
read_exact_at(&mut src, 0, &mut buf)?;
loop {
match FrontMatter::parse(&buf) {
Ok(fm) => return Ok(TilepackReader { fm, src }),
Err(ParseError::Truncated { needed }) => {
buf.resize(needed, 0);
read_exact_at(&mut src, 0, &mut buf)?;
}
Err(e) => return Err(e),
}
}
}
pub fn read_tile(&mut self, loc: TileLoc) -> Result<Option<Vec<u8>>, ParseError> {
let Some(r) = self.fm.tile_range(loc) else {
return Ok(None);
};
let mut buf = vec![0u8; (r.end - r.start) as usize];
read_exact_at(&mut self.src, r.start, &mut buf)?;
Ok(Some(buf))
}
}
fn read_exact_at<R: Read + Seek>(src: &mut R, at: u64, buf: &mut [u8]) -> Result<(), ParseError> {
src.seek(SeekFrom::Start(at)).map_err(|_| ParseError::Truncated {
needed: at as usize + buf.len(),
})?;
src.read_exact(buf).map_err(|_| ParseError::Truncated {
needed: at as usize + buf.len(),
})?;
Ok(())
}
fn parse_descriptors(header: Header, prefix: &[u8]) -> Result<Vec<GroupDescriptor>, ParseError> {
let mut groups = Vec::with_capacity(header.group_count as usize);
let mut pos = HEADER_LEN;
for _ in 0..header.group_count {
groups.push(GroupDescriptor::parse(&prefix[pos..pos + DESCRIPTOR_LEN])?);
pos += DESCRIPTOR_LEN;
}
Ok(groups)
}
fn front_matter_len_usize(layout: &Layout) -> Result<usize, ParseError> {
let fm_len = layout.front_matter_len();
usize::try_from(fm_len).map_err(|_| ParseError::Inconsistent("front matter length exceeds address space"))
}