132 lines
4.3 KiB
Rust
132 lines
4.3 KiB
Rust
use {
|
|
crate::{error::Error, B32Alphabet},
|
|
std::io::{ErrorKind, Read, Write},
|
|
};
|
|
|
|
pub struct Decoder<R: Read, W: Write> {
|
|
reader: R,
|
|
writer: W,
|
|
alphabet: B32Alphabet,
|
|
ignore_whitespace: bool
|
|
}
|
|
|
|
impl<R: Read, W: Write> Decoder<R, W> {
|
|
pub fn new(reader: R, writer: W, alphabet: Option<B32Alphabet>, 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<W, Error> {
|
|
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::<u8>::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);
|
|
}
|
|
}
|