mod traits;
mod types;
mod write;
pub(crate) use self::write::*;
pub use self::{traits::*, types::*};
use std::{
borrow::Cow,
io::{self, Read},
mem,
};
pub const MIN_CHUNK_BYTES_SIZE: usize =
mem::size_of::<u32>() + mem::size_of::<ChunkType>() + mem::size_of::<u32>();
pub(crate) const MAX_CHUNK_DATA_LENGTH: usize = u32::MAX as usize;
pub(crate) trait ChunkExt: Chunk {
#[inline]
fn bytes_len(&self) -> usize {
MIN_CHUNK_BYTES_SIZE + self.data().len()
}
}
impl<T> ChunkExt for T where T: Chunk {}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct RawChunk<D = Vec<u8>> {
pub(crate) length: u32,
pub(crate) ty: ChunkType,
pub(crate) data: D,
pub(crate) crc: u32,
}
impl<D> From<(ChunkType, D)> for RawChunk<D>
where
(ChunkType, D): Chunk,
{
#[inline]
fn from(value: (ChunkType, D)) -> Self {
Self {
length: value.length(),
crc: value.crc(),
ty: value.0,
data: value.1,
}
}
}
impl<'a> From<(ChunkType, Cow<'a, [u8]>)> for RawChunk<Vec<u8>> {
#[inline]
fn from(value: (ChunkType, Cow<'a, [u8]>)) -> Self {
RawChunk::<Cow<'a, [u8]>>::from(value).into()
}
}
impl<'a> From<(ChunkType, &'a [u8])> for RawChunk<Vec<u8>> {
#[inline]
fn from(value: (ChunkType, &'a [u8])) -> Self {
RawChunk::<&'a [u8]>::from(value).into()
}
}
impl<const N: usize> From<(ChunkType, [u8; N])> for RawChunk<Vec<u8>> {
#[inline]
fn from(value: (ChunkType, [u8; N])) -> Self {
RawChunk::<[u8; N]>::from(value).into()
}
}
impl From<(ChunkType, Box<[u8]>)> for RawChunk<Vec<u8>> {
#[inline]
fn from(value: (ChunkType, Box<[u8]>)) -> Self {
RawChunk::<Box<[u8]>>::from(value).into()
}
}
impl<'d> RawChunk<&'d [u8]> {
pub(crate) fn from_slice(ty: ChunkType, data: &'d [u8]) -> Self {
let chunk = (ty, data);
Self {
length: chunk.length(),
crc: chunk.crc(),
ty,
data,
}
}
}
impl<'a> From<RawChunk<Cow<'a, [u8]>>> for RawChunk<Vec<u8>> {
#[inline]
fn from(value: RawChunk<Cow<'a, [u8]>>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: value.data.into(),
crc: value.crc,
}
}
}
impl<'a> From<RawChunk<&'a [u8]>> for RawChunk<Vec<u8>> {
#[inline]
fn from(value: RawChunk<&'a [u8]>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: value.data.into(),
crc: value.crc,
}
}
}
impl<const N: usize> From<RawChunk<[u8; N]>> for RawChunk<Vec<u8>> {
#[inline]
fn from(value: RawChunk<[u8; N]>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: value.data.into(),
crc: value.crc,
}
}
}
impl From<RawChunk<Box<[u8]>>> for RawChunk<Vec<u8>> {
#[inline]
fn from(value: RawChunk<Box<[u8]>>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: value.data.into(),
crc: value.crc,
}
}
}
impl From<RawChunk<Vec<u8>>> for RawChunk<Box<[u8]>> {
#[inline]
fn from(value: RawChunk<Vec<u8>>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: value.data.into(),
crc: value.crc,
}
}
}
impl<'a> From<RawChunk<Cow<'a, [u8]>>> for RawChunk<Box<[u8]>> {
#[inline]
fn from(value: RawChunk<Cow<'a, [u8]>>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: value.data.into(),
crc: value.crc,
}
}
}
impl<'a> From<RawChunk<&'a [u8]>> for RawChunk<Box<[u8]>> {
#[inline]
fn from(value: RawChunk<&'a [u8]>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: value.data.into(),
crc: value.crc,
}
}
}
impl<const N: usize> From<RawChunk<[u8; N]>> for RawChunk<Box<[u8]>> {
#[inline]
fn from(value: RawChunk<[u8; N]>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: value.data.into(),
crc: value.crc,
}
}
}
impl From<RawChunk<Vec<u8>>> for RawChunk<Cow<'_, [u8]>> {
#[inline]
fn from(value: RawChunk<Vec<u8>>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: Cow::Owned(value.data),
crc: value.crc,
}
}
}
impl<'a> From<RawChunk<&'a [u8]>> for RawChunk<Cow<'a, [u8]>> {
#[inline]
fn from(value: RawChunk<&'a [u8]>) -> Self {
Self {
length: value.length,
ty: value.ty,
data: Cow::Borrowed(value.data),
crc: value.crc,
}
}
}
impl<D> RawChunk<D>
where
Self: Chunk,
{
#[inline]
pub(crate) fn as_ref(&self) -> RawChunk<&[u8]> {
RawChunk {
length: self.length,
ty: self.ty,
data: self.data(),
crc: self.crc,
}
}
}
impl<T: AsRef<[u8]>> Chunk for RawChunk<T> {
#[inline]
fn length(&self) -> u32 {
self.length
}
#[inline]
fn ty(&self) -> ChunkType {
self.ty
}
#[inline]
fn data(&self) -> &[u8] {
self.data.as_ref()
}
#[inline]
fn crc(&self) -> u32 {
self.crc
}
}
impl RawChunk {
#[inline]
pub fn from_data<T: Into<Vec<u8>>>(ty: ChunkType, data: T) -> Self {
#[inline]
fn inner(ty: ChunkType, data: Vec<u8>) -> RawChunk {
let chunk = (ty, &data[..]);
RawChunk {
length: chunk.length(),
crc: chunk.crc(),
ty,
data,
}
}
inner(ty, data.into())
}
}
impl<T: AsRef<[u8]>> Chunk for (ChunkType, T) {
#[inline]
fn ty(&self) -> ChunkType {
self.0
}
#[inline]
fn data(&self) -> &[u8] {
self.1.as_ref()
}
}
impl<T: Chunk + ?Sized> Chunk for &T {
#[inline]
fn length(&self) -> u32 {
T::length(*self)
}
#[inline]
fn ty(&self) -> ChunkType {
T::ty(*self)
}
#[inline]
fn data(&self) -> &[u8] {
T::data(*self)
}
#[inline]
fn crc(&self) -> u32 {
T::crc(*self)
}
}
impl<T: Chunk + ?Sized> Chunk for &mut T {
#[inline]
fn length(&self) -> u32 {
T::length(*self)
}
#[inline]
fn ty(&self) -> ChunkType {
T::ty(*self)
}
#[inline]
fn data(&self) -> &[u8] {
T::data(*self)
}
#[inline]
fn crc(&self) -> u32 {
T::crc(*self)
}
}
#[inline]
pub(crate) fn chunk_data_split(
ty: ChunkType,
data: &[u8],
mid: usize,
) -> (RawChunk<&[u8]>, Option<RawChunk<&[u8]>>) {
if let Some((first, last)) = data.split_at_checked(mid) {
if last.is_empty() {
(RawChunk::from_slice(ty, first), None)
} else {
(
RawChunk::from_slice(ty, first),
Some(RawChunk::from_slice(ty, last)),
)
}
} else {
(RawChunk::from_slice(ty, data), None)
}
}
#[inline]
pub fn read_as_chunks<R: Read>(
mut archive: R,
) -> io::Result<impl Iterator<Item = io::Result<impl Chunk>>> {
struct Chunks<R> {
reader: R,
done: bool,
}
impl<R: Read> Iterator for Chunks<R> {
type Item = io::Result<RawChunk>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
Some(match crate::io::read_chunk(&mut self.reader, u32::MAX) {
Ok(chunk) => {
self.done = chunk.ty() == ChunkType::AEND;
Ok(chunk)
}
Err(error) => {
self.done = true;
Err(error)
}
})
}
}
crate::io::read_signature(&mut archive)?;
Ok(Chunks {
reader: archive,
done: false,
})
}
#[inline]
pub fn read_chunks_from_slice<'a>(
archive: &'a [u8],
) -> io::Result<impl Iterator<Item = io::Result<impl Chunk + 'a>>> {
struct Chunks<'a> {
reader: &'a [u8],
done: bool,
}
impl<'a> Iterator for Chunks<'a> {
type Item = io::Result<RawChunk<&'a [u8]>>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
Some(match crate::bytes::read_chunk(self.reader, u32::MAX) {
Ok((chunk, bytes)) => {
self.done = chunk.ty() == ChunkType::AEND;
self.reader = bytes;
Ok(chunk)
}
Err(error) => {
self.done = true;
Err(error)
}
})
}
}
let archive = crate::bytes::read_signature(archive)?;
Ok(Chunks {
reader: archive,
done: false,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as test;
#[test]
fn chunk_references_preserve_fields() {
struct StoredChunk;
impl Chunk for StoredChunk {
fn length(&self) -> u32 {
1
}
fn ty(&self) -> ChunkType {
ChunkType::FDAT
}
fn data(&self) -> &[u8] {
b"abc"
}
fn crc(&self) -> u32 {
0x0102_0304
}
}
fn assert_fields(chunk: impl Chunk) {
assert_eq!(chunk.length(), 1);
assert_eq!(chunk.ty(), ChunkType::FDAT);
assert_eq!(chunk.data(), b"abc");
assert_eq!(chunk.crc(), 0x0102_0304);
}
let mut chunk = StoredChunk;
assert_fields(&chunk);
assert_fields(&mut chunk);
}
#[test]
fn chunk_trait_bounds() {
fn check_impl<T: Chunk>() {}
check_impl::<RawChunk<Vec<u8>>>();
check_impl::<RawChunk<Cow<[u8]>>>();
check_impl::<RawChunk<&[u8]>>();
check_impl::<RawChunk<[u8; 1]>>();
check_impl::<RawChunk<Box<[u8]>>>();
}
#[test]
fn data_split_at_zero() {
let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
let chunk = RawChunk::from_data(ChunkType::FDAT, data);
assert_eq!(
chunk_data_split(chunk.ty, chunk.data(), 0),
(
RawChunk::from_slice(ChunkType::FDAT, &[]),
Some(RawChunk::from_slice(
ChunkType::FDAT,
&[0xAA, 0xBB, 0xCC, 0xDD]
)),
)
)
}
#[test]
fn data_split_at_middle() {
let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
let chunk = RawChunk::from_data(ChunkType::FDAT, data);
assert_eq!(
chunk_data_split(chunk.ty, chunk.data(), 2),
(
RawChunk::from_slice(ChunkType::FDAT, &[0xAA, 0xBB]),
Some(RawChunk::from_slice(ChunkType::FDAT, &[0xCC, 0xDD])),
)
)
}
#[test]
fn data_split_at_just() {
let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
let chunk = RawChunk::from_data(ChunkType::FDAT, data);
assert_eq!(
chunk_data_split(chunk.ty, chunk.data(), 4),
(
RawChunk::from_slice(ChunkType::FDAT, &[0xAA, 0xBB, 0xCC, 0xDD]),
None,
)
)
}
#[test]
fn data_split_at_over() {
let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
let chunk = RawChunk::from_data(ChunkType::FDAT, data);
assert_eq!(
chunk_data_split(chunk.ty, chunk.data(), 5),
(
RawChunk::from_slice(ChunkType::FDAT, &[0xAA, 0xBB, 0xCC, 0xDD]),
None,
)
)
}
fn archive_with_broken_chunk() -> Vec<u8> {
let mut archive = crate::PNA_SIGNATURE.to_vec();
let mut chunk = Vec::new();
crate::io::write_chunk(&mut chunk, (ChunkType::FDAT, b"data")).unwrap();
*chunk.last_mut().unwrap() ^= 0xFF;
archive.extend_from_slice(&chunk);
archive
}
#[test]
fn read_as_chunks_stops_after_parsing_error() {
let archive = archive_with_broken_chunk();
let mut chunks = read_as_chunks(&archive[..]).unwrap();
assert!(matches!(chunks.next(), Some(Err(_))));
assert!(chunks.next().is_none());
}
#[test]
fn read_chunks_from_slice_stops_after_parsing_error() {
let archive = archive_with_broken_chunk();
let mut chunks = read_chunks_from_slice(&archive).unwrap();
assert!(matches!(chunks.next(), Some(Err(_))));
assert!(chunks.next().is_none());
}
}
#[cfg(test)]
pub(crate) mod test_support {
pub(crate) fn valid_chunk_bytes() -> Vec<u8> {
vec![
0x00, 0x00, 0x00, 0x04, b'F', b'D', b'A', b'T', 0xAA, 0xBB, 0xCC, 0xDD, 0x47, 0xF3, 0x2B, 0x10, ]
}
pub(crate) fn raw_chunk_bytes(ty: [u8; 4], data: &[u8]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(data.len() + 12);
bytes.extend_from_slice(&(data.len() as u32).to_be_bytes());
bytes.extend_from_slice(&ty);
bytes.extend_from_slice(data);
bytes.extend_from_slice(&crate::format::chunk_crc(&ty, data).to_be_bytes());
bytes
}
}