Some polish and adjustment to make the interface and code closer to the

corresponding b64 crate
This commit is contained in:
Nathan Fisher 2025-01-10 23:20:50 -05:00
parent 0d1f59bf6c
commit 0ce411a7fd
2 changed files with 46 additions and 27 deletions

View file

@ -1,17 +1,7 @@
use super::*; use {
use std::io::{self, ErrorKind, Read, Write}; crate::{error::Error, B32Alphabet},
std::io::{ErrorKind, Read, Write},
#[derive(Debug)] };
pub enum DecoderError {
IO(io::Error),
IllegalChar,
}
impl From<io::Error> for DecoderError {
fn from(value: io::Error) -> Self {
Self::IO(value)
}
}
pub struct Decoder<R: Read, W: Write> { pub struct Decoder<R: Read, W: Write> {
reader: R, reader: R,
@ -30,7 +20,22 @@ impl<R: Read, W: Write> Decoder<R, W> {
} }
} }
pub fn decode(mut self) -> Result<W, DecoderError> {
/// 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<W, Error> {
let mut byte_reader = self.reader.bytes(); let mut byte_reader = self.reader.bytes();
'outer: loop { 'outer: loop {
let mut in_buf = [0_u8; 8]; let mut in_buf = [0_u8; 8];
@ -58,7 +63,7 @@ impl<R: Read, W: Write> Decoder<R, W> {
for c in &in_buf { for c in &in_buf {
num <<= 5; num <<= 5;
if !matches!(self.alphabet.pad(), Some(ch) if ch == *c) { 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; num |= idx as u64;
} }
} }
@ -83,6 +88,11 @@ impl<R: Read, W: Write> Decoder<R, W> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::B32_RFC4648_ALPHABET;
use std::{
fs::File,
io::{BufReader, Read},
};
static HELLO: &'static str = "Hello, World!"; static HELLO: &'static str = "Hello, World!";
static ENCODED: &'static str = "JBSWY3DPFQQFO33SNRSCC==="; static ENCODED: &'static str = "JBSWY3DPFQQFO33SNRSCC===";
@ -98,7 +108,25 @@ mod tests {
let reader = ENCODED.as_bytes(); let reader = ENCODED.as_bytes();
let writer = Vec::<u8>::new(); let writer = Vec::<u8>::new();
let decoder = Decoder::new(reader, writer, None, false); let decoder = Decoder::new(reader, writer, None, false);
let output = decoder.decode().unwrap(); assert_eq!(decoder.decode().unwrap(), HELLO.as_bytes());
assert_eq!(HELLO.as_bytes(), output); }
#[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);
} }
} }

View file

@ -2,7 +2,6 @@ use std::{error, fmt, io, num::TryFromIntError};
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
Fmt(fmt::Error),
Io(io::Error), Io(io::Error),
IllegalChar(char), IllegalChar(char),
IntError(TryFromIntError), IntError(TryFromIntError),
@ -12,7 +11,6 @@ pub enum Error {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Fmt(e) => write!(f, "{e}"),
Self::Io(e) => write!(f, "{e}"), Self::Io(e) => write!(f, "{e}"),
Self::IllegalChar(c) => write!(f, "Illegal Character ({c})"), Self::IllegalChar(c) => write!(f, "Illegal Character ({c})"),
Self::MissingPadding => write!(f, "Missing Padding"), Self::MissingPadding => write!(f, "Missing Padding"),
@ -21,12 +19,6 @@ impl fmt::Display for Error {
} }
} }
impl From<fmt::Error> for Error {
fn from(value: fmt::Error) -> Self {
Self::Fmt(value)
}
}
impl From<io::Error> for Error { impl From<io::Error> for Error {
fn from(value: io::Error) -> Self { fn from(value: io::Error) -> Self {
Self::Io(value) Self::Io(value)
@ -48,7 +40,6 @@ impl From<TryFromIntError> for Error {
impl error::Error for Error { impl error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self { match self {
Self::Fmt(e) => Some(e),
Self::Io(e) => Some(e), Self::Io(e) => Some(e),
Self::IntError(e) => Some(e), Self::IntError(e) => Some(e),
_ => None, _ => None,