Clean up decoder and add documentation

This commit is contained in:
Nathan Fisher 2025-01-06 01:12:25 -05:00
parent 841160d2d5
commit 313b877553
6 changed files with 155 additions and 38 deletions

49
README.md Normal file
View File

@ -0,0 +1,49 @@
Contents
========
- [Introduction](#introduction)
- [Usage](#usage)
# Introduction
This is a Base64 encoding library as described in [RFC4648](https://www.rfc-editor.org/rfc/rfc4648).
This library encodes and decodes random data into ASCII characters using an appropriate
Base64 library. It is designed to leverage the abstractions in Rust's std library
in order to be generic over io streams. That is to say, you can read from anything
implementing **Read** and write the encoded or decoded output to anything which
implements **Write**.
# Usage
Add the crate to your `Cargo.toml` file.
```Toml
# Cargo.toml
[dependencies]
b64 = { git = "https://git.hitchhiker-linux.org/jeang3nie/b64_rs.git" }
```
## Encoding
Encoding 'Hello, World!' into a new `String`, wrapping at 76 characters and using
the default alphabet.
```
use {
std::io::Read,
b64::Encoder,
};
fn main() {
let mut encoder = Encoder::new("Hello, World!".as_bytes(), String::new(), None, Some(76));
encoder.encode().unwrap();
assert_eq!(encoder.output(), "SGVsbG8sIFdvcmxkIQ==");
}
```
## Decoding
```
use {
std::io::Read,
b64::Decoder,
};
fn main() {
let mut decoder = Decoder::new("SGVsbG8sIFdvcmxkIQ==".as_bytes(), vec![], None);
let output = decoder.decode().unwrap();
assert_eq!(&output, b"Hello, World!");
}
```

View File

@ -3,6 +3,7 @@ use {
std::io::{ErrorKind, Read, Write},
};
/// Decodes b64 encoded data
pub struct Decoder<R: Read, W: Write> {
reader: R,
writer: W,
@ -20,6 +21,10 @@ impl<R: Read, W: Write> Decoder<R, W> {
/// 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
@ -36,23 +41,19 @@ impl<R: Read, W: Write> Decoder<R, W> {
let mut num = 0_u32;
let mut n_bytes = 0;
while n_bytes < 4 {
if let Some(byte) = byte_reader.next() {
match byte {
Ok(b) => {
if b == b'\n' {
continue;
}
match byte_reader.next() {
Some(Ok(b)) => {
if b == b'\n' || b == b'\r' {
continue;
} else {
in_buf[n_bytes] = b;
n_bytes += 1;
}
Err(e) => match e.kind() {
ErrorKind::UnexpectedEof => break,
ErrorKind::Interrupted => continue,
_ => return Err(e.into()),
}
}
} else {
break;
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,
}
}
match n_bytes {
@ -90,12 +91,15 @@ impl<R: Read, W: Write> Decoder<R, W> {
#[cfg(test)]
mod tests {
use super::*;
use std::{
fs::File,
io::{BufReader, Read},
};
#[test]
fn decode() {
let mut output = vec![];
let mut decoder = Decoder::new("SGVsbG8sIFdvcmxk".as_bytes(), output, None);
output = decoder.decode().unwrap();
let mut decoder = Decoder::new("SGVsbG8sIFdvcmxk".as_bytes(), vec![], None);
let mut output = decoder.decode().unwrap();
assert_eq!(String::from_utf8(output.clone()).unwrap(), "Hello, World");
output.clear();
decoder = Decoder::new("SGVsbG8sIFdvcmxkIQ==".as_bytes(), output, None);
@ -104,9 +108,19 @@ mod tests {
output.clear();
decoder = Decoder::new("SGVsbG8sIFdvcmxkIQo=".as_bytes(), output, None);
output = decoder.decode().unwrap();
assert_eq!(
String::from_utf8(output).unwrap(),
"Hello, World!\n"
);
assert_eq!(String::from_utf8(output).unwrap(), "Hello, World!\n");
}
#[test]
fn decode_wrapped() {
let infile = File::open("src/testdata/lorem_b64.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);
assert_eq!(decoder.decode().unwrap(), outfile);
}
}

View File

@ -6,6 +6,7 @@ use {
},
};
/// Encodes arbitraty data as b64 encoded ASCII text
pub struct Encoder<R: Read, W: Write> {
reader: R,
writer: W,
@ -16,6 +17,10 @@ pub struct Encoder<R: Read, W: Write> {
impl<R: Read, W: Write> Encoder<R, W> {
/// Creates a new encoder with the given reader and writer. If alphabet is
/// `None` the encoder will use the default rfc4648 alphabet.
/// # Important
/// Both reader and writer should be buffered for best performance, as the
/// encoder will pull single bytes from the reader and write three bytes at
/// a time into the writer.
pub fn new(reader: R, writer: W, alphabet: Option<B64Alphabet>, wrap: Option<usize>) -> Self {
Self {
reader,
@ -25,6 +30,7 @@ impl<R: Read, W: Write> Encoder<R, W> {
}
}
#[allow(clippy::needless_range_loop)]
pub fn encode(&mut self) -> Result<(), Error> {
let mut total: usize = 0;
loop {
@ -88,7 +94,10 @@ impl<R: Read> Encoder<R, String> {
#[cfg(test)]
mod tests {
use super::*;
use std::fs::{self, File};
use std::{
fs::{self, File},
io::BufReader,
};
#[test]
fn encode() {
@ -111,8 +120,9 @@ mod tests {
#[test]
fn wrapping() {
let infile = File::open("src/testdata/lorem.txt").unwrap();
let reader = BufReader::new(infile);
let outfile = fs::read_to_string("src/testdata/lorem_b64.txt").unwrap();
let mut encoder = Encoder::new(infile, String::new(), None, Some(76));
let mut encoder = Encoder::new(reader, String::new(), None, Some(76));
encoder.encode().unwrap();
assert_eq!(encoder.output(), outfile);
}

View File

@ -1,14 +1,20 @@
#[warn(clippy::all, clippy::pedantic)]
pub mod decode;
pub mod encode;
pub mod error;
#![warn(clippy::all, clippy::pedantic)]
#![doc = include_str!("../README.md")]
mod decode;
mod encode;
mod error;
pub use {decode::Decoder, encode::Encoder, error::Error as B64Error};
#[derive(Clone, Copy)]
/// Defines the character set used for encoding and decoding
pub struct B64Alphabet {
items: [char; 64],
pad: char,
}
/// The most common Base64 alphabet as defined in [RFC4648](https://www.rfc-editor.org/rfc/rfc4648)
pub static B64_RFC4648_ALPHABET: B64Alphabet = B64Alphabet {
items: [
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',

View File

@ -1 +1,9 @@
Lorem ipsum odor amet, consectetuer adipiscing elit. Mauris praesent senectus vivamus urna donec magna bibendum aenean. Nam nec sodales ex cursus inceptos vitae non habitant consectetur. Ex montes quis pellentesque tempor neque. Class ipsum fringilla mauris quam tempus. Nullam habitasse arcu ac nascetur class ad in. Senectus nostra arcu habitant arcu blandit potenti. Congue penatibus sollicitudin ullamcorper ridiculus semper. Ac porttitor bibendum nam; rhoncus pellentesque natoque pretium sit feugiat. Litora convallis maecenas elit netus blandit mus duis gravida sollicitudin.
Lorem ipsum odor amet, consectetuer adipiscing elit. Hendrerit eu semper lectus amet est proin. Tempus himenaeos at fusce lacus habitasse sociosqu torquent varius arcu. Odio netus fermentum nisl natoque per cubilia himenaeos urna. Elit venenatis eros risus vulputate duis interdum. Mauris dis ad pharetra dui aptent. Imperdiet orci auctor condimentum adipiscing arcu. Nullam tempor magna odio volutpat nisi. Condimentum scelerisque risus faucibus; duis sollicitudin nullam.
Condimentum ex fringilla tempus ultricies consectetur odio egestas venenatis vulputate. Sem sapien pellentesque ornare; viverra scelerisque consequat. Platea posuere mi senectus felis dolor dictumst mattis ultricies est. Malesuada elit adipiscing suspendisse est a. Justo luctus turpis tempus leo finibus rutrum nostra magnis aliquam. Felis posuere molestie praesent porta justo nisi. Etiam aptent conubia consectetur dis; penatibus elementum ut.
Morbi nostra netus ad nunc egestas neque nascetur fusce. Consequat ultrices scelerisque ut rhoncus volutpat, hendrerit convallis massa. Tortor himenaeos nullam malesuada suspendisse consectetur potenti volutpat. In suscipit sit maximus; lacinia eu nulla porta. Tempus commodo libero auctor cras bibendum sit. Metus aptent mauris nascetur venenatis diam metus. Inceptos mi ultrices class morbi donec. Orci pulvinar risus accumsan faucibus litora duis commodo commodo.
Aliquam etiam habitasse fames massa, hendrerit purus interdum nullam. Curabitur placerat tortor placerat nec adipiscing habitasse. Massa litora interdum pretium fusce nascetur primis faucibus magna. Dui curabitur molestie justo ullamcorper leo molestie mauris. Inceptos malesuada taciti litora libero maecenas neque nam laoreet. Tristique vivamus eget maecenas; porta fames curae mollis cras. Ipsum vulputate ullamcorper et nascetur nascetur vel orci.
Curae et odio; urna eros pulvinar malesuada eget dignissim. Cursus fermentum lectus phasellus tempor posuere vulputate quam. Mauris commodo commodo mauris inceptos metus varius urna sagittis. Ligula duis erat ipsum pretium eu facilisis. Duis lobortis proin facilisi ad suscipit vestibulum hac tortor interdum. Ultrices tincidunt maximus aptent phasellus in ullamcorper nisl varius dis. Venenatis enim potenti potenti sodales massa id elementum.

View File

@ -1,11 +1,41 @@
TG9yZW0gaXBzdW0gb2RvciBhbWV0LCBjb25zZWN0ZXR1ZXIgYWRpcGlzY2luZyBlbGl0LiBNYXVy
aXMgcHJhZXNlbnQgc2VuZWN0dXMgdml2YW11cyB1cm5hIGRvbmVjIG1hZ25hIGJpYmVuZHVtIGFl
bmVhbi4gTmFtIG5lYyBzb2RhbGVzIGV4IGN1cnN1cyBpbmNlcHRvcyB2aXRhZSBub24gaGFiaXRh
bnQgY29uc2VjdGV0dXIuIEV4IG1vbnRlcyBxdWlzIHBlbGxlbnRlc3F1ZSB0ZW1wb3IgbmVxdWUu
IENsYXNzIGlwc3VtIGZyaW5naWxsYSBtYXVyaXMgcXVhbSB0ZW1wdXMuIE51bGxhbSBoYWJpdGFz
c2UgYXJjdSBhYyBuYXNjZXR1ciBjbGFzcyBhZCBpbi4gU2VuZWN0dXMgbm9zdHJhIGFyY3UgaGFi
aXRhbnQgYXJjdSBibGFuZGl0IHBvdGVudGkuIENvbmd1ZSBwZW5hdGlidXMgc29sbGljaXR1ZGlu
IHVsbGFtY29ycGVyIHJpZGljdWx1cyBzZW1wZXIuIEFjIHBvcnR0aXRvciBiaWJlbmR1bSBuYW07
IHJob25jdXMgcGVsbGVudGVzcXVlIG5hdG9xdWUgcHJldGl1bSBzaXQgZmV1Z2lhdC4gTGl0b3Jh
IGNvbnZhbGxpcyBtYWVjZW5hcyBlbGl0IG5ldHVzIGJsYW5kaXQgbXVzIGR1aXMgZ3JhdmlkYSBz
b2xsaWNpdHVkaW4uCg==
TG9yZW0gaXBzdW0gb2RvciBhbWV0LCBjb25zZWN0ZXR1ZXIgYWRpcGlzY2luZyBlbGl0LiBIZW5k
cmVyaXQgZXUgc2VtcGVyIGxlY3R1cyBhbWV0IGVzdCBwcm9pbi4gVGVtcHVzIGhpbWVuYWVvcyBh
dCBmdXNjZSBsYWN1cyBoYWJpdGFzc2Ugc29jaW9zcXUgdG9ycXVlbnQgdmFyaXVzIGFyY3UuIE9k
aW8gbmV0dXMgZmVybWVudHVtIG5pc2wgbmF0b3F1ZSBwZXIgY3ViaWxpYSBoaW1lbmFlb3MgdXJu
YS4gRWxpdCB2ZW5lbmF0aXMgZXJvcyByaXN1cyB2dWxwdXRhdGUgZHVpcyBpbnRlcmR1bS4gTWF1
cmlzIGRpcyBhZCBwaGFyZXRyYSBkdWkgYXB0ZW50LiBJbXBlcmRpZXQgb3JjaSBhdWN0b3IgY29u
ZGltZW50dW0gYWRpcGlzY2luZyBhcmN1LiBOdWxsYW0gdGVtcG9yIG1hZ25hIG9kaW8gdm9sdXRw
YXQgbmlzaS4gQ29uZGltZW50dW0gc2NlbGVyaXNxdWUgcmlzdXMgZmF1Y2lidXM7IGR1aXMgc29s
bGljaXR1ZGluIG51bGxhbS4KCkNvbmRpbWVudHVtIGV4IGZyaW5naWxsYSB0ZW1wdXMgdWx0cmlj
aWVzIGNvbnNlY3RldHVyIG9kaW8gZWdlc3RhcyB2ZW5lbmF0aXMgdnVscHV0YXRlLiBTZW0gc2Fw
aWVuIHBlbGxlbnRlc3F1ZSBvcm5hcmU7IHZpdmVycmEgc2NlbGVyaXNxdWUgY29uc2VxdWF0LiBQ
bGF0ZWEgcG9zdWVyZSBtaSBzZW5lY3R1cyBmZWxpcyBkb2xvciBkaWN0dW1zdCBtYXR0aXMgdWx0
cmljaWVzIGVzdC4gTWFsZXN1YWRhIGVsaXQgYWRpcGlzY2luZyBzdXNwZW5kaXNzZSBlc3QgYS4g
SnVzdG8gbHVjdHVzIHR1cnBpcyB0ZW1wdXMgbGVvIGZpbmlidXMgcnV0cnVtIG5vc3RyYSBtYWdu
aXMgYWxpcXVhbS4gRmVsaXMgcG9zdWVyZSBtb2xlc3RpZSBwcmFlc2VudCBwb3J0YSBqdXN0byBu
aXNpLiBFdGlhbSBhcHRlbnQgY29udWJpYSBjb25zZWN0ZXR1ciBkaXM7IHBlbmF0aWJ1cyBlbGVt
ZW50dW0gdXQuCgpNb3JiaSBub3N0cmEgbmV0dXMgYWQgbnVuYyBlZ2VzdGFzIG5lcXVlIG5hc2Nl
dHVyIGZ1c2NlLiBDb25zZXF1YXQgdWx0cmljZXMgc2NlbGVyaXNxdWUgdXQgcmhvbmN1cyB2b2x1
dHBhdCwgaGVuZHJlcml0IGNvbnZhbGxpcyBtYXNzYS4gVG9ydG9yIGhpbWVuYWVvcyBudWxsYW0g
bWFsZXN1YWRhIHN1c3BlbmRpc3NlIGNvbnNlY3RldHVyIHBvdGVudGkgdm9sdXRwYXQuIEluIHN1
c2NpcGl0IHNpdCBtYXhpbXVzOyBsYWNpbmlhIGV1IG51bGxhIHBvcnRhLiBUZW1wdXMgY29tbW9k
byBsaWJlcm8gYXVjdG9yIGNyYXMgYmliZW5kdW0gc2l0LiBNZXR1cyBhcHRlbnQgbWF1cmlzIG5h
c2NldHVyIHZlbmVuYXRpcyBkaWFtIG1ldHVzLiBJbmNlcHRvcyBtaSB1bHRyaWNlcyBjbGFzcyBt
b3JiaSBkb25lYy4gT3JjaSBwdWx2aW5hciByaXN1cyBhY2N1bXNhbiBmYXVjaWJ1cyBsaXRvcmEg
ZHVpcyBjb21tb2RvIGNvbW1vZG8uCgpBbGlxdWFtIGV0aWFtIGhhYml0YXNzZSBmYW1lcyBtYXNz
YSwgaGVuZHJlcml0IHB1cnVzIGludGVyZHVtIG51bGxhbS4gQ3VyYWJpdHVyIHBsYWNlcmF0IHRv
cnRvciBwbGFjZXJhdCBuZWMgYWRpcGlzY2luZyBoYWJpdGFzc2UuIE1hc3NhIGxpdG9yYSBpbnRl
cmR1bSBwcmV0aXVtIGZ1c2NlIG5hc2NldHVyIHByaW1pcyBmYXVjaWJ1cyBtYWduYS4gRHVpIGN1
cmFiaXR1ciBtb2xlc3RpZSBqdXN0byB1bGxhbWNvcnBlciBsZW8gbW9sZXN0aWUgbWF1cmlzLiBJ
bmNlcHRvcyBtYWxlc3VhZGEgdGFjaXRpIGxpdG9yYSBsaWJlcm8gbWFlY2VuYXMgbmVxdWUgbmFt
IGxhb3JlZXQuIFRyaXN0aXF1ZSB2aXZhbXVzIGVnZXQgbWFlY2VuYXM7IHBvcnRhIGZhbWVzIGN1
cmFlIG1vbGxpcyBjcmFzLiBJcHN1bSB2dWxwdXRhdGUgdWxsYW1jb3JwZXIgZXQgbmFzY2V0dXIg
bmFzY2V0dXIgdmVsIG9yY2kuCgpDdXJhZSBldCBvZGlvOyB1cm5hIGVyb3MgcHVsdmluYXIgbWFs
ZXN1YWRhIGVnZXQgZGlnbmlzc2ltLiBDdXJzdXMgZmVybWVudHVtIGxlY3R1cyBwaGFzZWxsdXMg
dGVtcG9yIHBvc3VlcmUgdnVscHV0YXRlIHF1YW0uIE1hdXJpcyBjb21tb2RvIGNvbW1vZG8gbWF1
cmlzIGluY2VwdG9zIG1ldHVzIHZhcml1cyB1cm5hIHNhZ2l0dGlzLiBMaWd1bGEgZHVpcyBlcmF0
IGlwc3VtIHByZXRpdW0gZXUgZmFjaWxpc2lzLiBEdWlzIGxvYm9ydGlzIHByb2luIGZhY2lsaXNp
IGFkIHN1c2NpcGl0IHZlc3RpYnVsdW0gaGFjIHRvcnRvciBpbnRlcmR1bS4gVWx0cmljZXMgdGlu
Y2lkdW50IG1heGltdXMgYXB0ZW50IHBoYXNlbGx1cyBpbiB1bGxhbWNvcnBlciBuaXNsIHZhcml1
cyBkaXMuIFZlbmVuYXRpcyBlbmltIHBvdGVudGkgcG90ZW50aSBzb2RhbGVzIG1hc3NhIGlkIGVs
ZW1lbnR1bS4K