use { crate::{error::Error, B32Alphabet}, std::io::{ErrorKind, Read, Write}, }; pub struct Decoder { reader: R, writer: W, alphabet: B32Alphabet, ignore_whitespace: bool } impl Decoder { pub fn new(reader: R, writer: W, alphabet: Option, ignore_whitespace: bool) -> Self { Self { reader, writer, alphabet: alphabet.unwrap_or_default(), ignore_whitespace, } } /// Decodes the bytes provided by `self.reader` and writes the resulting /// bytes into `self.writer`. /// # Important /// Both reader and writer should be buffered for good performance, as the /// process of decoding will perform a series of very short read and write /// operations. /// # Errors /// - io failure will return `Error::Io` /// - a character not in the alphabet, or encountering any character after /// the first occurance of the padding character, will return `Error::IllegalChar` /// - if the reader comes up short of the final four byte block will return /// `Error::MissingPadding` /// - `intError` should never be returned. If this error is recieved the code /// is somehow broken. Please file a bug. pub fn decode(mut self) -> Result { let mut byte_reader = self.reader.bytes(); 'outer: loop { let mut in_buf = [0_u8; 8]; let mut out_buf = [0_u8; 5]; let mut num: u64 = 0; let mut n_bytes = 0; while n_bytes < 8 { match byte_reader.next() { Some(Ok(b)) => { if self.ignore_whitespace && b.is_ascii_whitespace() { continue; } else if b == b'\n' || b == b'\r' { continue; } else { in_buf[n_bytes] = b; n_bytes += 1; } } Some(Err(e)) if e.kind() == ErrorKind::UnexpectedEof => break, Some(Err(e)) if e.kind() == ErrorKind::Interrupted => continue, Some(Err(e)) => return Err(e.into()), None => break, } } for c in &in_buf { num <<= 5; if !matches!(self.alphabet.pad(), Some(ch) if ch == *c) { let idx = self.alphabet.idx(*c).ok_or(Error::IllegalChar((*c).into()))?; num |= idx as u64; } } for i in (0..5).rev() { let b = (num & 0xff) as u8; out_buf[i] = b; num >>= 8; } for c in &out_buf { if *c == b'\0' { break 'outer; } else { self.writer.write_all(&[*c])?; } } } self.writer.flush()?; Ok(self.writer) } } #[cfg(test)] mod tests { use super::*; use crate::B32_RFC4648_ALPHABET; use std::{ fs::File, io::{BufReader, Read}, }; static HELLO: &'static str = "Hello, World!"; static ENCODED: &'static str = "JBSWY3DPFQQFO33SNRSCC==="; #[test] fn get_idx() { let idx = B32_RFC4648_ALPHABET.idx(b'S').unwrap(); assert_eq!(idx, 18); } #[test] fn hello() { let reader = ENCODED.as_bytes(); let writer = Vec::::new(); let decoder = Decoder::new(reader, writer, None, false); assert_eq!(decoder.decode().unwrap(), HELLO.as_bytes()); } #[test] fn ignore_whitespace() { let decoder = Decoder::new(" JBSWY3DP\tFQQFO33SNRSCC===".as_bytes(), vec![], None, true); assert_eq!(decoder.decode().unwrap(), HELLO.as_bytes()); } #[test] fn decode_wrapped() { let infile = File::open("src/testdata/lorem_b32.txt").unwrap(); let reader = BufReader::new(infile); let mut outfile = vec![]; File::open("src/testdata/lorem.txt") .unwrap() .read_to_end(&mut outfile) .unwrap(); let decoder = Decoder::new(reader, vec![], None, false); assert_eq!(decoder.decode().unwrap(), outfile); } }