This commit is contained in:
aiden 2023-05-25 01:45:46 +01:00
commit 2addbb8d55
Signed by: aiden
GPG Key ID: EFA9C74AEBF806E0
6 changed files with 332 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/Cargo.lock

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "base64"
version = "0.1.0"
edition = "2021"
[features]
encode-rt = ["encode-sz"]
encode-ct = ["encode-sz"]
encode-sz = []

72
src/encode_ct.rs Normal file
View File

@ -0,0 +1,72 @@
use crate::{TABLE, encode_sz::encoder_output_size_usize_panic as comp_size};
pub const fn array_from<const N: usize>(input: &[u8]) -> [u8; N] {
if comp_size(input.len()) != N {
panic!("output length is not encoder_output_size_usize_panic(input)");
}
let mut input_idx = 0;
let mut output: [u8; N] = unsafe {
std::mem::MaybeUninit::uninit().assume_init()
};
let mut output_idx = 0;
macro_rules! push {
($ch: expr) => {
output[output_idx] = $ch;
output_idx += 1;
};
}
let mut prev = 0u8;
let mut remainder = 0u8;
let mut six_bits;
macro_rules! read_six_bits {
() => {
macro_rules! set_remainder {
($val: expr) => {
remainder = $val;
prev = prev.wrapping_add(1);
};
}
let prev_bits = (prev & 0b11) * 2;
let req = 6 - prev_bits;
if req == 0 {
six_bits = Some(remainder);
set_remainder!(0);
} else if input_idx == input.len() {
six_bits = None;
} else {
let curr_byte = input[input_idx];
input_idx += 1;
let consumed = curr_byte >> (8 - req);
six_bits = Some((remainder << (6 - prev_bits)) | consumed);
set_remainder!(curr_byte & (0xff >> req));
}
};
}
loop {
read_six_bits!();
let Some(idx) = six_bits else {
break;
};
push!(TABLE[idx as usize]);
}
let prev = (prev & 0b11) * 2;
if prev != 0 {
let req = 6 - prev;
let idx = remainder << req;
push!(TABLE[idx as usize]);
}
while output_idx < N {
output[output_idx] = b'=';
output_idx += 1;
}
return output;
}

165
src/encode_rt.rs Normal file
View File

@ -0,0 +1,165 @@
use crate::{TABLE, encode_sz::encoder_output_size_usize as comp_size};
fn encode<T: B64OutputTraitInternal>(input: &[u8], output: &mut T) -> Result<(), ()> {
if !output.size(
comp_size(input.len())
.and_then(|sz| usize::try_from(sz).ok())
.ok_or(())?
) {
Err(())?;
}
let mut iter = input.iter();
let mut prev = 0u8;
let mut remainder = 0u8;
let mut read_six_bits = || {
macro_rules! set_remainder {
($val: expr) => {
remainder = $val;
prev = prev.wrapping_add(1);
};
}
let prev_bits = (prev & 0b11) * 2;
let req = 6 - prev_bits;
if req == 0 {
let six_bits = Some(remainder);
set_remainder!(0);
return six_bits;
}
let curr_byte = iter.next()?;
let consumed = curr_byte >> (8 - req);
let six_bits = Some((remainder << (6 - prev_bits)) | consumed);
set_remainder!(curr_byte & (0xff >> req));
return six_bits;
};
while let Some(idx) = read_six_bits() {
output.push(TABLE[idx as usize]);
}
let prev = (prev & 0b11) * 2;
if prev != 0 {
let req = 6 - prev;
let idx = remainder << req;
output.push(TABLE[idx as usize]);
}
output.pad();
return Ok(());
}
pub struct B64Output<T>(Option<T>, usize);
pub trait B64OutputTrait<O> {
fn encode<T: AsRef<[u8]>>(self, input: T) -> Result<O, ()>;
}
trait B64OutputTraitInternal {
fn push(&mut self, byte: u8);
fn pad(&mut self);
fn size(&mut self, size: usize) -> bool;
}
impl B64Output<Vec<u8>> {
pub fn to_vec() -> Self {
return Self(None, 0);
}
}
impl B64Output<String> {
pub fn to_string() -> Self {
return Self(None, 0);
}
}
impl<'a> B64Output<&'a mut [u8]> {
pub fn slice(slice: &'a mut [u8]) -> Self {
return Self(Some(slice), 0);
}
}
impl B64OutputTraitInternal for B64Output<Vec<u8>> {
#[inline]
fn push(&mut self, byte: u8) {
let vec = self.0.as_mut().unwrap();
vec.push(byte);
return;
}
#[inline]
fn pad(&mut self) {
let vec = self.0.as_mut().unwrap();
vec.resize(self.1, b'=');
return;
}
#[inline]
fn size(&mut self, size: usize) -> bool {
assert_eq!(self.0, None);
self.0 = Some(Vec::with_capacity(size));
self.1 = size;
return true;
}
}
impl B64OutputTraitInternal for B64Output<String> {
#[inline]
fn push(&mut self, byte: u8) {
let string = self.0.as_mut().unwrap();
string.push(byte as char);
return;
}
#[inline]
fn pad(&mut self) {
let string = self.0.as_mut().unwrap();
for _ in string.len()..self.1 {
string.push('=');
}
return;
}
#[inline]
fn size(&mut self, size: usize) -> bool {
assert_eq!(self.0, None);
self.0 = Some(String::with_capacity(size));
self.1 = size;
return true;
}
}
impl B64OutputTraitInternal for B64Output<&mut [u8]> {
#[inline]
fn push(&mut self, byte: u8) {
let sl = self.0.as_mut().unwrap();
sl[self.1] = byte;
self.1 += 1;
return;
}
#[inline]
fn pad(&mut self) {
let sl = self.0.as_mut().unwrap();
let cap = sl.len();
sl[self.1..cap].fill(b'=');
return;
}
#[inline]
fn size(&mut self, size: usize) -> bool {
let sl = self.0.as_ref().unwrap();
return sl.len() == size;
}
}
impl B64OutputTrait<Vec<u8>> for B64Output<Vec<u8>> {
fn encode<I: AsRef<[u8]>>(mut self, input: I) -> Result<Vec<u8>, ()> {
encode(input.as_ref(), &mut self)?;
return self.0.ok_or(());
}
}
impl B64OutputTrait<String> for B64Output<String> {
fn encode<I: AsRef<[u8]>>(mut self, input: I) -> Result<String, ()> {
encode(input.as_ref(), &mut self)?;
return self.0.ok_or(());
}
}
impl B64OutputTrait<()> for B64Output<&mut [u8]> {
fn encode<I: AsRef<[u8]>>(mut self, input: I) -> Result<(), ()> {
return encode(input.as_ref(), &mut self);
}
}

30
src/encode_sz.rs Normal file
View File

@ -0,0 +1,30 @@
pub fn encoder_output_size(count: u32) -> u64 {
return if count == 0 {
0
} else {
((count as u64 + 2) * 0x55555556) >> 30 & !3
}
}
pub fn encoder_output_size_usize(count: usize) -> Option<u64> {
return if usize::BITS < u32::BITS || count <= u32::MAX as usize {
Some(encoder_output_size(count as u32))
} else {
count.try_into().ok()
.and_then(|count: u64| {
let mut d = count % 3;
if d != 0 {
d = 3 - d;
}
return count.checked_add(d);
})
.and_then(|count| count.checked_div(3))
.and_then(|count| count.checked_mul(4))
};
}
pub const fn encoder_output_size_usize_panic(count: usize) -> usize {
let mut d = count % 3;
if d != 0 {
d = 3 - d;
}
return (count + d) / 3 * 4;
}

54
src/lib.rs Normal file
View File

@ -0,0 +1,54 @@
// aiden@cmp.bz
// currently only implements encoding
const TABLE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#[cfg(feature = "encode-sz")]
pub mod encode_sz;
#[cfg(feature = "encode-rt")]
pub mod encode_rt;
#[cfg(feature = "encode-ct")]
pub mod encode_ct;
#[cfg(test)]
mod tests {
use {super::*, std::assert_eq};
#[cfg(feature = "encode-sz")]
#[test]
fn encode_sz() {
use encode_sz::*;
assert_eq!(encoder_output_size(5), 8);
assert_eq!(encoder_output_size_usize(8).unwrap(), 12);
assert_eq!(encoder_output_size_usize_panic(12), 16);
}
#[cfg(feature = "encode-rt")]
#[test]
fn encode_rt() {
use {encode_sz::*, encode_rt::*};
const INP: &[u8] = &*b"encode_rt test input string";
let mut out = [0u8; encoder_output_size_usize_panic(INP.len())];
B64Output::slice(&mut(out)).encode(INP).unwrap();
assert_eq!(out, *b"ZW5jb2RlX3J0IHRlc3QgaW5wdXQgc3RyaW5n");
let vec = B64Output::to_vec().encode(INP).unwrap();
assert_eq!(vec.as_slice(), out);
let string = B64Output::to_string().encode(INP).unwrap();
assert_eq!(string.as_bytes(), out);
}
#[cfg(feature = "encode-ct")]
#[test]
fn encode_ct() {
use {encode_sz::*, encode_ct::*};
const INP: &[u8] = &*b"hello";
const OUT: [u8; encoder_output_size_usize_panic(INP.len())] = array_from(INP);
assert_eq!(OUT, *b"aGVsbG8=");
}
}