diff --git a/lib/Cargo.toml b/lib/Cargo.toml index aee1025..1665c2d 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -21,3 +21,4 @@ rustdoc-args = ["--cfg=docsrs"] default = ["std"] alloc = [] std = ["alloc"] +v3-preview = [] diff --git a/lib/benches/lib.rs b/lib/benches/lib.rs index 761d878..ea6ed0a 100644 --- a/lib/benches/lib.rs +++ b/lib/benches/lib.rs @@ -1,9 +1,17 @@ #![feature(test)] -extern crate data_encoding; extern crate test; -use data_encoding::{Specification, BASE32, BASE32_DNSCURVE, BASE64, BASE64_NOPAD, HEXLOWER}; +#[cfg(feature = "v3-preview")] +use std::convert::TryFrom as _; + +#[cfg(not(feature = "v3-preview"))] +use data_encoding as constants; +#[cfg(feature = "v3-preview")] +use data_encoding::v3_preview as constants; +#[cfg(feature = "v3-preview")] +use data_encoding::v3_preview::{Bit1, Bit2, Bit3, Bit6, Encoding, False, True}; +use data_encoding::Specification; use test::Bencher; #[bench] @@ -13,6 +21,8 @@ fn base02_encode_base(b: &mut Bencher) { let mut spec = Specification::new(); spec.symbols.push_str("01"); let base = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let base = Encoding::::try_from(base).unwrap(); b.iter(|| base.encode_mut(input, output)); } @@ -23,6 +33,8 @@ fn base02_decode_base(b: &mut Bencher) { let mut spec = Specification::new(); spec.symbols.push_str("01"); let base = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let base = Encoding::::try_from(base).unwrap(); b.iter(|| base.decode_mut(input, output)); } @@ -33,6 +45,8 @@ fn base04_encode_base(b: &mut Bencher) { let mut spec = Specification::new(); spec.symbols.push_str("0123"); let base = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let base = Encoding::::try_from(base).unwrap(); b.iter(|| base.encode_mut(input, output)); } @@ -43,6 +57,8 @@ fn base04_decode_base(b: &mut Bencher) { let mut spec = Specification::new(); spec.symbols.push_str("0123"); let base = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let base = Encoding::::try_from(base).unwrap(); b.iter(|| base.decode_mut(input, output)); } @@ -53,6 +69,8 @@ fn base08_encode_base(b: &mut Bencher) { let mut spec = Specification::new(); spec.symbols.push_str("01234567"); let base = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let base = Encoding::::try_from(base).unwrap(); b.iter(|| base.encode_mut(input, output)); } @@ -63,6 +81,8 @@ fn base08_decode_base(b: &mut Bencher) { let mut spec = Specification::new(); spec.symbols.push_str("01234567"); let base = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let base = Encoding::::try_from(base).unwrap(); b.iter(|| base.decode_mut(input, output)); } @@ -70,49 +90,49 @@ fn base08_decode_base(b: &mut Bencher) { fn base16_encode_base(b: &mut Bencher) { let input = &[0u8; 4096]; let output = &mut [0u8; 8192]; - b.iter(|| HEXLOWER.encode_mut(input, output)); + b.iter(|| constants::HEXLOWER.encode_mut(input, output)); } #[bench] fn base16_decode_base(b: &mut Bencher) { let input = &[b'0'; 4096]; let output = &mut [0u8; 2048]; - b.iter(|| HEXLOWER.decode_mut(input, output)); + b.iter(|| constants::HEXLOWER.decode_mut(input, output)); } #[bench] fn base32_encode_base(b: &mut Bencher) { let input = &[0u8; 4096]; let output = &mut [0u8; 6560]; - b.iter(|| BASE32.encode_mut(input, output)); + b.iter(|| constants::BASE32.encode_mut(input, output)); } #[bench] fn base32_decode_base(b: &mut Bencher) { let input = &[b'A'; 4096]; let output = &mut [0u8; 2560]; - b.iter(|| BASE32.decode_mut(input, output)); + b.iter(|| constants::BASE32.decode_mut(input, output)); } #[bench] fn base64_encode_base(b: &mut Bencher) { let input = &[0u8; 4096]; let output = &mut [0u8; 5462]; - b.iter(|| BASE64_NOPAD.encode_mut(input, output)); + b.iter(|| constants::BASE64_NOPAD.encode_mut(input, output)); } #[bench] fn base64_decode_base(b: &mut Bencher) { let input = &[b'A'; 4096]; let output = &mut [0u8; 3072]; - b.iter(|| BASE64_NOPAD.decode_mut(input, output)); + b.iter(|| constants::BASE64_NOPAD.decode_mut(input, output)); } #[bench] fn base64_encode_pad(b: &mut Bencher) { let input = &mut [b'A'; 4096]; let output = &mut [0u8; 5464]; - b.iter(|| BASE64.encode_mut(input, output)); + b.iter(|| constants::BASE64.encode_mut(input, output)); } #[bench] @@ -126,17 +146,19 @@ fn base64_decode_pad(b: &mut Bencher) { } } let output = &mut [0u8; 3072]; - b.iter(|| BASE64.decode_mut(input, output).unwrap()); + b.iter(|| constants::BASE64.decode_mut(input, output).unwrap()); } #[bench] fn base64_encode_wrap(b: &mut Bencher) { let input = &[0u8; 4096]; let output = &mut [0u8; 5608]; - let mut spec = BASE64.specification(); + let mut spec = data_encoding::BASE64.specification(); spec.wrap.width = 76; spec.wrap.separator.push_str("\r\n"); let base64 = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let base64 = Encoding::::try_from(base64).unwrap(); b.iter(|| base64.encode_mut(input, output)); } @@ -148,10 +170,12 @@ fn base64_decode_wrap(b: &mut Bencher) { input[x + 3] = b'\n'; } let output = &mut [0u8; 3072]; - let mut spec = BASE64.specification(); + let mut spec = data_encoding::BASE64.specification(); spec.wrap.width = 76; spec.wrap.separator.push_str("\r\n"); let base64 = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let base64 = Encoding::::try_from(base64).unwrap(); b.iter(|| base64.decode_mut(input, output).unwrap()); } @@ -159,12 +183,12 @@ fn base64_decode_wrap(b: &mut Bencher) { fn dnscurve_decode_base(b: &mut Bencher) { let input = &[b'0'; 4096]; let output = &mut [0u8; 2560]; - b.iter(|| BASE32_DNSCURVE.decode_mut(input, output)); + b.iter(|| constants::BASE32_DNSCURVE.decode_mut(input, output)); } #[bench] fn dnscurve_encode_base(b: &mut Bencher) { let input = &[0u8; 4096]; let output = &mut [0u8; 6554]; - b.iter(|| BASE32_DNSCURVE.encode_mut(input, output)); + b.iter(|| constants::BASE32_DNSCURVE.encode_mut(input, output)); } diff --git a/lib/fuzz/Cargo.toml b/lib/fuzz/Cargo.toml index ff9e982..33f2b5a 100644 --- a/lib/fuzz/Cargo.toml +++ b/lib/fuzz/Cargo.toml @@ -29,3 +29,10 @@ name = "encode_write" path = "fuzz_targets/encode_write.rs" test = false doc = false + +[[bin]] +name = "v3-preview" +path = "fuzz_targets/v3-preview.rs" +test = false +doc = false +required-features = ["data-encoding/v3-preview"] diff --git a/lib/fuzz/examples/analyze.rs b/lib/fuzz/examples/analyze.rs index 6e834c2..a89819b 100644 --- a/lib/fuzz/examples/analyze.rs +++ b/lib/fuzz/examples/analyze.rs @@ -1,6 +1,3 @@ -extern crate data_encoding; -extern crate data_encoding_fuzz; - use std::collections::HashMap; use std::ops::AddAssign; diff --git a/lib/fuzz/examples/debug.rs b/lib/fuzz/examples/debug.rs index f28648b..4f2b101 100644 --- a/lib/fuzz/examples/debug.rs +++ b/lib/fuzz/examples/debug.rs @@ -1,5 +1,3 @@ -extern crate data_encoding_fuzz; - use std::io::Read; use data_encoding_fuzz::generate_specification; diff --git a/lib/fuzz/fuzz_targets/v3-preview.rs b/lib/fuzz/fuzz_targets/v3-preview.rs new file mode 100644 index 0000000..80112cf --- /dev/null +++ b/lib/fuzz/fuzz_targets/v3-preview.rs @@ -0,0 +1,98 @@ +#![no_main] + +use data_encoding::v3_preview::{Bit1, Bit2, Bit3, Bit4, Bit5, Bit6, Encoding, False, True}; +use data_encoding_fuzz::{decode_prefix, generate_encoding}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let mut data = data; + let dyn_base = generate_encoding(&mut data); + let mut count = 0; + macro_rules! test { + ($Bit:ident, $Msb:ident, $Pad:ident, $Wrap:ident, $Ignore:ident) => { + if let Ok(base) = <&Encoding<$Bit, $Msb, $Pad, $Wrap, $Ignore>>::try_from(&dyn_base) { + count += 1; + let encoded = base.encode(data); + assert_eq!(encoded, dyn_base.encode(data)); + assert_eq!(base.decode(encoded.as_bytes()).unwrap(), data); + if dyn_base.is_canonical() { + let raw = decode_prefix(&dyn_base, &mut data); + assert_eq!(base.encode(&raw).as_bytes(), data); + } + } + }; + } + test!(Bit1, False, False, False, False); + test!(Bit1, False, False, False, True); + test!(Bit1, False, False, True, True); + test!(Bit1, False, True, False, False); + test!(Bit1, False, True, False, True); + test!(Bit1, False, True, True, True); + test!(Bit1, True, False, False, False); + test!(Bit1, True, False, False, True); + test!(Bit1, True, False, True, True); + test!(Bit1, True, True, False, False); + test!(Bit1, True, True, False, True); + test!(Bit1, True, True, True, True); + test!(Bit2, False, False, False, False); + test!(Bit2, False, False, False, True); + test!(Bit2, False, False, True, True); + test!(Bit2, False, True, False, False); + test!(Bit2, False, True, False, True); + test!(Bit2, False, True, True, True); + test!(Bit2, True, False, False, False); + test!(Bit2, True, False, False, True); + test!(Bit2, True, False, True, True); + test!(Bit2, True, True, False, False); + test!(Bit2, True, True, False, True); + test!(Bit2, True, True, True, True); + test!(Bit3, False, False, False, False); + test!(Bit3, False, False, False, True); + test!(Bit3, False, False, True, True); + test!(Bit3, False, True, False, False); + test!(Bit3, False, True, False, True); + test!(Bit3, False, True, True, True); + test!(Bit3, True, False, False, False); + test!(Bit3, True, False, False, True); + test!(Bit3, True, False, True, True); + test!(Bit3, True, True, False, False); + test!(Bit3, True, True, False, True); + test!(Bit3, True, True, True, True); + test!(Bit4, False, False, False, False); + test!(Bit4, False, False, False, True); + test!(Bit4, False, False, True, True); + test!(Bit4, False, True, False, False); + test!(Bit4, False, True, False, True); + test!(Bit4, False, True, True, True); + test!(Bit4, True, False, False, False); + test!(Bit4, True, False, False, True); + test!(Bit4, True, False, True, True); + test!(Bit4, True, True, False, False); + test!(Bit4, True, True, False, True); + test!(Bit4, True, True, True, True); + test!(Bit5, False, False, False, False); + test!(Bit5, False, False, False, True); + test!(Bit5, False, False, True, True); + test!(Bit5, False, True, False, False); + test!(Bit5, False, True, False, True); + test!(Bit5, False, True, True, True); + test!(Bit5, True, False, False, False); + test!(Bit5, True, False, False, True); + test!(Bit5, True, False, True, True); + test!(Bit5, True, True, False, False); + test!(Bit5, True, True, False, True); + test!(Bit5, True, True, True, True); + test!(Bit6, False, False, False, False); + test!(Bit6, False, False, False, True); + test!(Bit6, False, False, True, True); + test!(Bit6, False, True, False, False); + test!(Bit6, False, True, False, True); + test!(Bit6, False, True, True, True); + test!(Bit6, True, False, False, False); + test!(Bit6, True, False, False, True); + test!(Bit6, True, False, True, True); + test!(Bit6, True, True, False, False); + test!(Bit6, True, True, False, True); + test!(Bit6, True, True, True, True); + assert_eq!(count, 1); +}); diff --git a/lib/fuzz/src/lib.rs b/lib/fuzz/src/lib.rs index d196554..baa3e13 100644 --- a/lib/fuzz/src/lib.rs +++ b/lib/fuzz/src/lib.rs @@ -1,5 +1,3 @@ -extern crate data_encoding; - use data_encoding::{DecodePartial, Encoding, Specification}; pub fn generate_encoding(data: &mut &[u8]) -> Encoding { diff --git a/lib/src/lib.rs b/lib/src/lib.rs index ed8b194..a18e6c2 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -184,6 +184,9 @@ macro_rules! check { }; } +#[cfg(feature = "v3-preview")] +pub mod v3_preview; + trait Static: Copy { fn val(self) -> T; } @@ -255,12 +258,12 @@ macro_rules! dispatch { ($body: expr) => { $body }; } -unsafe fn chunk_unchecked(x: &[u8], n: usize, i: usize) -> &[u8] { +unsafe fn chunk_unchecked(x: &[T], n: usize, i: usize) -> &[T] { debug_assert!((i + 1) * n <= x.len()); unsafe { core::slice::from_raw_parts(x.as_ptr().add(n * i), n) } } -unsafe fn chunk_mut_unchecked(x: &mut [u8], n: usize, i: usize) -> &mut [u8] { +unsafe fn chunk_mut_unchecked(x: &mut [T], n: usize, i: usize) -> &mut [T] { debug_assert!((i + 1) * n <= x.len()); unsafe { core::slice::from_raw_parts_mut(x.as_mut_ptr().add(n * i), n) } } @@ -273,6 +276,7 @@ fn floor(x: usize, m: usize) -> usize { x / m * m } +#[inline] fn vectorize(n: usize, bs: usize, mut f: F) { for k in 0 .. n / bs { for i in k * bs .. (k + 1) * bs { @@ -362,6 +366,7 @@ fn order(msb: bool, n: usize, i: usize) -> usize { } } +#[inline] fn enc(bit: usize) -> usize { match bit { 1 | 2 | 4 => 1, @@ -371,6 +376,7 @@ fn enc(bit: usize) -> usize { } } +#[inline] fn dec(bit: usize) -> usize { enc(bit) * 8 / bit } @@ -880,6 +886,7 @@ pub type InternalEncoding = &'static [u8]; // - width % dec(bit) == 0 // - for all x in separator values[x] is IGNORE #[derive(Debug, Clone, PartialEq, Eq)] +#[repr(transparent)] pub struct Encoding(#[doc(hidden)] pub InternalEncoding); /// How to translate characters when decoding diff --git a/lib/src/v3_preview.rs b/lib/src/v3_preview.rs new file mode 100644 index 0000000..bf6bcde --- /dev/null +++ b/lib/src/v3_preview.rs @@ -0,0 +1,1275 @@ +//! Provides an unstable preview of the 3.0.0 version. +//! +//! This module is gated by the `v3-preview` feature. This feature and this module are unstable in +//! the sense that breaking changes are only considered minor changes instead of major changes. In +//! particular, you should use the tilde requirement `~2.6.0` instead of the default caret +//! requirement `2.6.0` (or explicitly `^2.6.0`). For more information, consult the [SemVer +//! compatibility][semver] section of the Cargo Book. +//! +//! This feature and this module also have a different MSRV: 1.70 instead of 1.48. +//! +//! [semver]: https://doc.rust-lang.org/cargo/reference/resolver.html#semver-compatibility + +#![warn(let_underscore_drop)] +#![warn(unsafe_op_in_unsafe_fn)] +#![allow(clippy::incompatible_msrv)] +#![allow(clippy::match_bool)] + +#[cfg(feature = "alloc")] +use alloc::borrow::Cow; +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +use core::convert::{TryFrom, TryInto as _}; +use core::marker::PhantomData; +use core::mem::MaybeUninit; + +#[cfg(feature = "alloc")] +pub use crate::Specification; +use crate::{ + chunk_mut_unchecked, chunk_unchecked, dec, div_ceil, enc, floor, order, vectorize, + InternalEncoding, IGNORE, PADDING, +}; +pub use crate::{DecodeError, DecodeKind, DecodePartial, Encoding as DynEncoding}; + +/// Type-level bit-width of an encoding. +pub trait BitWidth: sealed::BitWidth {} + +/// Type-level bool. +pub trait Bool: sealed::Bool {} + +mod sealed { + pub trait BitWidth { + const VAL: usize; + } + + pub trait Bool { + type If: Copy; + const VAL: bool; + fn open(cond: Self::If) -> If; + fn make( + then: impl FnOnce() -> Then, else_: impl FnOnce() -> Else, + ) -> Self::If; + } + + #[derive(Debug, Copy, Clone)] + pub enum If { + Then(Then), + Else(Else), + } +} +use sealed::If; + +macro_rules! new_bit_width { + ($(($N:ident, $v:expr, $b:literal),)*) => { + $( + #[doc = concat!(" Bit-width of ", $b, " encodings.")] + #[derive(Debug)] + pub enum $N {} + impl BitWidth for $N {} + impl sealed::BitWidth for $N { const VAL: usize = $v; } + )* + }; +} +new_bit_width![ + (Bit1, 1, "base2"), + (Bit2, 2, "base4"), + (Bit3, 3, "base8"), + (Bit4, 4, "base16"), + (Bit5, 5, "base32"), + (Bit6, 6, "base64"), +]; + +/// Type-level false. +#[derive(Debug)] +pub enum False {} +impl Bool for False {} +impl sealed::Bool for False { + type If = Else; + const VAL: bool = false; + fn open(cond: Self::If) -> If { + If::Else(cond) + } + fn make( + _then: impl FnOnce() -> Then, else_: impl FnOnce() -> Else, + ) -> Self::If { + else_() + } +} + +/// Type-level true. +#[derive(Debug, Copy, Clone)] +pub enum True {} +impl Bool for True {} +impl sealed::Bool for True { + type If = Then; + const VAL: bool = true; + fn open(cond: Self::If) -> If { + If::Then(cond) + } + fn make( + then: impl FnOnce() -> Then, _else: impl FnOnce() -> Else, + ) -> Self::If { + then() + } +} + +// TODO(https://github.com/rust-lang/rust/issues/79995): Use write_slice() instead. +unsafe fn copy_from_slice(dst: &mut [MaybeUninit], src: &[u8]) { + dst.copy_from_slice(unsafe { &*(src as *const [u8] as *const [MaybeUninit]) }); +} + +// TODO(https://github.com/rust-lang/rust/issues/63569): Use slice_assume_init_mut() instead. +unsafe fn slice_assume_init_mut(xs: &mut [MaybeUninit]) -> &mut [u8] { + unsafe { &mut *(xs as *mut [MaybeUninit] as *mut [u8]) } +} + +unsafe fn slice_uninit_mut(xs: &mut [u8]) -> &mut [MaybeUninit] { + unsafe { &mut *(xs as *mut [u8] as *mut [MaybeUninit]) } +} + +#[cfg(feature = "alloc")] +fn reserve_spare(xs: &mut Vec, n: usize) -> &mut [MaybeUninit] { + xs.reserve(n); + &mut xs.spare_capacity_mut()[.. n] +} + +fn encode_len(len: usize) -> usize { + div_ceil(8 * len, Bit::VAL) +} + +fn encode_block( + symbols: &[u8; 256], input: &[u8], output: &mut [MaybeUninit], +) { + debug_assert!(input.len() <= enc(Bit::VAL)); + debug_assert_eq!(output.len(), encode_len::(input.len())); + let bit = Bit::VAL; + let msb = Msb::VAL; + let mut x = 0u64; + for (i, input) in input.iter().enumerate() { + x |= u64::from(*input) << (8 * order(msb, enc(bit), i)); + } + for (i, output) in output.iter_mut().enumerate() { + let y = x >> (bit * order(msb, dec(bit), i)); + let _ = output.write(symbols[(y & 0xff) as usize]); + } +} + +fn encode_mut( + symbols: &[u8; 256], input: &[u8], output: &mut [MaybeUninit], +) { + debug_assert_eq!(output.len(), encode_len::(input.len())); + let bit = Bit::VAL; + let enc = enc(bit); + let dec = dec(bit); + let n = input.len() / enc; + let bs = match bit { + 5 => 2, + 6 => 4, + _ => 1, + }; + vectorize(n, bs, |i| { + let input = unsafe { chunk_unchecked(input, enc, i) }; + let output = unsafe { chunk_mut_unchecked(output, dec, i) }; + encode_block::(symbols, input, output); + }); + encode_block::(symbols, &input[enc * n ..], &mut output[dec * n ..]); +} + +// Fails if an input character does not translate to a symbol. The error is the +// lowest index of such character. The output is not written to. +fn decode_block( + values: &[u8; 256], input: &[u8], output: &mut [MaybeUninit], +) -> Result<(), usize> { + debug_assert!(output.len() <= enc(Bit::VAL)); + debug_assert_eq!(input.len(), encode_len::(output.len())); + let bit = Bit::VAL; + let msb = Msb::VAL; + let mut x = 0u64; + for j in 0 .. input.len() { + let y = values[input[j] as usize]; + check!(j, y < 1 << bit); + x |= u64::from(y) << (bit * order(msb, dec(bit), j)); + } + for (j, output) in output.iter_mut().enumerate() { + let _ = output.write((x >> (8 * order(msb, enc(bit), j)) & 0xff) as u8); + } + Ok(()) +} + +// Fails if an input character does not translate to a symbol. The error `pos` +// is the lowest index of such character. The output is valid up to `pos / dec * +// enc` excluded. +fn decode_mut( + values: &[u8; 256], input: &[u8], output: &mut [MaybeUninit], +) -> Result<(), usize> { + debug_assert_eq!(input.len(), encode_len::(output.len())); + let bit = Bit::VAL; + let enc = enc(bit); + let dec = dec(bit); + let n = input.len() / dec; + for i in 0 .. n { + let input = unsafe { chunk_unchecked(input, dec, i) }; + let output = unsafe { chunk_mut_unchecked(output, enc, i) }; + decode_block::(values, input, output).map_err(|e| dec * i + e)?; + } + decode_block::(values, &input[dec * n ..], &mut output[enc * n ..]) + .map_err(|e| dec * n + e) +} + +// Fails if there are non-zero trailing bits. +fn check_trail( + ctb: bool, values: &[u8; 256], input: &[u8], +) -> Result<(), ()> { + if 8 % Bit::VAL == 0 || !ctb { + return Ok(()); + } + let trail = Bit::VAL * input.len() % 8; + if trail == 0 { + return Ok(()); + } + let mut mask = (1 << trail) - 1; + if !Msb::VAL { + mask <<= Bit::VAL - trail; + } + check!((), values[input[input.len() - 1] as usize] & mask == 0); + Ok(()) +} + +// Fails if the padding length is invalid. The error is the index of the first +// padding character. +fn check_pad(values: &[u8; 256], input: &[u8]) -> Result { + let bit = Bit::VAL; + debug_assert_eq!(input.len(), dec(bit)); + let is_pad = |x: &&u8| values[**x as usize] == PADDING; + let count = input.iter().rev().take_while(is_pad).count(); + let len = input.len() - count; + check!(len, len > 0 && bit * len % 8 < bit); + Ok(len) +} + +fn encode_base_len(len: usize) -> usize { + encode_len::(len) +} + +fn encode_base( + symbols: &[u8; 256], input: &[u8], output: &mut [MaybeUninit], +) { + debug_assert_eq!(output.len(), encode_base_len::(input.len())); + encode_mut::(symbols, input, output); +} + +fn encode_pad_len(len: usize) -> usize { + match Pad::VAL { + false => encode_base_len::(len), + true => div_ceil(len, enc(Bit::VAL)) * dec(Bit::VAL), + } +} + +fn encode_pad( + symbols: &[u8; 256], pad: Pad::If, input: &[u8], output: &mut [MaybeUninit], +) { + let pad = match Pad::open(pad) { + If::Then(x) => x, + If::Else(()) => return encode_base::(symbols, input, output), + }; + debug_assert_eq!(output.len(), encode_pad_len::(input.len())); + let olen = encode_base_len::(input.len()); + encode_base::(symbols, input, &mut output[.. olen]); + for output in output.iter_mut().skip(olen) { + let _ = output.write(pad); + } +} + +fn encode_wrap_len( + wrap: Wrap::If<(usize, &[u8]), ()>, ilen: usize, +) -> usize { + let olen = encode_pad_len::(ilen); + match Wrap::open(wrap) { + If::Then((col, end)) => olen + end.len() * div_ceil(olen, col), + If::Else(()) => olen, + } +} + +fn encode_wrap_mut( + symbols: &[u8; 256], pad: Pad::If, wrap: Wrap::If<(usize, &[u8]), ()>, input: &[u8], + output: &mut [MaybeUninit], +) { + let (col, end) = match Wrap::open(wrap) { + If::Then((col, end)) => (col, end), + If::Else(()) => return encode_pad::(symbols, pad, input, output), + }; + debug_assert_eq!(output.len(), encode_wrap_len::(wrap, input.len())); + debug_assert_eq!(col % dec(Bit::VAL), 0); + let bit = Bit::VAL; + let col = col / dec(bit); + let enc = col * enc(bit); + let dec = col * dec(bit) + end.len(); + let olen = dec - end.len(); + let n = input.len() / enc; + for i in 0 .. n { + let input = unsafe { chunk_unchecked(input, enc, i) }; + let output = unsafe { chunk_mut_unchecked(output, dec, i) }; + encode_base::(symbols, input, &mut output[.. olen]); + unsafe { copy_from_slice(&mut output[olen ..], end) }; + } + if input.len() > enc * n { + let olen = dec * n + encode_pad_len::(input.len() - enc * n); + encode_pad::(symbols, pad, &input[enc * n ..], &mut output[dec * n .. olen]); + unsafe { copy_from_slice(&mut output[olen ..], end) }; + } +} + +// Returns the longest valid input length and associated output length. +fn decode_wrap_len(len: usize) -> (usize, usize) { + let bit = Bit::VAL; + if Pad::VAL { + (floor(len, dec(bit)), len / dec(bit) * enc(bit)) + } else { + let trail = bit * len % 8; + (len - trail / bit, bit * len / 8) + } +} + +// Fails with Length if length is invalid. The error is the largest valid +// length. +fn decode_pad_len(len: usize) -> Result { + let (ilen, olen) = decode_wrap_len::(len); + check!(DecodeError { position: ilen, kind: DecodeKind::Length }, ilen == len); + Ok(olen) +} + +// Fails with Length if length is invalid. The error is the largest valid +// length. +fn decode_base_len(len: usize) -> Result { + decode_pad_len::(len) +} + +// Fails with Symbol if an input character does not translate to a symbol. The +// error is the lowest index of such character. +// Fails with Trailing if there are non-zero trailing bits. +fn decode_base_mut( + ctb: bool, values: &[u8; 256], input: &[u8], output: &mut [MaybeUninit], +) -> Result { + debug_assert_eq!(Ok(output.len()), decode_base_len::(input.len())); + let bit = Bit::VAL; + let fail = |pos, kind| DecodePartial { + read: pos / dec(bit) * dec(bit), + written: pos / dec(bit) * enc(bit), + error: DecodeError { position: pos, kind }, + }; + decode_mut::(values, input, output).map_err(|pos| fail(pos, DecodeKind::Symbol))?; + check_trail::(ctb, values, input) + .map_err(|()| fail(input.len() - 1, DecodeKind::Trailing))?; + Ok(output.len()) +} + +// Fails with Symbol if an input character does not translate to a symbol. The +// error is the lowest index of such character. +// Fails with Padding if some padding length is invalid. The error is the index +// of the first padding character of the invalid padding. +// Fails with Trailing if there are non-zero trailing bits. +fn decode_pad_mut( + ctb: bool, values: &[u8; 256], input: &[u8], output: &mut [MaybeUninit], +) -> Result { + if !Pad::VAL { + return decode_base_mut::(ctb, values, input, output); + } + debug_assert_eq!(Ok(output.len()), decode_pad_len::(input.len())); + let bit = Bit::VAL; + let enc = enc(bit); + let dec = dec(bit); + let mut inpos = 0; + let mut outpos = 0; + let mut outend = output.len(); + while inpos < input.len() { + match decode_base_mut::( + ctb, + values, + &input[inpos ..], + &mut output[outpos .. outend], + ) { + Ok(written) => { + if cfg!(debug_assertions) { + inpos = input.len(); + } + outpos += written; + break; + } + Err(partial) => { + inpos += partial.read; + outpos += partial.written; + } + } + let inlen = check_pad::(values, &input[inpos .. inpos + dec]).map_err(|pos| { + DecodePartial { + read: inpos, + written: outpos, + error: DecodeError { position: inpos + pos, kind: DecodeKind::Padding }, + } + })?; + let outlen = decode_base_len::(inlen).unwrap(); + let written = decode_base_mut::( + ctb, + values, + &input[inpos .. inpos + inlen], + &mut output[outpos .. outpos + outlen], + ) + .map_err(|partial| { + debug_assert_eq!(partial.read, 0); + debug_assert_eq!(partial.written, 0); + DecodePartial { + read: inpos, + written: outpos, + error: DecodeError { + position: inpos + partial.error.position, + kind: partial.error.kind, + }, + } + })?; + debug_assert_eq!(written, outlen); + inpos += dec; + outpos += outlen; + outend -= enc - outlen; + } + debug_assert_eq!(inpos, input.len()); + debug_assert_eq!(outpos, outend); + Ok(outend) +} + +fn skip_ignore(values: &[u8; 256], input: &[u8], mut inpos: usize) -> usize { + while inpos < input.len() && values[input[inpos] as usize] == IGNORE { + inpos += 1; + } + inpos +} + +// Returns next input and output position. +// Fails with Symbol if an input character does not translate to a symbol. The +// error is the lowest index of such character. +// Fails with Padding if some padding length is invalid. The error is the index +// of the first padding character of the invalid padding. +// Fails with Trailing if there are non-zero trailing bits. +fn decode_wrap_block( + ctb: bool, values: &[u8; 256], input: &[u8], output: &mut [MaybeUninit], +) -> Result<(usize, usize), DecodeError> { + let bit = Bit::VAL; + let dec = dec(bit); + let mut buf = [0u8; 8]; + let mut shift = [0usize; 8]; + let mut bufpos = 0; + let mut inpos = 0; + while bufpos < dec { + inpos = skip_ignore(values, input, inpos); + if inpos == input.len() { + break; + } + shift[bufpos] = inpos; + buf[bufpos] = input[inpos]; + bufpos += 1; + inpos += 1; + } + let olen = decode_pad_len::(bufpos).map_err(|mut e| { + e.position = shift[e.position]; + e + })?; + let written = + decode_pad_mut::(ctb, values, &buf[.. bufpos], &mut output[.. olen]) + .map_err(|partial| { + debug_assert_eq!(partial.read, 0); + debug_assert_eq!(partial.written, 0); + DecodeError { position: shift[partial.error.position], kind: partial.error.kind } + })?; + Ok((inpos, written)) +} + +// Fails with Symbol if an input character does not translate to a symbol. The +// error is the lowest index of such character. +// Fails with Padding if some padding length is invalid. The error is the index +// of the first padding character of the invalid padding. +// Fails with Trailing if there are non-zero trailing bits. +// Fails with Length if input length (without ignored characters) is invalid. +fn decode_wrap_mut( + ctb: bool, values: &[u8; 256], input: &[u8], output: &mut [MaybeUninit], +) -> Result { + if !Ignore::VAL { + return decode_pad_mut::(ctb, values, input, output); + } + debug_assert_eq!(output.len(), decode_wrap_len::(input.len()).1); + let mut inpos = 0; + let mut outpos = 0; + while inpos < input.len() { + let (inlen, outlen) = decode_wrap_len::(input.len() - inpos); + match decode_pad_mut::( + ctb, + values, + &input[inpos .. inpos + inlen], + &mut output[outpos .. outpos + outlen], + ) { + Ok(written) => { + inpos += inlen; + outpos += written; + break; + } + Err(partial) => { + inpos += partial.read; + outpos += partial.written; + } + } + let (ipos, opos) = decode_wrap_block::( + ctb, + values, + &input[inpos ..], + &mut output[outpos ..], + ) + .map_err(|mut error| { + error.position += inpos; + DecodePartial { read: inpos, written: outpos, error } + })?; + inpos += ipos; + outpos += opos; + } + let inpos = skip_ignore(values, input, inpos); + if inpos == input.len() { + Ok(outpos) + } else { + Err(DecodePartial { + read: inpos, + written: outpos, + error: DecodeError { position: inpos, kind: DecodeKind::Length }, + }) + } +} + +/// Error converting from a dynamic encoding to a static one. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ConvertError { + /// Different bit-width. + BitWidth, + + /// Different bit-order. + BitOrder, + + /// Different padding. + Padding, + + /// Different wrap. + Wrap, + + /// Different ignore. + Ignore, +} + +/// Base-conversion encoding. +/// +/// See [`Specification`] for technical details or how to define a new one. +#[derive(Debug, Clone)] +#[repr(transparent)] +pub struct Encoding { + // The config match the fields in data. In particular, we have the following properties: + // - If Bit is Bit1, Bit2, or Bit4 then Pad is False. + // - If Wrap is True, then Ignore is True. + data: InternalEncoding, + _type: PhantomData<(Bit, Msb, Pad, Wrap, Ignore)>, +} + +impl PartialEq + for Encoding +{ + fn eq(&self, other: &Self) -> bool { + self.data == other.data + } +} + +impl Eq + for Encoding +{ +} + +impl + Encoding +{ + fn sym(&self) -> &[u8; 256] { + self.data[0 .. 256].try_into().unwrap() + } + + fn val(&self) -> &[u8; 256] { + self.data[256 .. 512].try_into().unwrap() + } + + fn pad(&self) -> Pad::If { + Pad::make(|| self.data[512], || ()) + } + + fn ctb(&self) -> bool { + self.data[513] & 0x10 != 0 + } + + fn wrap(&self) -> Wrap::If<(usize, &[u8]), ()> { + Wrap::make(|| (self.data[514] as usize, &self.data[515 ..]), || ()) + } + + fn has_ignore(&self) -> bool { + self.data.len() >= 515 + } + + /// Minimum number of input and output blocks when encoding. + fn block_len(&self) -> (usize, usize) { + let bit = Bit::VAL; + match Wrap::open(self.wrap()) { + If::Then((col, end)) => (col / dec(bit) * enc(bit), col + end.len()), + If::Else(()) => (enc(bit), dec(bit)), + } + } + + /// Returns the encoded length of an input of length `len`. + /// + /// See [`Self::encode_mut()`] for when to use it. + #[must_use] + pub fn encode_len(&self, len: usize) -> usize { + encode_wrap_len::(self.wrap(), len) + } + + /// Encodes `input` in `output`. + /// + /// # Panics + /// + /// Panics if the `output` length does not match the result of [`Self::encode_len()`] for the + /// `input` length. + pub fn encode_mut_uninit<'a>( + &self, input: &[u8], output: &'a mut [MaybeUninit], + ) -> &'a mut [u8] { + assert_eq!(output.len(), self.encode_len(input.len())); + encode_wrap_mut::(self.sym(), self.pad(), self.wrap(), input, output); + unsafe { slice_assume_init_mut(output) } + } + + /// Encodes `input` in `output`. + /// + /// # Panics + /// + /// Panics if the `output` length does not match the result of [`Self::encode_len()`] for the + /// `input` length. + pub fn encode_mut(&self, input: &[u8], output: &mut [u8]) { + let _ = self.encode_mut_uninit(input, unsafe { slice_uninit_mut(output) }); + } + + /// Appends the encoding of `input` to `output`. + #[cfg(feature = "alloc")] + pub fn encode_append(&self, input: &[u8], output: &mut String) { + let output = unsafe { output.as_mut_vec() }; + let output_len = output.len(); + let len = self.encode_len(input.len()); + let _len = self.encode_mut_uninit(input, reserve_spare(output, len)).len(); + debug_assert_eq!(len, _len); + unsafe { output.set_len(output_len + len) }; + } + + /// Returns an object to encode a fragmented input and append it to `output`. + /// + /// See the documentation of [`Encoder`] for more details and examples. + #[cfg(feature = "alloc")] + pub fn new_encoder<'a>( + &'a self, output: &'a mut String, + ) -> Encoder<'a, Bit, Msb, Pad, Wrap, Ignore> { + Encoder::new(self, output) + } + + /// Writes the encoding of `input` to `output` using a temporary `buffer`. + /// + /// # Panics + /// + /// Panics if the buffer is shorter than 510 bytes. + /// + /// # Errors + /// + /// Returns an error when writing to the output fails. + pub fn encode_write_buffer_uninit( + &self, input: &[u8], output: &mut impl core::fmt::Write, buffer: &mut [MaybeUninit], + ) -> core::fmt::Result { + assert!(510 <= buffer.len()); + let (enc, dec) = self.block_len(); + for input in input.chunks(buffer.len() / dec * enc) { + let buffer = &mut buffer[.. self.encode_len(input.len())]; + let buffer = self.encode_mut_uninit(input, buffer); + output.write_str(unsafe { core::str::from_utf8_unchecked(buffer) })?; + } + Ok(()) + } + + /// Writes the encoding of `input` to `output` using a temporary `buffer`. + /// + /// # Panics + /// + /// Panics if the buffer is shorter than 510 bytes. + /// + /// # Errors + /// + /// Returns an error when writing to the output fails. + pub fn encode_write_buffer( + &self, input: &[u8], output: &mut impl core::fmt::Write, buffer: &mut [u8], + ) -> core::fmt::Result { + self.encode_write_buffer_uninit(input, output, unsafe { slice_uninit_mut(buffer) }) + } + + /// Writes the encoding of `input` to `output`. + /// + /// This allocates a buffer of 1024 bytes on the stack. If you want to control the buffer size + /// and location, use [`Self::encode_write_buffer()`] instead. + /// + /// # Errors + /// + /// Returns an error when writing to the output fails. + pub fn encode_write( + &self, input: &[u8], output: &mut impl core::fmt::Write, + ) -> core::fmt::Result { + self.encode_write_buffer(input, output, &mut [0; 1024]) + } + + /// Returns encoded `input`. + #[cfg(feature = "alloc")] + #[must_use] + pub fn encode(&self, input: &[u8]) -> String { + let mut output = Vec::new(); + let len = self.encode_len(input.len()); + let _len = self.encode_mut_uninit(input, reserve_spare(&mut output, len)).len(); + debug_assert_eq!(len, _len); + unsafe { output.set_len(len) }; + unsafe { String::from_utf8_unchecked(output) } + } + + /// Returns the decoded length of an input of length `len`. + /// + /// See [`Self::decode_mut()`] for when to use it. + /// + /// # Errors + /// + /// Returns an error if `len` is invalid. The error [kind][DecodeError::kind] is + /// [`DecodeKind::Length`] and the error [position][DecodeError::position] is the greatest valid + /// input length. + pub fn decode_len(&self, len: usize) -> Result { + let (ilen, olen) = decode_wrap_len::(len); + check!( + DecodeError { position: ilen, kind: DecodeKind::Length }, + self.has_ignore() || len == ilen + ); + Ok(olen) + } + + /// Decodes `input` in `output`. + /// + /// Returns the decoded output. Its length may be smaller than the output length if the input + /// contained padding or ignored characters. The output bytes after the returned length are not + /// initialized and should not be read. + /// + /// # Panics + /// + /// Panics if the `output` length does not match the result of [`Self::decode_len()`] for the + /// `input` length. Also panics if `decode_len` fails for the `input` length. + /// + /// # Errors + /// + /// Returns an error if `input` is invalid. See [`Self::decode_mut()`] for more details. + pub fn decode_mut_uninit<'a>( + &self, input: &[u8], output: &'a mut [MaybeUninit], + ) -> Result<&'a mut [u8], DecodePartial> { + assert_eq!(Ok(output.len()), self.decode_len(input.len())); + let len = decode_wrap_mut::(self.ctb(), self.val(), input, output)?; + Ok(unsafe { slice_assume_init_mut(&mut output[.. len]) }) + } + + /// Decodes `input` in `output`. + /// + /// Returns the length of the decoded output. This length may be smaller than the output length + /// if the input contained padding or ignored characters. The output bytes after the returned + /// length are not initialized and should not be read. + /// + /// # Panics + /// + /// Panics if the `output` length does not match the result of [`Self::decode_len()`] for the + /// `input` length. Also panics if `decode_len` fails for the `input` length. + /// + /// # Errors + /// + /// Returns an error if `input` is invalid. See [`Self::decode()`] for more details. The are two + /// differences though: + /// + /// - [`DecodeKind::Length`] may be returned only if the encoding allows ignored characters, + /// because otherwise this is already checked by [`Self::decode_len()`]. + /// - The [`DecodePartial::read`] first bytes of the input have been successfully decoded to the + /// [`DecodePartial::written`] first bytes of the output. + pub fn decode_mut(&self, input: &[u8], output: &mut [u8]) -> Result { + Ok(self.decode_mut_uninit(input, unsafe { slice_uninit_mut(output) })?.len()) + } + + /// Returns decoded `input`. + /// + /// # Errors + /// + /// Returns an error if `input` is invalid. The error kind can be: + /// + /// - [`DecodeKind::Length`] if the input length is invalid. The [position] is the greatest + /// valid input length. + /// - [`DecodeKind::Symbol`] if the input contains an invalid character. The [position] is the + /// first invalid character. + /// - [`DecodeKind::Trailing`] if the input has non-zero trailing bits. This is only possible if + /// the encoding checks trailing bits. The [position] is the first character containing + /// non-zero trailing bits. + /// - [`DecodeKind::Padding`] if the input has an invalid padding length. This is only possible + /// if the encoding uses padding. The [position] is the first padding character of the first + /// padding of invalid length. + /// + /// [position]: DecodeError::position + #[cfg(feature = "alloc")] + pub fn decode(&self, input: &[u8]) -> Result, DecodeError> { + let max_len = self.decode_len(input.len())?; + let mut output = Vec::new(); + let len = self + .decode_mut_uninit(input, reserve_spare(&mut output, max_len)) + .map_err(|partial| partial.error)? + .len(); + unsafe { output.set_len(len) }; + Ok(output) + } + + #[doc(hidden)] + #[must_use] + pub const unsafe fn new_unchecked(data: &'static [u8]) -> Self { + #[cfg(feature = "alloc")] + let data = Cow::Borrowed(data); + Encoding { data, _type: PhantomData } + } + + fn check_compatible(base: &DynEncoding) -> Result<(), ConvertError> { + check!(ConvertError::BitWidth, base.bit() == Bit::VAL); + check!(ConvertError::BitOrder, base.msb() == Msb::VAL); + check!(ConvertError::Padding, base.pad().is_some() == Pad::VAL); + check!(ConvertError::Wrap, base.wrap().is_some() == Wrap::VAL); + check!(ConvertError::Ignore, base.has_ignore() == Ignore::VAL); + Ok(()) + } +} + +/// Encodes fragmented input to an output. +/// +/// It is equivalent to use an [`Encoder`] with multiple calls to [`Encoder::append()`] than to +/// first concatenate all the input and then use [`Encoding::encode_append()`]. In particular, this +/// function will not introduce padding or wrapping between inputs. +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct Encoder<'a, Bit: BitWidth, Msb: Bool, Pad: Bool, Wrap: Bool, Ignore: Bool> { + encoding: &'a Encoding, + output: &'a mut String, + buffer: [u8; 255], + length: u8, +} + +#[cfg(feature = "alloc")] +impl<'a, Bit: BitWidth, Msb: Bool, Pad: Bool, Wrap: Bool, Ignore: Bool> Drop + for Encoder<'a, Bit, Msb, Pad, Wrap, Ignore> +{ + fn drop(&mut self) { + self.encoding.encode_append(&self.buffer[.. self.length as usize], self.output); + } +} + +#[cfg(feature = "alloc")] +impl<'a, Bit: BitWidth, Msb: Bool, Pad: Bool, Wrap: Bool, Ignore: Bool> + Encoder<'a, Bit, Msb, Pad, Wrap, Ignore> +{ + fn new(encoding: &'a Encoding, output: &'a mut String) -> Self { + Encoder { encoding, output, buffer: [0; 255], length: 0 } + } + + /// Encodes the provided input fragment and appends the result to the output. + pub fn append(&mut self, mut input: &[u8]) { + #[allow(clippy::cast_possible_truncation)] // no truncation + let max = self.encoding.block_len().0 as u8; + if self.length != 0 { + let len = self.length; + #[allow(clippy::cast_possible_truncation)] // no truncation + let add = core::cmp::min((max - len) as usize, input.len()) as u8; + self.buffer[len as usize ..][.. add as usize].copy_from_slice(&input[.. add as usize]); + self.length += add; + input = &input[add as usize ..]; + if self.length != max { + debug_assert!(self.length < max); + debug_assert!(input.is_empty()); + return; + } + self.encoding.encode_append(&self.buffer[.. max as usize], self.output); + self.length = 0; + } + let len = floor(input.len(), max as usize); + self.encoding.encode_append(&input[.. len], self.output); + input = &input[len ..]; + #[allow(clippy::cast_possible_truncation)] // no truncation + let len = input.len() as u8; + self.buffer[.. len as usize].copy_from_slice(input); + self.length = len; + } + + /// Makes sure all inputs have been encoded and appended to the output. + /// + /// This is equivalent to dropping the encoder and required for correctness, otherwise some + /// encoded data may be missing at the end. + pub fn finalize(self) {} +} + +impl TryFrom + for Encoding +{ + type Error = ConvertError; + + fn try_from(base: DynEncoding) -> Result { + Encoding::::check_compatible(&base)?; + Ok(Encoding { data: base.0, _type: PhantomData }) + } +} + +impl + From> for DynEncoding +{ + fn from(base: Encoding) -> Self { + DynEncoding(base.data) + } +} + +impl<'a, Bit: BitWidth, Msb: Bool, Pad: Bool, Wrap: Bool, Ignore: Bool> TryFrom<&'a DynEncoding> + for &'a Encoding +{ + type Error = ConvertError; + + fn try_from(base: &'a DynEncoding) -> Result { + Encoding::::check_compatible(base)?; + Ok(unsafe { + &*(base as *const DynEncoding).cast::>() + }) + } +} + +impl<'a, Bit: BitWidth, Msb: Bool, Pad: Bool, Wrap: Bool, Ignore: Bool> + From<&'a Encoding> for &'a DynEncoding +{ + fn from(base: &'a Encoding) -> Self { + unsafe { &*(base as *const Encoding).cast::() } + } +} + +/// Hexadecimal encoding. +pub type Hex = Encoding; + +/// Base32 encoding. +pub type Base32 = Encoding; + +/// Base32 encoding (no padding). +pub type Base32NoPad = Encoding; + +/// Base32 encoding (LSB first, no padding). +pub type Base32LsbNoPad = Encoding; + +/// Base64 encoding. +pub type Base64 = Encoding; + +/// Base64 encoding (no padding). +pub type Base64NoPad = Encoding; + +/// Base64 encoding (wrap). +pub type Base64Wrap = Encoding; + +/// Lowercase hexadecimal encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Hex, HEXLOWER}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("0123456789abcdef"); +/// assert_eq!(HEXLOWER, Hex::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +pub static HEXLOWER: Hex = unsafe { Hex::new_unchecked(crate::HEXLOWER_IMPL) }; + +/// Lowercase hexadecimal encoding with case-insensitive decoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Hex, HEXLOWER_PERMISSIVE}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("0123456789abcdef"); +/// spec.translate.from.push_str("ABCDEF"); +/// spec.translate.to.push_str("abcdef"); +/// assert_eq!(HEXLOWER_PERMISSIVE, Hex::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +pub static HEXLOWER_PERMISSIVE: Hex = + unsafe { Hex::new_unchecked(crate::HEXLOWER_PERMISSIVE_IMPL) }; + +/// Uppercase hexadecimal encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Hex, HEXUPPER}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("0123456789ABCDEF"); +/// assert_eq!(HEXUPPER, Hex::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +/// +/// It is compliant with [RFC4648] and known as "base16" or "hex". +/// +/// [RFC4648]: https://tools.ietf.org/html/rfc4648#section-8 +pub static HEXUPPER: Hex = unsafe { Hex::new_unchecked(crate::HEXUPPER_IMPL) }; + +/// Uppercase hexadecimal encoding with case-insensitive decoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Hex, HEXUPPER_PERMISSIVE}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("0123456789ABCDEF"); +/// spec.translate.from.push_str("abcdef"); +/// spec.translate.to.push_str("ABCDEF"); +/// assert_eq!(HEXUPPER_PERMISSIVE, Hex::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +pub static HEXUPPER_PERMISSIVE: Hex = + unsafe { Hex::new_unchecked(crate::HEXUPPER_PERMISSIVE_IMPL) }; + +/// Padded base32 encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base32, BASE32}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"); +/// spec.padding = Some('='); +/// assert_eq!(BASE32, Base32::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +/// +/// It conforms to [RFC4648]. +/// +/// [RFC4648]: https://tools.ietf.org/html/rfc4648#section-6 +pub static BASE32: Base32 = unsafe { Base32::new_unchecked(crate::BASE32_IMPL) }; + +/// Unpadded base32 encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base32NoPad, BASE32_NOPAD}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"); +/// assert_eq!(BASE32_NOPAD, Base32NoPad::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +pub static BASE32_NOPAD: Base32NoPad = + unsafe { Base32NoPad::new_unchecked(crate::BASE32_NOPAD_IMPL) }; + +/// Padded base32hex encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base32, BASE32HEX}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("0123456789ABCDEFGHIJKLMNOPQRSTUV"); +/// spec.padding = Some('='); +/// assert_eq!(BASE32HEX, Base32::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +/// +/// It conforms to [RFC4648]. +/// +/// [RFC4648]: https://tools.ietf.org/html/rfc4648#section-7 +pub static BASE32HEX: Base32 = unsafe { Base32::new_unchecked(crate::BASE32HEX_IMPL) }; + +/// Unpadded base32hex encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base32NoPad, BASE32HEX_NOPAD}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("0123456789ABCDEFGHIJKLMNOPQRSTUV"); +/// assert_eq!(BASE32HEX_NOPAD, Base32NoPad::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +pub static BASE32HEX_NOPAD: Base32NoPad = + unsafe { Base32NoPad::new_unchecked(crate::BASE32HEX_NOPAD_IMPL) }; + +/// DNSSEC base32 encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base32NoPad, BASE32_DNSSEC}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("0123456789abcdefghijklmnopqrstuv"); +/// spec.translate.from.push_str("ABCDEFGHIJKLMNOPQRSTUV"); +/// spec.translate.to.push_str("abcdefghijklmnopqrstuv"); +/// assert_eq!(BASE32_DNSSEC, Base32NoPad::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +/// +/// It conforms to [RFC5155]: +/// +/// - It uses a base32 extended hex alphabet. +/// - It is case-insensitive when decoding and uses lowercase when encoding. +/// - It does not use padding. +/// +/// [RFC5155]: https://tools.ietf.org/html/rfc5155 +pub static BASE32_DNSSEC: Base32NoPad = + unsafe { Base32NoPad::new_unchecked(crate::BASE32_DNSSEC_IMPL) }; + +#[allow(clippy::doc_markdown)] +/// DNSCurve base32 encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::{BitOrder, Specification}; +/// # use data_encoding::v3_preview::{Base32LsbNoPad, BASE32_DNSCURVE}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("0123456789bcdfghjklmnpqrstuvwxyz"); +/// spec.bit_order = BitOrder::LeastSignificantFirst; +/// spec.translate.from.push_str("BCDFGHJKLMNPQRSTUVWXYZ"); +/// spec.translate.to.push_str("bcdfghjklmnpqrstuvwxyz"); +/// assert_eq!(BASE32_DNSCURVE, Base32LsbNoPad::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +/// +/// It conforms to [DNSCurve]. +/// +/// [DNSCurve]: https://dnscurve.org/in-implement.html +pub static BASE32_DNSCURVE: Base32LsbNoPad = + unsafe { Base32LsbNoPad::new_unchecked(crate::BASE32_DNSCURVE_IMPL) }; + +/// Padded base64 encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base64, BASE64}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); +/// spec.padding = Some('='); +/// assert_eq!(BASE64, Base64::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +/// +/// It conforms to [RFC4648]. +/// +/// [RFC4648]: https://tools.ietf.org/html/rfc4648#section-4 +pub static BASE64: Base64 = unsafe { Base64::new_unchecked(crate::BASE64_IMPL) }; + +/// Unpadded base64 encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base64NoPad, BASE64_NOPAD}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); +/// assert_eq!(BASE64_NOPAD, Base64NoPad::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +pub static BASE64_NOPAD: Base64NoPad = + unsafe { Base64NoPad::new_unchecked(crate::BASE64_NOPAD_IMPL) }; + +/// MIME base64 encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base64Wrap, BASE64_MIME}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); +/// spec.padding = Some('='); +/// spec.wrap.width = 76; +/// spec.wrap.separator.push_str("\r\n"); +/// assert_eq!(BASE64_MIME, Base64Wrap::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +/// +/// It does not exactly conform to [RFC2045] because it does not print the header +/// and does not ignore all characters. +/// +/// [RFC2045]: https://tools.ietf.org/html/rfc2045 +pub static BASE64_MIME: Base64Wrap = unsafe { Base64Wrap::new_unchecked(crate::BASE64_MIME_IMPL) }; + +/// MIME base64 encoding without trailing bits check. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base64Wrap, BASE64_MIME_PERMISSIVE}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); +/// spec.padding = Some('='); +/// spec.wrap.width = 76; +/// spec.wrap.separator.push_str("\r\n"); +/// spec.check_trailing_bits = false; +/// assert_eq!(BASE64_MIME_PERMISSIVE, Base64Wrap::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +/// +/// It does not exactly conform to [RFC2045] because it does not print the header +/// and does not ignore all characters. +/// +/// [RFC2045]: https://tools.ietf.org/html/rfc2045 +pub static BASE64_MIME_PERMISSIVE: Base64Wrap = + unsafe { Base64Wrap::new_unchecked(crate::BASE64_MIME_PERMISSIVE_IMPL) }; + +/// Padded base64url encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base64, BASE64URL}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); +/// spec.padding = Some('='); +/// assert_eq!(BASE64URL, Base64::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +/// +/// It conforms to [RFC4648]. +/// +/// [RFC4648]: https://tools.ietf.org/html/rfc4648#section-5 +pub static BASE64URL: Base64 = unsafe { Base64::new_unchecked(crate::BASE64URL_IMPL) }; + +/// Unpadded base64url encoding. +/// +/// This encoding is a static version of: +/// +/// ```rust +/// # use data_encoding::Specification; +/// # use data_encoding::v3_preview::{Base64NoPad, BASE64URL_NOPAD}; +/// # use core::convert::TryFrom as _; +/// let mut spec = Specification::new(); +/// spec.symbols.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); +/// assert_eq!(BASE64URL_NOPAD, Base64NoPad::try_from(spec.encoding().unwrap()).unwrap()); +/// ``` +pub static BASE64URL_NOPAD: Base64NoPad = + unsafe { Base64NoPad::new_unchecked(crate::BASE64URL_NOPAD_IMPL) }; diff --git a/lib/tests/lib.rs b/lib/tests/lib.rs index b3a2ada..b34f0f6 100644 --- a/lib/tests/lib.rs +++ b/lib/tests/lib.rs @@ -1,15 +1,41 @@ -extern crate data_encoding; - +#[cfg(feature = "v3-preview")] +use core::convert::TryFrom as _; + +#[cfg(not(feature = "v3-preview"))] +use data_encoding as constants; +#[cfg(feature = "v3-preview")] +use data_encoding::v3_preview as constants; +#[cfg(feature = "v3-preview")] +use data_encoding::v3_preview::{ + Bit1, Bit2, Bit3, Bit4, Bit6, BitWidth, Bool, Encoding, False, True, +}; use data_encoding::DecodeKind::*; -use data_encoding::{DecodeError, Encoding, Specification}; +#[cfg(not(feature = "v3-preview"))] +use data_encoding::Encoding; +use data_encoding::{DecodeError, Specification}; + +macro_rules! split_v3 { + (fn $name:ident[$($g:tt)*]($base:ident: &Encoding[$($i:tt)*] + $(, $x:ident: $t:ty)* $(,)?) $(-> $r:ty)? { $($body:tt)* } + ) => { + #[cfg(feature = "v3-preview")] + fn $name<$($g)*>($base: &Encoding<$($i)*> $(, $x: $t)*) $(-> $r)? { $($body)* } + #[cfg(not(feature = "v3-preview"))] + fn $name($base: &Encoding $(, $x: $t)*) $(-> $r)? { $($body)* } + }; +} macro_rules! test { (fn $t: ident; $($s: stmt);*;) => { #[test] fn $t() { - fn test(b: &Encoding, x: &[u8], y: &[u8]) { - assert_eq!(&b.encode(x).into_bytes() as &[u8], y); - assert_eq!(&b.decode(y).unwrap() as &[u8], x); + split_v3! { + fn test[Bit: BitWidth, Msb: Bool, Pad: Bool, Wrap: Bool, Ignore: Bool]( + b: &Encoding[Bit, Msb, Pad, Wrap, Ignore], x: &[u8], y: &[u8], + ) { + assert_eq!(&b.encode(x).into_bytes() as &[u8], y); + assert_eq!(&b.decode(y).unwrap() as &[u8], x); + } } $($s)* } @@ -25,6 +51,8 @@ test! { let mut s = Specification::new(); s.symbols.push_str("01"); let b = s.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let b = Encoding::::try_from(b).unwrap(); test(&b, b"", b""); test(&b, b"f", b"01100110"); test(&b, b"fo", b"0110011001101111"); @@ -36,6 +64,8 @@ test! { let mut s = Specification::new(); s.symbols.push_str("0123"); let b = s.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let b = Encoding::::try_from(b).unwrap(); test(&b, b"", b""); test(&b, b"f", b"1212"); test(&b, b"fo", b"12121233"); @@ -51,6 +81,8 @@ test! { s.symbols.push_str("01234567"); s.padding = Some('='); let b = s.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let b = Encoding::::try_from(b).unwrap(); test(&b, b"", b""); test(&b, b"f", b"314====="); test(&b, b"fo", b"314674=="); @@ -62,7 +94,7 @@ test! { test! { fn hexlower; - let b = &data_encoding::HEXLOWER; + let b = &constants::HEXLOWER; test(b, b"", b""); test(b, b"f", b"66"); test(b, b"fo", b"666f"); @@ -70,14 +102,14 @@ test! { test(b, b"foob", b"666f6f62"); test(b, b"fooba", b"666f6f6261"); test(b, b"foobar", b"666f6f626172"); - assert_eq!(data_encoding::HEXLOWER.decode(b"6f").unwrap(), b"o"); - assert!(data_encoding::HEXLOWER.decode(b"6F").is_err()); - assert_eq!(data_encoding::HEXLOWER_PERMISSIVE.decode(b"6F").unwrap(), b"o"); + assert_eq!(constants::HEXLOWER.decode(b"6f").unwrap(), b"o"); + assert!(constants::HEXLOWER.decode(b"6F").is_err()); + assert_eq!(constants::HEXLOWER_PERMISSIVE.decode(b"6F").unwrap(), b"o"); } test! { fn hexupper; - let b = &data_encoding::HEXUPPER; + let b = &constants::HEXUPPER; test(b, b"", b""); test(b, b"f", b"66"); test(b, b"fo", b"666F"); @@ -85,14 +117,14 @@ test! { test(b, b"foob", b"666F6F62"); test(b, b"fooba", b"666F6F6261"); test(b, b"foobar", b"666F6F626172"); - assert_eq!(data_encoding::HEXUPPER.decode(b"6F").unwrap(), b"o"); - assert!(data_encoding::HEXUPPER.decode(b"6f").is_err()); - assert_eq!(data_encoding::HEXUPPER_PERMISSIVE.decode(b"6f").unwrap(), b"o"); + assert_eq!(constants::HEXUPPER.decode(b"6F").unwrap(), b"o"); + assert!(constants::HEXUPPER.decode(b"6f").is_err()); + assert_eq!(constants::HEXUPPER_PERMISSIVE.decode(b"6f").unwrap(), b"o"); } test! { fn base32; - let b = &data_encoding::BASE32; + let b = &constants::BASE32; test(b, b"", b""); test(b, b"f", b"MY======"); test(b, b"fo", b"MZXQ===="); @@ -104,7 +136,7 @@ test! { test! { fn base32hex; - let b = &data_encoding::BASE32HEX; + let b = &constants::BASE32HEX; test(b, b"", b""); test(b, b"f", b"CO======"); test(b, b"fo", b"CPNG===="); @@ -116,7 +148,7 @@ test! { test! { fn base32_dnscurve; - let b = &data_encoding::BASE32_DNSCURVE; + let b = &constants::BASE32_DNSCURVE; test(b, &[0x64, 0x88], b"4321"); test(b, b"f", b"63"); test(b, b"fo", b"6vv0"); @@ -136,7 +168,7 @@ test! { test! { fn base64; - let b = &data_encoding::BASE64; + let b = &constants::BASE64; test(b, b"", b""); test(b, b"f", b"Zg=="); test(b, b"fo", b"Zm8="); @@ -149,7 +181,7 @@ test! { test! { fn base64url; - let b = &data_encoding::BASE64URL; + let b = &constants::BASE64URL; test(b, b"", b""); test(b, b"f", b"Zg=="); test(b, b"fo", b"Zm8="); @@ -162,7 +194,7 @@ test! { test! { fn base64_no_pad; - let b = &data_encoding::BASE64_NOPAD; + let b = &constants::BASE64_NOPAD; test(&b, b"", b""); test(&b, b"f", b"Zg"); test(&b, b"fo", b"Zm8"); @@ -174,7 +206,7 @@ test! { #[test] fn base32_error() { - let b = &data_encoding::BASE32; + let b = &constants::BASE32; assert_eq!(b.decode(b"ABC").err().unwrap(), DecodeError { position: 0, kind: Length }); assert_eq!(b.decode(b"========").err().unwrap(), DecodeError { position: 0, kind: Padding }); assert_eq!(b.decode(b"MB======").err().unwrap(), DecodeError { position: 1, kind: Trailing }); @@ -185,7 +217,7 @@ fn base32_error() { #[test] fn base64_error() { - let b = &data_encoding::BASE64; + let b = &constants::BASE64; assert_eq!(b.decode(b"====").err().unwrap(), DecodeError { position: 0, kind: Padding }); assert_eq!(b.decode(b"====").err().unwrap(), DecodeError { position: 0, kind: Padding }); assert_eq!( @@ -218,9 +250,7 @@ fn base64_error() { #[test] fn base64_nopad_error() { - let mut s = data_encoding::BASE64.specification(); - s.padding = None; - let b = s.encoding().unwrap(); + let b = &constants::BASE64_NOPAD; assert_eq!(b.decode(b"Z").err().unwrap(), DecodeError { position: 0, kind: Length }); assert_eq!(b.decode(b"Zh").err().unwrap(), DecodeError { position: 1, kind: Trailing }); assert_eq!(b.decode(b"Zg==").err().unwrap(), DecodeError { position: 2, kind: Symbol }); @@ -237,6 +267,10 @@ fn bit_order() { let msb = spec.encoding().unwrap(); spec.bit_order = data_encoding::BitOrder::LeastSignificantFirst; let lsb = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let msb = Encoding::::try_from(msb).unwrap(); + #[cfg(feature = "v3-preview")] + let lsb = Encoding::::try_from(lsb).unwrap(); assert_eq!(msb.encode(b"ABC"), "414243"); assert_eq!(lsb.encode(b"ABC"), "142434"); } @@ -248,6 +282,10 @@ fn trailing_bits() { let strict = spec.encoding().unwrap(); spec.check_trailing_bits = false; let permissive = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let strict = Encoding::::try_from(strict).unwrap(); + #[cfg(feature = "v3-preview")] + let permissive = Encoding::::try_from(permissive).unwrap(); assert!(strict.decode(b"001").is_err()); assert!(permissive.decode(b"001").is_ok()); } @@ -266,13 +304,17 @@ fn ignore() { } j } - fn check(base: &Encoding, buf: &[u8], cmp: &[u8], shift: &[usize]) { - let res = base.decode(buf); - match base.decode(cmp) { - Ok(x) => assert_eq!(Ok(x), res), - Err(mut x) => { - x.position = shift[x.position]; - assert_eq!(Err(x), res); + split_v3! { + fn check[Pad: Bool]( + base: &Encoding[Bit3, True, Pad, False, True], buf: &[u8], cmp: &[u8], shift: &[usize] + ) { + let res = base.decode(buf); + match base.decode(cmp) { + Ok(x) => assert_eq!(Ok(x), res), + Err(mut x) => { + x.position = shift[x.position]; + assert_eq!(Err(x), res); + } } } } @@ -289,17 +331,21 @@ fn ignore() { } false } - fn forall(base: &Encoding, chars: &[u8], max: usize) { - let mut idx = vec![0; max]; - let mut buf = vec![0; max]; - let mut cmp = vec![0; max]; - let mut shift = vec![0; max]; - for size in 0 .. (max + 1) { - loop { - let len = skip(&buf[.. size], &mut cmp, &mut shift); - check(base, &buf[.. size], &cmp[.. len], &shift[.. len]); - if !incr(chars, &mut idx[.. size], &mut buf[.. size]) { - break; + split_v3! { + fn forall[Pad: Bool]( + base: &Encoding[Bit3, True, Pad, False, True], chars: &[u8], max: usize + ) { + let mut idx = vec![0; max]; + let mut buf = vec![0; max]; + let mut cmp = vec![0; max]; + let mut shift = vec![0; max]; + for size in 0 .. (max + 1) { + loop { + let len = skip(&buf[.. size], &mut cmp, &mut shift); + check(base, &buf[.. size], &cmp[.. len], &shift[.. len]); + if !incr(chars, &mut idx[.. size], &mut buf[.. size]) { + break; + } } } } @@ -310,6 +356,10 @@ fn ignore() { let no_pad = spec.encoding().unwrap(); spec.padding = Some('='); let padded = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let no_pad = Encoding::::try_from(no_pad).unwrap(); + #[cfg(feature = "v3-preview")] + let padded = Encoding::::try_from(padded).unwrap(); #[cfg(miri)] const MAX: [usize; 4] = [4, 3, 3, 2]; #[cfg(all(not(miri), debug_assertions))] @@ -338,6 +388,8 @@ fn translate() { spec.translate.from.push_str("-_O"); spec.translate.to.push_str("=.0"); let base = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let base = Encoding::::try_from(base).unwrap(); assert_eq!(base.decode(b" O OO= = = == ").unwrap(), [0]); assert_eq!(base.decode(b"O__OO__--_--.-").unwrap(), [0]); assert_eq!(base.decode(b"_____. . . ... ..").unwrap(), []); @@ -394,6 +446,7 @@ fn specification() { assert_eq!(errmsg(spec.encoding()), "invalid wrap width or separator length"); } +#[cfg(not(feature = "v3-preview"))] #[test] fn round_trip() { let test = |e: Encoding| { @@ -443,7 +496,7 @@ fn is_canonical() { #[test] fn decode_error() { - let b = &data_encoding::BASE64; + let b = &constants::BASE64; assert_eq!(errmsg(b.decode(b"A")), "invalid length at 0"); assert_eq!(errmsg(b.decode(b"A.AA")), "invalid symbol at 1"); assert_eq!(errmsg(b.decode(b"AAB=")), "non-zero trailing bits at 2"); @@ -452,9 +505,7 @@ fn decode_error() { #[test] fn encode_base() { - let mut spec = data_encoding::BASE64.specification(); - spec.padding = None; - let b = spec.encoding().unwrap(); + let b = &constants::BASE64_NOPAD; assert_eq!(b.encode(b""), ""); assert_eq!(b.encode(b"h"), "aA"); assert_eq!(b.encode(b"he"), "aGU"); @@ -465,9 +516,7 @@ fn encode_base() { #[test] fn decode_base() { - let mut spec = data_encoding::BASE64.specification(); - spec.padding = None; - let b = spec.encoding().unwrap(); + let b = &constants::BASE64_NOPAD; let err_length = |pos| Err(DecodeError { position: pos, kind: Length }); let err_symbol = |pos| Err(DecodeError { position: pos, kind: Symbol }); let err_trailing = |pos| Err(DecodeError { position: pos, kind: Trailing }); @@ -495,7 +544,7 @@ fn decode_base() { #[test] fn encode_pad() { - let b = &data_encoding::BASE64; + let b = &constants::BASE64; assert_eq!(b.encode(b""), ""); assert_eq!(b.encode(b"h"), "aA=="); assert_eq!(b.encode(b"he"), "aGU="); @@ -506,7 +555,7 @@ fn encode_pad() { #[test] fn decode_pad() { - let b = &data_encoding::BASE64; + let b = &constants::BASE64; let err_length = |pos| Err(DecodeError { position: pos, kind: Length }); let err_symbol = |pos| Err(DecodeError { position: pos, kind: Symbol }); let err_trailing = |pos| Err(DecodeError { position: pos, kind: Trailing }); @@ -552,11 +601,12 @@ fn decode_pad() { #[test] fn encode_wrap() { - let mut spec = data_encoding::BASE64.specification(); - spec.padding = None; + let mut spec = data_encoding::BASE64_NOPAD.specification(); spec.wrap.width = 4; spec.wrap.separator.push_str(":"); let b = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let b = Encoding::::try_from(b).unwrap(); assert_eq!(b.encode(b""), ""); assert_eq!(b.encode(b"h"), "aA:"); assert_eq!(b.encode(b"he"), "aGU:"); @@ -567,10 +617,11 @@ fn encode_wrap() { #[test] fn decode_wrap() { - let mut spec = data_encoding::BASE64.specification(); - spec.padding = None; + let mut spec = data_encoding::BASE64_NOPAD.specification(); spec.ignore.push_str(":"); let b = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let b = Encoding::::try_from(b).unwrap(); let err_length = |pos| Err(DecodeError { position: pos, kind: Length }); let err_symbol = |pos| Err(DecodeError { position: pos, kind: Symbol }); let err_trailing = |pos| Err(DecodeError { position: pos, kind: Trailing }); @@ -602,6 +653,8 @@ fn encode_pad_wrap() { spec.wrap.width = 4; spec.wrap.separator.push_str(":"); let b = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let b = Encoding::::try_from(b).unwrap(); assert_eq!(b.encode(b""), ""); assert_eq!(b.encode(b"h"), "aA==:"); assert_eq!(b.encode(b"he"), "aGU=:"); @@ -615,6 +668,8 @@ fn decode_pad_wrap() { let mut spec = data_encoding::BASE64.specification(); spec.ignore.push_str(":"); let b = spec.encoding().unwrap(); + #[cfg(feature = "v3-preview")] + let b = Encoding::::try_from(b).unwrap(); let err_length = |pos| Err(DecodeError { position: pos, kind: Length }); let err_symbol = |pos| Err(DecodeError { position: pos, kind: Symbol }); let err_trailing = |pos| Err(DecodeError { position: pos, kind: Trailing }); @@ -662,7 +717,7 @@ fn decode_pad_wrap() { fn encode_append() { fn test(input: &[u8], output: &str, expected: &str) { let mut output = output.to_string(); - data_encoding::BASE64.encode_append(input, &mut output); + constants::BASE64.encode_append(input, &mut output); assert_eq!(output, expected); } test(b"", "", ""); @@ -676,7 +731,7 @@ fn encode_append() { fn encode_write() { fn test(input: &[u8], output: &str, expected: &str) { let mut output = output.to_string(); - data_encoding::BASE64.encode_write(input, &mut output).unwrap(); + constants::BASE64.encode_write(input, &mut output).unwrap(); assert_eq!(output, expected); } test(b"", "", ""); @@ -691,8 +746,11 @@ fn encoder() { #[track_caller] fn test(inputs: &[&[u8]], expected: &str) { let mut output = String::new(); - static BASE: Encoding = data_encoding::BASE64; - let mut encoder = BASE.new_encoder(&mut output); + #[cfg(feature = "v3-preview")] + use constants::BASE64; + #[cfg(not(feature = "v3-preview"))] + static BASE64: Encoding = data_encoding::BASE64; + let mut encoder = BASE64.new_encoder(&mut output); for input in inputs { encoder.append(input); } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index c1b64ec..e446bc6 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -136,13 +136,16 @@ impl Action { if self.dir.is_published() { instructions *= &[&["--release"]]; } - let features: &[&[&str]] = match self.dir { - Dir::Lib => { - &[&["--no-default-features", "--features=alloc"], &["--no-default-features"]] - } - _ => &[], - }; - instructions *= features; + if self.dir == Dir::Lib { + instructions *= + &[&["--no-default-features", "--features=alloc"], &["--no-default-features"]]; + } + } + if self.dir == Dir::Lib + && matches!(self.task, Task::Build | Task::Test | Task::Miri | Task::Bench) + && !matches!(self.toolchain, Toolchain::Msrv) + { + instructions *= &[&["--features=v3-preview"]]; } if self.dir == Dir::Nostd && self.task == Task::Test { instructions = Instructions::default();