riffu 4.0.1

" This crate provides utilities for reading and writing RIFF formatted files. A fork of the original library that provides an option to either eagerly or lazily read into the file.
Documentation
use crate::{
    constants::{LIST_ID, RIFF_ID, SEQT_ID_LOWERCASE, SEQT_ID_UPPERCASE},
    error::RiffResult,
    FourCC, RiffError,
};
use memmap::Mmap;
use std::convert::TryInto;
use std::path::Path;
use std::{fmt::Debug, fs::File};

#[derive(Debug)]
pub struct Riff {
    inner: Mmap,
}

impl Riff {
    pub fn from_path<P>(path: P) -> RiffResult<Riff>
    where
        P: AsRef<Path>,
    {
        let inner = unsafe { Mmap::map(&File::open(&path)?)? };
        Ok(Riff { inner })
    }

    pub fn as_chunk(&self) -> RiffResult<Chunk> {
        Chunk::from_bytes(&self.inner)
    }
}

/// Represents the possible data contained in a `ChunkDisk`.
#[derive(Debug)]
pub enum Chunk<'a> {
    Raw(&'a [u8]),
    List(&'a [u8]),
    Seqt(&'a [u8]),
}

impl<'a> Chunk<'a> {
    pub fn from_bytes(data: &[u8]) -> RiffResult<Chunk> {
        let id = data.get(0..4).ok_or(RiffError::InsufficientBytes)?;
        let payload_len = u32::from_le_bytes(
            data.get(4..8)
                .ok_or(RiffError::InsufficientBytes)?
                .try_into()?,
        ) as usize;
        let data = data
            .get(0..payload_len + payload_len % 2 + 8)
            .ok_or(RiffError::InsufficientBytes)?;
        match id {
            LIST_ID | RIFF_ID => Ok(Chunk::List(data)),
            SEQT_ID_LOWERCASE | SEQT_ID_UPPERCASE => Ok(Chunk::Seqt(data)),
            _ => Ok(Chunk::Raw(data)),
        }
    }

    pub fn id(&self) -> RiffResult<FourCC> {
        let id = &self.as_bytes()[0..4];
        let result = FourCC::new(id)?;
        Ok(result)
    }

    pub fn payload_len(&self) -> RiffResult<u32> {
        let id = self
            .as_bytes()
            .get(4..8)
            .map(|s| s.try_into().ok())
            .flatten()
            .ok_or(RiffError::InsufficientBytes)?;
        let result = u32::from_le_bytes(id);
        Ok(result)
    }

    pub fn chunk_type(&self) -> RiffResult<FourCC> {
        let id = self.read_n_bytes_from_offset(8, 4)?;
        let result = FourCC::new(id)?;
        Ok(result)
    }

    fn read_n_bytes_from_offset(&self, offset: u32, count: u32) -> RiffResult<&[u8]> {
        let pos_begin = offset as usize;
        let pos_end = (offset + count) as usize;
        let data = self
            .as_bytes()
            .get(pos_begin..pos_end)
            .ok_or(RiffError::InsufficientBytes)?;
        Ok(data)
    }

    pub fn content(&self) -> RiffResult<&[u8]> {
        let offset = self.content_offset();
        let len = self.payload_len()?;
        self.read_n_bytes_from_offset(offset, len)
    }

    pub fn content_offset(&self) -> u32 {
        match self {
            Chunk::Raw(_) => 8,
            Chunk::List(_) => 12,
            Chunk::Seqt(_) => 8,
        }
    }

    pub fn as_bytes(&self) -> &[u8] {
        match self {
            Chunk::Raw(data) => (data),
            Chunk::List(data) => (data),
            Chunk::Seqt(data) => (data),
        }
    }

    pub fn iter(&self) -> RiffResult<ChunkIter> {
        let offset = self.content_offset();
        let cursor_end = self.as_bytes().len() as u32;
        match self {
            Chunk::Raw(_) => Ok(ChunkIter {
                cursor: 0,
                cursor_end,
                data: self.as_bytes(),
                error_occurred: false,
            }),
            _ => Ok(ChunkIter {
                cursor: offset,
                cursor_end,
                data: self.as_bytes(),
                error_occurred: false,
            }),
        }
    }
}

#[derive(Debug)]
pub struct ChunkIter<'a> {
    cursor: u32,
    cursor_end: u32,
    data: &'a [u8],
    error_occurred: bool,
}

macro_rules! try_result {
    ( $self : ident , $value :expr ) => {
        match $value {
            Ok(r) => r,
            Err(err) => {
                $self.error_occurred = true;
                return Some(Err(err));
            }
        }
    };
}

macro_rules! try_option {
    ( $self : ident , $value :expr ) => {
        match $value {
            Some(r) => r,
            None => return Some(Err(RiffError::InsufficientBytes)),
        }
    };
}

impl<'a> Iterator for ChunkIter<'a> {
    type Item = RiffResult<Chunk<'a>>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.error_occurred || self.cursor >= self.cursor_end {
            None
        } else {
            let cursor = self.cursor as usize;
            let cursor_end = self.cursor_end as usize;
            let data = try_option!(self, self.data.get(cursor..cursor_end));
            let chunk = try_result!(self, Chunk::from_bytes(data));
            let payload_len = try_result!(self, chunk.payload_len());
            self.cursor += 8 + payload_len + payload_len % 2;
            Some(Ok(chunk))
        }
    }
}