From 0ce411a7fdebf06c05964d4ab1f475be1677d60c Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Fri, 10 Jan 2025 23:20:50 -0500 Subject: [PATCH] Some polish and adjustment to make the interface and code closer to the corresponding b64 crate --- src/decode.rs | 64 ++++++++++++++++++++++++++++++++++++--------------- src/error.rs | 9 -------- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/decode.rs b/src/decode.rs index dff83bd..33658d0 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -1,17 +1,7 @@ -use super::*; -use std::io::{self, ErrorKind, Read, Write}; - -#[derive(Debug)] -pub enum DecoderError { - IO(io::Error), - IllegalChar, -} - -impl From for DecoderError { - fn from(value: io::Error) -> Self { - Self::IO(value) - } -} +use { + crate::{error::Error, B32Alphabet}, + std::io::{ErrorKind, Read, Write}, +}; pub struct Decoder { reader: R, @@ -30,7 +20,22 @@ impl Decoder { } } - pub fn decode(mut self) -> Result { + + /// 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]; @@ -58,7 +63,7 @@ impl Decoder { 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(DecoderError::IllegalChar)?; + let idx = self.alphabet.idx(*c).ok_or(Error::IllegalChar((*c).into()))?; num |= idx as u64; } } @@ -83,6 +88,11 @@ impl Decoder { #[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==="; @@ -98,7 +108,25 @@ mod tests { let reader = ENCODED.as_bytes(); let writer = Vec::::new(); let decoder = Decoder::new(reader, writer, None, false); - let output = decoder.decode().unwrap(); - assert_eq!(HELLO.as_bytes(), output); + 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); } } diff --git a/src/error.rs b/src/error.rs index a0ca903..ab33a9d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,6 @@ use std::{error, fmt, io, num::TryFromIntError}; #[derive(Debug)] pub enum Error { - Fmt(fmt::Error), Io(io::Error), IllegalChar(char), IntError(TryFromIntError), @@ -12,7 +11,6 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Fmt(e) => write!(f, "{e}"), Self::Io(e) => write!(f, "{e}"), Self::IllegalChar(c) => write!(f, "Illegal Character ({c})"), Self::MissingPadding => write!(f, "Missing Padding"), @@ -21,12 +19,6 @@ impl fmt::Display for Error { } } -impl From for Error { - fn from(value: fmt::Error) -> Self { - Self::Fmt(value) - } -} - impl From for Error { fn from(value: io::Error) -> Self { Self::Io(value) @@ -48,7 +40,6 @@ impl From for Error { impl error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Self::Fmt(e) => Some(e), Self::Io(e) => Some(e), Self::IntError(e) => Some(e), _ => None,