Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ name = "bench"
harness = false

[features]
#default = ["array_chunks"]
# depcrecated, will be removed in a future release
array_chunks = []
unsafe = []
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![API](https://docs.rs/base45/badge.svg)](https://docs.rs/base45)

A encoder/decoder for base45 that is fully compatible with
[draft-faltstrom-base45-02](https://www.ietf.org/id/draft-faltstrom-base45-02.txt). When encoding QR or Aztec codes a different scheme than the standard base64, base32, and base16 is needed.
[rfc9285](https://datatracker.ietf.org/doc/html/rfc9285). When encoding QR or Aztec codes, a scheme other than the standard base64, base32, and base16 is needed.

## Installation

Expand Down
15 changes: 5 additions & 10 deletions src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ fn decode_intl(input: &[u8]) -> Result<Vec<u8>, DecodeError> {
let mut output = Vec::with_capacity(match input.len() & 1 {
0 => input.len() * 2 / 3,
1 => 1 + input.len() * 2 / 3,
#[cfg(feature = "unsafe")]
// SAFETY: it's one of 0 or 1. There are no other options.
_ => unsafe { core::hint::unreachable_unchecked() },
#[cfg(not(feature = "unsafe"))]
_ => unreachable!(),
});

#[inline(always)]
Expand All @@ -58,17 +61,9 @@ fn decode_intl(input: &[u8]) -> Result<Vec<u8>, DecodeError> {
alphabet::decode(v).ok_or(DecodeError)
}

#[cfg(feature = "array_chunks")]
let (chunks, pre) = (input.array_chunks(), |&b| preproc(b));
#[cfg(not(feature = "array_chunks"))]
let (chunks, pre) = (input.chunks_exact(3), |slic: &[u8]| match slic {
&[a, b, c] => preproc([a, b, c]),
// SAFETY: chunks_exact ensures every `.next()` returns an effective &[T] where `.len` == 3
_ => unsafe { core::hint::unreachable_unchecked() },
});
let ((chunks, remainder), pre) = (input.as_chunks::<3>(), |&b| preproc(b));

let remainder = chunks.remainder();
for c in chunks.map(pre) {
for c in chunks.iter().map(pre) {
let xy = core_fn(c?)?;
output.extend_from_slice(&xy);
}
Expand Down
31 changes: 15 additions & 16 deletions src/encode.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
use crate::alphabet::{self, SIZE, SIZE_SIZE};

#[inline(always)]
fn divmod<const N: u32>(x: u32) -> (u32, u32) {
(x / N, x % N)
}

#[inline(always)]
fn ae(b: u8) -> u8 {
match alphabet::encode(b) {
Some(ch) => ch,
#[cfg(feature = "unsafe")]
// SAFETY: encode for this is highly unlikely to ever reach this point.
#[cfg(not(test))]
None => unsafe { core::hint::unreachable_unchecked() },
#[cfg(test)]
#[cfg(not(feature = "unsafe"))]
None => unreachable!(),
}
}

fn encode_buffer(input: &[u8]) -> String {
// setup
#[cfg(feature = "array_chunks")]
let input = input.array_chunks::<2>();
#[cfg(not(feature = "array_chunks"))]
let input = input.chunks_exact(2);
let (input, remainder) = input.as_chunks::<2>();

let mut s = Vec::with_capacity(input.len() + ((input.len() + 1) / 2));
let mut s = Vec::with_capacity(input.len() + input.len().div_ceil(2));

// Core function
#[inline(always)]
Expand All @@ -33,23 +33,22 @@ fn encode_buffer(input: &[u8]) -> String {
s.extend_from_slice(&[ae(c as _), ae(d as _), ae(e as _)]);
}
// take remainder AoT
let rem = input.remainder();
for c in input {
#[cfg(feature = "array_chunks")]
let c = *c;
// SAFETY: `chunks_exact` always returns exactly that number of items.
#[cfg(not(feature = "array_chunks"))]
let c = unsafe { [*c.get_unchecked(0), *c.get_unchecked(1)] };
core_fn(c, &mut s);
core_fn(*c, &mut s);
}
// Final byte
if let &[_0] = rem {
if let &[_0] = remainder {
let (d, c) = divmod::<SIZE>(_0 as u32);

s.extend_from_slice(&[ae(c as _), ae(d as _)]);
}
#[cfg(feature = "unsafe")]
// SAFETY: we control all bytes that enter this vector.
unsafe { String::from_utf8_unchecked(s) }
unsafe {
String::from_utf8_unchecked(s)
}
#[cfg(not(feature = "unsafe"))]
String::from_utf8(s).expect("All bytes encoded must be ascii")
}

/// Encode a string to base45
Expand Down
2 changes: 0 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![cfg_attr(feature = "array_chunks", feature(array_chunks))]
#![cfg_attr(test, feature(test))]
//! Encoder/decoder for base45 that is fully compatible with
//! [`draft-faltstrom-base45-07.txt`](https://www.ietf.org/archive/id/draft-faltstrom-base45-07.txt)
//!
Expand Down
31 changes: 0 additions & 31 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,34 +111,3 @@ fn decode_full_bytes() {
let s = decode("FGWFGWFGWFGW").unwrap();
assert_eq!(s, b"\xff\xff\xff\xff\xff\xff\xff\xff");
}

// cursed code, quickly becomes degenerate.

// fn rbd<const N: usize>(bench: &mut test::Bencher) {
// use rand::{distributions::*, *};
// use std::convert::TryFrom;
// let mut rng = thread_rng();
// let sample = Slice::new(&crate::alphabet::TABLE).unwrap();
// let b: Vec<u8> = sample.sample_iter(rng).copied().take(N).collect();
// let b: [u8; N] = TryFrom::try_from(b).unwrap();
// bench.iter(|| {
// let decoded = decode(&b[..]);
// assert!(decoded.is_ok());
// });
// }
// #[bench]
// fn bench_decode_random_3(b: &mut test::Bencher) {
// rbd::<3>(b);
// }
// #[bench]
// fn bench_decode_random_30(b: &mut test::Bencher) {
// rbd::<30>(b);
// }
// #[bench]
// fn bench_decode_random_3000(b: &mut test::Bencher) {
// rbd::<3000>(b);
// }
// #[bench]
// fn bench_decode_random_3002(b: &mut test::Bencher) {
// rbd::<3002>(b);
// }
Loading