Initial commit
This commit is contained in:
commit
66d5702da0
7 changed files with 273 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "b16"
|
||||
version = "0.1.0"
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "b16"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
Hexadecimal encoder and decoder written in Rust
|
131
src/decode.rs
Normal file
131
src/decode.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
use std::{
|
||||
fmt,
|
||||
io::{self, ErrorKind, Read, Write},
|
||||
num::TryFromIntError,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors which might possibly occur while decoding base64 encoded data
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
IllegalChar(char),
|
||||
IntError(TryFromIntError),
|
||||
MissingChar,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Io(e) => write!(f, "{e}"),
|
||||
Self::IllegalChar(c) => write!(f, "Illegal Character ({c})"),
|
||||
Self::IntError(e) => write!(f, "Int Error: {e}"),
|
||||
Self::MissingChar => write!(f, "Missing character"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Io(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for Error {
|
||||
fn from(value: char) -> Self {
|
||||
Self::IllegalChar(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TryFromIntError> for Error {
|
||||
fn from(value: TryFromIntError) -> Self {
|
||||
Self::IntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Io(e) => Some(e),
|
||||
Self::IntError(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Decoder<R: Read, W: Write> {
|
||||
reader: R,
|
||||
writer: W,
|
||||
}
|
||||
|
||||
impl<R: Read, W: Write> Decoder<R, W> {
|
||||
pub fn new(reader: R, writer: W) -> Self {
|
||||
Self { reader, writer }
|
||||
}
|
||||
|
||||
/// Decodes the bytes provided by `self.reader` and writes the resulting
|
||||
/// bytes into `self.writer`.
|
||||
/// # 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<(), Error> {
|
||||
loop {
|
||||
let mut in_buf = [0_u8; 2];
|
||||
let mut out_buf = [0_u8; 1];
|
||||
let mut n_bytes = 0;
|
||||
loop {
|
||||
n_bytes += match self.reader.read(&mut in_buf) {
|
||||
Ok(n) => n,
|
||||
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
break;
|
||||
}
|
||||
match n_bytes {
|
||||
0 => break,
|
||||
1 => return Err(Error::MissingChar),
|
||||
2 => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let mut c = char::from(in_buf[0]);
|
||||
if let Some(idx) = super::get_idx(c.to_ascii_uppercase()) {
|
||||
out_buf[0] |= (idx << 4) as u8;
|
||||
} else {
|
||||
return Err(Error::IllegalChar(c));
|
||||
}
|
||||
c = char::from(in_buf[1]);
|
||||
if let Some(idx) = super::get_idx(c.to_ascii_uppercase()) {
|
||||
out_buf[0] |= idx as u8;
|
||||
} else {
|
||||
return Err(Error::IllegalChar(c));
|
||||
}
|
||||
self.writer.write_all(&out_buf)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn decode() {
|
||||
let mut decoder = Decoder::new("48656C6C6F2C20576F726C64".as_bytes(), vec![]);
|
||||
decoder.decode().unwrap();
|
||||
assert_eq!(String::from_utf8(decoder.writer).unwrap(), "Hello, World");
|
||||
decoder = Decoder::new("48656C6C6F2C20576F726C6421".as_bytes(), vec![]);
|
||||
decoder.decode().unwrap();
|
||||
assert_eq!(String::from_utf8(decoder.writer).unwrap(), "Hello, World!");
|
||||
decoder = Decoder::new("48656C6C6F2C20576F726C64210A".as_bytes(), vec![]);
|
||||
decoder.decode().unwrap();
|
||||
assert_eq!(
|
||||
String::from_utf8(decoder.writer).unwrap(),
|
||||
"Hello, World!\n"
|
||||
);
|
||||
}
|
||||
}
|
111
src/encode.rs
Normal file
111
src/encode.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
use {
|
||||
super::B16_ALPHABET,
|
||||
std::{
|
||||
fmt::{self, Write},
|
||||
io::{self, ErrorKind, Read},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
Fmt(fmt::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Io(e) => write!(f, "{e}"),
|
||||
Self::Fmt(e) => write!(f, "{e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Io(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fmt::Error> for Error {
|
||||
fn from(value: fmt::Error) -> Self {
|
||||
Self::Fmt(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Encoder<R: Read, W: Write> {
|
||||
reader: R,
|
||||
writer: W,
|
||||
}
|
||||
|
||||
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.
|
||||
pub fn new(reader: R, writer: W) -> Self {
|
||||
Self { reader, writer }
|
||||
}
|
||||
|
||||
/// Encodes the given data as hexadecimal
|
||||
/// # Errors
|
||||
/// May return an error on IO failure
|
||||
pub fn encode(&mut self) -> Result<(), Error> {
|
||||
loop {
|
||||
let mut ibuf = [0; 1];
|
||||
let mut obuf = [' '; 2];
|
||||
let mut n_bytes = 0;
|
||||
loop {
|
||||
n_bytes += match self.reader.read(&mut ibuf) {
|
||||
Ok(n) => n,
|
||||
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
break;
|
||||
}
|
||||
if n_bytes == 0 {
|
||||
break;
|
||||
}
|
||||
let mut idx = usize::from(ibuf[0] & 0b1111);
|
||||
obuf[1] = B16_ALPHABET[idx];
|
||||
idx = usize::from((ibuf[0] & 0b1111_0000) >> 4);
|
||||
obuf[0] = B16_ALPHABET[idx];
|
||||
write!(self.writer, "{}{}", obuf[0], obuf[1])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Encoder<R, String> {
|
||||
pub fn output(self) -> String {
|
||||
self.writer
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn encode() {
|
||||
let mut encoder = Encoder::new("Hello, World".as_bytes(), String::new());
|
||||
encoder.encode().unwrap();
|
||||
assert_eq!(encoder.output(), "48656C6C6F2C20576F726C64");
|
||||
encoder = Encoder {
|
||||
reader: "Hello, World!".as_bytes(),
|
||||
writer: String::new(),
|
||||
};
|
||||
encoder.encode().unwrap();
|
||||
assert_eq!(encoder.output(), "48656C6C6F2C20576F726C6421");
|
||||
encoder = Encoder::new("Hello, World!\n".as_bytes(), String::new());
|
||||
encoder.encode().unwrap();
|
||||
assert_eq!(encoder.output(), "48656C6C6F2C20576F726C64210A");
|
||||
}
|
||||
}
|
16
src/lib.rs
Normal file
16
src/lib.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
pub mod decode;
|
||||
#[warn(clippy::all, clippy::pedantic)]
|
||||
pub mod encode;
|
||||
|
||||
pub static B16_ALPHABET: [char; 16] = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
||||
];
|
||||
|
||||
pub(crate) fn get_idx(c: char) -> Option<usize> {
|
||||
for (idx, x) in B16_ALPHABET.iter().enumerate() {
|
||||
if *x == c {
|
||||
return Some(idx);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
Loading…
Add table
Reference in a new issue