use std::borrow::Cow;
use std::ops::Range;
use crate::io::{ByteSource, Reader, Window};
use crate::{Error, Result};
const HEADER_LEN: u64 = 16;
const CHUNK_HEADER_LEN: u64 = 8;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Narc {
members: Vec<Range<u64>>,
}
impl Narc {
pub fn parse<S: ByteSource + ?Sized>(source: &S) -> Result<Self> {
let head = source.read_up_to(0, HEADER_LEN + CHUNK_HEADER_LEN + 4)?;
let mut header = Reader::new("NARC", &head);
header.expect(b"NARC")?;
header.seek(HEADER_LEN as usize)?;
header.expect(b"BTAF")?;
let fat_len = header.u32()? as u64;
let count = header.u16()? as u64;
let entries = HEADER_LEN + CHUNK_HEADER_LEN + 4;
if entries + count * 8 > HEADER_LEN + fat_len {
return Err(header.error_at(entries as usize, "member table overruns its chunk"));
}
let fnt_at = HEADER_LEN + fat_len;
let image_at = fnt_at + chunk_len(source, fnt_at, b"BTNF")?;
let image = image_at + CHUNK_HEADER_LEN..image_at + chunk_len(source, image_at, b"GMIF")?;
let table = source.read(entries, count * 8)?;
let mut table = Reader::new("NARC", &table);
let members = (0..count)
.map(|_| {
let (start, end) = (table.u32()? as u64, table.u32()? as u64);
let member = image.start + start..image.start + end;
match start <= end && member.end <= image.end {
true => Ok(member),
false => Err(Error::malformed(
"NARC",
entries,
"member lies outside the image",
)),
}
})
.collect::<Result<_>>()?;
Ok(Self { members })
}
pub fn len(&self) -> usize {
self.members.len()
}
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
pub fn range(&self, index: usize) -> Result<Range<u64>> {
self.members
.get(index)
.cloned()
.ok_or_else(|| Error::NotFound(format!("NARC member {index} of {}", self.len())))
}
pub fn member<'s, S: ByteSource + ?Sized>(
&self,
archive: &'s S,
index: usize,
) -> Result<Cow<'s, [u8]>> {
let range = self.range(index)?;
archive.read(range.start, range.end - range.start)
}
pub fn member_source<'s, S: ByteSource + ?Sized>(
&self,
archive: &'s S,
index: usize,
) -> Result<Window<&'s S>> {
let range = self.range(index)?;
Window::new(archive, range.start, range.end - range.start)
}
}
fn chunk_len<S: ByteSource + ?Sized>(source: &S, offset: u64, magic: &[u8; 4]) -> Result<u64> {
let header = source
.read(offset, CHUNK_HEADER_LEN)
.map_err(|_| Error::malformed("NARC", offset, "chunk lies outside the archive"))?;
let mut header = Reader::new("NARC", &header);
header
.expect(magic)
.map_err(|_| Error::malformed("NARC", offset, "unexpected chunk"))?;
let len = header.u32()? as u64;
match len >= CHUNK_HEADER_LEN && offset + len <= source.size() {
true => Ok(len),
false => Err(Error::malformed(
"NARC",
offset + 4,
"chunk length is out of range",
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::{Counting, narc};
#[test]
fn members_are_borrowed_from_memory() {
let bytes = narc(&[b"first", b"", b"third"]);
let archive = Narc::parse(bytes.as_slice()).unwrap();
assert_eq!(archive.len(), 3);
assert!(matches!(
archive.member(bytes.as_slice(), 0).unwrap(),
Cow::Borrowed(b"first")
));
assert_eq!(&*archive.member(bytes.as_slice(), 1).unwrap(), b"");
assert_eq!(&*archive.member(bytes.as_slice(), 2).unwrap(), b"third");
assert!(matches!(
archive.member(bytes.as_slice(), 3),
Err(Error::NotFound(_))
));
}
#[test]
fn parsing_reads_only_the_tables() {
let big = vec![7; 100_000];
let bytes = narc(&[&big, b"small"]);
let source = Counting::new(bytes.as_slice());
let archive = Narc::parse(&source).unwrap();
assert!(
source.bytes_read() < 100,
"read {} bytes",
source.bytes_read()
);
assert_eq!(
&*archive
.member_source(&source, 1)
.unwrap()
.read_all()
.unwrap(),
b"small"
);
}
#[test]
fn rejects_members_outside_the_image() {
let mut bytes = narc(&[b"abc"]);
bytes[32..36].copy_from_slice(&100u32.to_le_bytes()); assert!(Narc::parse(bytes.as_slice()).is_err());
}
}