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 std::io::{self, 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)
}
}
use {
crate::{error::Error, B32Alphabet},
std::io::{ErrorKind, Read, Write},
};
pub struct Decoder<R: Read, W: Write> {
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();
'outer: loop {
let mut in_buf = [0_u8; 8];
@ -58,7 +63,7 @@ impl<R: Read, W: Write> Decoder<R, W> {
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<R: Read, W: Write> Decoder<R, W> {
#[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::<u8>::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);
}
}

View file

@ -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<fmt::Error> for Error {
fn from(value: fmt::Error) -> Self {
Self::Fmt(value)
}
}
impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
Self::Io(value)
@ -48,7 +40,6 @@ impl From<TryFromIntError> 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,