binreader 0.2.1

A helper library to make reading binary data more pleasant.
Documentation
use crate::{BinReader, Endidness, OwnableBinReader, Result, SliceableBinReader};
use bytes::Bytes;
use fs3::FileExt as _;
use memmap2::{Mmap, MmapMut};
use std::{cell::Cell, fs::File, path::Path};

pub struct MmapBinReader {
    initial_offset: usize,
    position: Cell<usize>,
    map: Mmap,
    endidness: Endidness,
    maybe_mapped_file: Option<File>,
}

impl MmapBinReader {
    fn new(
        initial_offset: usize,
        map: Mmap,
        endidness: Endidness,
        maybe_mapped_file: Option<File>,
    ) -> Self {
        Self {
            initial_offset,
            position: Cell::new(0),
            map,
            endidness,
            maybe_mapped_file,
        }
    }

    fn adj_pos(&self, amt: isize) {
        let tmp = self.position.get() as isize;
        self.position.replace((tmp + amt) as usize);
    }
}

impl Drop for MmapBinReader {
    fn drop(&mut self) {
        if let Some(file) = &self.maybe_mapped_file {
            file.unlock().unwrap();
        }
    }
}

impl AsRef<[u8]> for MmapBinReader {
    fn as_ref(&self) -> &[u8] {
        self.map.as_ref()
    }
}

impl<'r> BinReader<'r> for MmapBinReader {
    #[inline]
    fn is_empty(&self) -> bool {
        self.remaining() == 0
    }

    #[inline]
    fn size(&self) -> usize {
        self.map.len()
    }

    #[inline]
    fn remaining(&self) -> usize {
        self.size() - self.position.get()
    }

    #[inline]
    fn current_offset(&self) -> usize {
        self.initial_offset + self.position.get()
    }

    #[inline]
    fn endidness(&self) -> Endidness {
        self.endidness
    }

    #[inline]
    fn change_endidness(&mut self, endidness: Endidness) {
        self.endidness = endidness
    }

    #[inline]
    fn initial_offset(&self) -> usize {
        self.initial_offset
    }

    fn advance_to(&self, offset: usize) -> Result<()> {
        self.validate_offset(offset, 0)?;
        self.position.replace(offset - self.initial_offset);
        Ok(())
    }

    fn advance_by(&self, bytes: isize) -> Result<()> {
        self.validate_offset((self.position.get() as isize + bytes) as usize, 0)?;
        self.adj_pos(bytes);
        Ok(())
    }

    fn u8_at(&self, offset: usize) -> Result<u8> {
        self.validate_offset(offset, 0)?;
        Ok(self.map[offset - self.initial_offset])
    }

    fn next_u8(&self) -> Result<u8> {
        self.validate_offset(self.current_offset(), 1)?;
        self.adj_pos(1);
        Ok(self.map[self.position.get() - 1])
    }

    fn from_slice_with_offset(
        slice: &[u8],
        initial_offset: usize,
        endidness: Endidness,
    ) -> Result<Self> {
        let mut mmap_mut = MmapMut::map_anon(slice.len())?;
        mmap_mut.copy_from_slice(slice);
        Ok(Self::new(
            initial_offset,
            mmap_mut.make_read_only()?,
            endidness,
            None,
        ))
    }
}

impl<'r> OwnableBinReader<'r> for MmapBinReader {
    fn from_file_with_offset<P: AsRef<Path>>(
        path: P,
        initial_offset: usize,
        endidness: Endidness,
    ) -> Result<Self> {
        let file = File::open(path)?;
        file.try_lock_shared()?;
        let mmap = unsafe { Mmap::map(&file)? };
        Ok(Self::new(initial_offset, mmap, endidness, Some(file)))
    }

    fn from_bytes_with_offset(
        bytes: Bytes,
        initial_offset: usize,
        endidness: Endidness,
    ) -> Result<Self> {
        Self::from_slice_with_offset(&bytes, initial_offset, endidness)
    }
}

impl<'r> SliceableBinReader<'r> for MmapBinReader {}

add_read! { MmapBinReader }
add_borrow! { MmapBinReader }
add_seek! { MmapBinReader }
add_bufread! { MmapBinReader }

#[cfg(feature = "nom")]
add_all_noms! { MmapBinReader }

#[cfg(test)]
mod tests {
    use super::*;
    use crate::testing;

    #[test]
    fn basic_test() {
        testing::basic_test_1::<MmapBinReader>();
    }

    #[test]
    fn basic_le_test() {
        testing::basic_le_test::<MmapBinReader>();
    }

    #[test]
    fn basic_be_test() {
        testing::basic_be_test::<MmapBinReader>();
    }
}