As a Rust learning exercise, I'm working on implementing cryptopals challenges using iterators to perform lazy processing of data. For the first challenge, this is a base64 decoder implemented as an iterator adaptor:
use std::iter;
enum B64Result {
Byte(u32),
Pad,
Invalid
}
fn b64_character_to_byte(byte: u8) -> B64Result {
if byte >= b'A' && byte <= b'Z' {
B64Result::Byte((byte - 65) as u32)
} else if byte >= b'a' && byte <= b'z' {
B64Result::Byte((byte - 97 + 26) as u32)
} else if byte >= b'0' && byte <= b'9' {
B64Result::Byte((byte - 48 + 52) as u32)
} else if byte == b'+' {
B64Result::Byte(62)
} else if byte == b'/' {
B64Result::Byte(63)
} else if byte == b'=' {
B64Result::Pad
} else {
B64Result::Invalid
}
}
// Decodes a 4-character block of base64 characters, return the number of bytes decoded.
fn decode_block(input: &[u8; 4], output: &mut [u8; 3]) -> Result<u8, &'static str> {
let mut accum = 0 as u32;
let mut pad_count = 0;
for (i, character) in input.iter().enumerate() {
let shift = 18 - (i % 4) * 6;
match b64_character_to_byte(*character) {
B64Result::Byte(byte) => {
if pad_count != 0 {
return Err("Invalid base64 string");
}
accum |= byte << shift;
},
B64Result::Pad => {
if pad_count == 2 {
return Err("Invalid base64 string");
}
pad_count += 1;
},
B64Result::Invalid => return Err("Invalid base64 string"),
}
}
output[0] = (accum >> 16) as u8;
output[1] = ((accum >> 8) & 0xff) as u8;
output[2] = (accum & 0xff) as u8;
Ok(3 - pad_count)
}
pub struct Base64Decoder<'a, T>
where T: Iterator<Item=u8> + 'a
{
input: &'a mut T,
buffer: [u8; 3],
buffer_pos: u8,
buffer_len: u8,
}
impl<'a, T> Base64Decoder<'a, T>
where T: Iterator<Item=u8> + 'a
{
pub fn new(input: &'a mut T) -> Base64Decoder<'a, T> {
Base64Decoder::<'a, T> {
input,
buffer: [0; 3],
buffer_pos: 0,
buffer_len: 0,
}
}
}
impl<'a, T> iter::Iterator for Base64Decoder<'a, T>
where T: Iterator<Item=u8> + 'a
{
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
if self.buffer_pos == 0 {
let mut buf = [0; 4];
let mut len = 0;
// fetch more from input
while len < 4 {
match self.input.next() {
Some(b) => {
if b != b'\n' {
buf[len] = b;
len += 1;
} else {
continue;
}
}
None => return None
}
}
match decode_block(&buf, &mut self.buffer) {
Ok(len) => self.buffer_len = len,
Err(_) => return None
}
}
let rv = Some(self.buffer[self.buffer_pos as usize]);
self.buffer_pos = (self.buffer_pos + 1) % self.buffer_len;
rv
}
}
pub trait Base64Decodable: Iterator<Item=u8> {
fn b64decode<'a>(&'a mut self) -> Base64Decoder<'a, Self>
where Self: Sized + Iterator<Item=u8> + 'a
{
Base64Decoder::new(self)
}
}
impl<'a, T> Base64Decodable for T
where T: Iterator<Item=u8> + 'a
{ }
The idea is to be able to lazily decode base64 from any u8
iterator. This is an example usage:
// simple program to decode base64 from stdin
use std::io;
use std::io::Read;
use std::io::Write;
use matasano::util::base64::Base64Decodable;
fn main() {
for byte in io::stdin().bytes().filter(|b| b.is_ok()).map(|b| b.unwrap()).b64decode() {
io::stdout().write([byte]).unwrap();
}
}
}
Is this a good design? What can I do to improve, or what other tools/patterns can I use to implement lazy transformations like this?