binhex-rs 0.1.1

Crate to read BinHex 4 encoded files
Documentation
use crate::ReadByte;
use std::io;

pub(crate) trait SeekOver {
    /// Finds the string in the given reader and position the cursor after it.
    /// Returns an error if the string can not be found or the input stream contains a null byte
    fn seek_over_string<const N: usize>(&mut self, needle: &[u8; N]) -> io::Result<()>;
}

impl<T: io::Seek + io::Read> SeekOver for T {
    fn seek_over_string<const N: usize>(&mut self, needle: &[u8; N]) -> io::Result<()> {
        let mut window = [0u8; N];
        let mut window_ptr = 0;
        self.read_exact(&mut window)?;
        if window.contains(&0) {
            return Err(io::Error::other("Not Found"));
        }

        if window[0..N] == needle[0..N] {
            return Ok(());
        }

        loop {
            if window[window_ptr..N] == needle[0..(N - window_ptr)]
                && window[0..window_ptr] == needle[(N - window_ptr)..N]
            {
                return Ok(());
            }

            window[window_ptr] = match self.read_byte()? {
                0 => return Err(io::Error::other("Not Found")),
                v => v,
            };
            window_ptr = (window_ptr + 1) % N;
        }
    }
}

#[cfg(test)]
mod test {
    use std::fs;
    use std::io;
    use std::io::Seek;

    use super::SeekOver;

    #[test]
    fn search_success() {
        let mut file = fs::File::open("./sample-file.hqx").unwrap();
        let needle = b"text";
        file.seek_over_string(needle).unwrap();
        assert_eq!(file.stream_position().unwrap(), 42);

        file.seek(io::SeekFrom::Start(0)).unwrap();
        let needle = b"(This file must be converted with BinHex 4.0)";
        file.seek_over_string(needle).unwrap();
        assert_eq!(file.stream_position().unwrap(), 133);
    }

    #[test]
    fn search_fail() {
        let mut file = fs::File::open("./sample-file.hqx").unwrap();
        file.seek(io::SeekFrom::Start(0)).unwrap();
        let needle = b"nowhere";
        assert!(file.seek_over_string(needle).is_err());
    }

    #[test]
    fn early_match() {
        let mut file = fs::File::open("./sample-file.hqx").unwrap();

        file.seek_over_string(b"Subject").unwrap();
        assert_eq!(file.stream_position().unwrap(), b"Subject".len() as u64);

        file.seek(io::SeekFrom::Start(0)).unwrap();
        file.seek_over_string(b"ubject").unwrap();
        assert_eq!(file.stream_position().unwrap(), b"ubject".len() as u64 + 1);

        file.seek(io::SeekFrom::Start(0)).unwrap();
        file.seek_over_string(b"bject").unwrap();
        assert_eq!(file.stream_position().unwrap(), b"bject".len() as u64 + 2);

        file.seek(io::SeekFrom::Start(0)).unwrap();
        file.seek_over_string(b"ject").unwrap();
        assert_eq!(file.stream_position().unwrap(), b"ject".len() as u64 + 3);

        file.seek(io::SeekFrom::Start(0)).unwrap();
        file.seek_over_string(b"4").unwrap();
        assert_eq!(file.stream_position().unwrap(), 17);
    }

    #[test]
    fn late_match() {
        let mut file = fs::File::open("./sample-file.hqx").unwrap();
        file.seek_over_string(b"!!!:").unwrap();
        assert_eq!(
            file.stream_position().unwrap(),
            file.seek(io::SeekFrom::End(0)).unwrap()
        );
    }
}