encoding
This commit is contained in:
commit
2addbb8d55
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "base64"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
encode-rt = ["encode-sz"]
|
||||
encode-ct = ["encode-sz"]
|
||||
encode-sz = []
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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=");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue