diff --git a/Cargo.toml b/Cargo.toml index 30e73eec..4b4385c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ structopt = "0.3" default = ["std"] alloc = [] std = [] +slow_but_safe = [] [profile.bench] # Useful for better disassembly when using `perf record` and `perf annotate` diff --git a/src/decode.rs b/src/decode.rs index 4cc937d5..2762c4cf 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -444,6 +444,18 @@ fn write_u64(output: &mut [u8], value: u64) { output[..8].copy_from_slice(&value.to_be_bytes()); } +#[cfg(feature = "slow_but_safe")] +fn decode_aligned(b64ch: u8, decode_table: &[u8; 256]) -> u8 { + let mut result: u8 = 0x00; + let mut mask: u8; + let idx: [u8;2] = [ b64ch % 64, b64ch % 64 + 64]; + for i in 0..2 { + mask = 0xFF ^ (((idx[i] == b64ch) as i8 - 1) as u8); + result = result | (decode_table[idx[i] as usize] & mask); + } + result +} + /// Decode 8 bytes of input into 6 bytes of output. 8 bytes of output will be written, but only the /// first 6 of those contain meaningful data. /// @@ -463,13 +475,19 @@ fn decode_chunk( ) -> Result<(), DecodeError> { let mut accum: u64; + #[cfg(not(feature = "slow_but_safe"))] let morsel = decode_table[input[0] as usize]; + #[cfg(feature = "slow_but_safe")] + let morsel = decode_aligned(input[0], decode_table); if morsel == tables::INVALID_VALUE { return Err(DecodeError::InvalidByte(index_at_start_of_input, input[0])); } accum = (morsel as u64) << 58; + #[cfg(not(feature = "slow_but_safe"))] let morsel = decode_table[input[1] as usize]; + #[cfg(feature = "slow_but_safe")] + let morsel = decode_aligned(input[1], decode_table); if morsel == tables::INVALID_VALUE { return Err(DecodeError::InvalidByte( index_at_start_of_input + 1, @@ -478,7 +496,10 @@ fn decode_chunk( } accum |= (morsel as u64) << 52; + #[cfg(not(feature = "slow_but_safe"))] let morsel = decode_table[input[2] as usize]; + #[cfg(feature = "slow_but_safe")] + let morsel = decode_aligned(input[2], decode_table); if morsel == tables::INVALID_VALUE { return Err(DecodeError::InvalidByte( index_at_start_of_input + 2, @@ -487,7 +508,10 @@ fn decode_chunk( } accum |= (morsel as u64) << 46; + #[cfg(not(feature = "slow_but_safe"))] let morsel = decode_table[input[3] as usize]; + #[cfg(feature = "slow_but_safe")] + let morsel = decode_aligned(input[3], decode_table); if morsel == tables::INVALID_VALUE { return Err(DecodeError::InvalidByte( index_at_start_of_input + 3, @@ -496,7 +520,10 @@ fn decode_chunk( } accum |= (morsel as u64) << 40; + #[cfg(not(feature = "slow_but_safe"))] let morsel = decode_table[input[4] as usize]; + #[cfg(feature = "slow_but_safe")] + let morsel = decode_aligned(input[4], decode_table); if morsel == tables::INVALID_VALUE { return Err(DecodeError::InvalidByte( index_at_start_of_input + 4, @@ -505,7 +532,10 @@ fn decode_chunk( } accum |= (morsel as u64) << 34; + #[cfg(not(feature = "slow_but_safe"))] let morsel = decode_table[input[5] as usize]; + #[cfg(feature = "slow_but_safe")] + let morsel = decode_aligned(input[5], decode_table); if morsel == tables::INVALID_VALUE { return Err(DecodeError::InvalidByte( index_at_start_of_input + 5, @@ -514,7 +544,10 @@ fn decode_chunk( } accum |= (morsel as u64) << 28; + #[cfg(not(feature = "slow_but_safe"))] let morsel = decode_table[input[6] as usize]; + #[cfg(feature = "slow_but_safe")] + let morsel = decode_aligned(input[6], decode_table); if morsel == tables::INVALID_VALUE { return Err(DecodeError::InvalidByte( index_at_start_of_input + 6, @@ -523,7 +556,10 @@ fn decode_chunk( } accum |= (morsel as u64) << 22; + #[cfg(not(feature = "slow_but_safe"))] let morsel = decode_table[input[7] as usize]; + #[cfg(feature = "slow_but_safe")] + let morsel = decode_aligned(input[7], decode_table); if morsel == tables::INVALID_VALUE { return Err(DecodeError::InvalidByte( index_at_start_of_input + 7, diff --git a/src/lib.rs b/src/lib.rs index 6bded160..dbc55a3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,12 +138,12 @@ impl CharacterSet { fn decode_table(self) -> &'static [u8; 256] { match self { - CharacterSet::Standard => tables::STANDARD_DECODE, - CharacterSet::UrlSafe => tables::URL_SAFE_DECODE, - CharacterSet::Crypt => tables::CRYPT_DECODE, - CharacterSet::Bcrypt => tables::BCRYPT_DECODE, - CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_DECODE, - CharacterSet::BinHex => tables::BINHEX_DECODE, + CharacterSet::Standard => &tables::STANDARD_DECODE_HOLDER.data, + CharacterSet::UrlSafe => &tables::URL_SAFE_DECODE_HOLDER.data, + CharacterSet::Crypt => &tables::CRYPT_DECODE_HOLDER.data, + CharacterSet::Bcrypt => &tables::BCRYPT_DECODE_HOLDER.data, + CharacterSet::ImapMutf7 => &tables::IMAP_MUTF7_DECODE_HOLDER.data, + CharacterSet::BinHex => &tables::BINHEX_DECODE_HOLDER.data, } } } diff --git a/src/tables.rs b/src/tables.rs index a45851cd..4632c3c2 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -1,3 +1,27 @@ +#[repr(align(64))] +pub struct DecodeTableHolder { + pub data: [u8; 256], +} + +pub const STANDARD_DECODE_HOLDER: DecodeTableHolder = DecodeTableHolder { + data: *STANDARD_DECODE, +}; +pub const URL_SAFE_DECODE_HOLDER: DecodeTableHolder = DecodeTableHolder { + data: *URL_SAFE_DECODE, +}; +pub const CRYPT_DECODE_HOLDER: DecodeTableHolder = DecodeTableHolder { + data: *CRYPT_DECODE, +}; +pub const BCRYPT_DECODE_HOLDER: DecodeTableHolder = DecodeTableHolder { + data: *BCRYPT_DECODE, +}; +pub const IMAP_MUTF7_DECODE_HOLDER: DecodeTableHolder = DecodeTableHolder { + data: *IMAP_MUTF7_DECODE, +}; +pub const BINHEX_DECODE_HOLDER: DecodeTableHolder = DecodeTableHolder { + data: *BINHEX_DECODE, +}; + pub const INVALID_VALUE: u8 = 255; #[rustfmt::skip] pub const STANDARD_ENCODE: &[u8; 64] = &[ @@ -1955,3 +1979,19 @@ pub const BINHEX_DECODE: &[u8; 256] = &[ INVALID_VALUE, // input 254 (0xFE) INVALID_VALUE, // input 255 (0xFF) ]; + +#[test] +fn alignment_check() { + let p: *const u8 = STANDARD_DECODE_HOLDER.data.as_ptr(); + assert_eq!((p as u64) % 64, 0); + let p: *const u8 = URL_SAFE_DECODE_HOLDER.data.as_ptr(); + assert_eq!((p as u64) % 64, 0); + let p: *const u8 = CRYPT_DECODE_HOLDER.data.as_ptr(); + assert_eq!((p as u64) % 64, 0); + let p: *const u8 = BCRYPT_DECODE_HOLDER.data.as_ptr(); + assert_eq!((p as u64) % 64, 0); + let p: *const u8 = IMAP_MUTF7_DECODE_HOLDER.data.as_ptr(); + assert_eq!((p as u64) % 64, 0); + let p: *const u8 = BINHEX_DECODE_HOLDER.data.as_ptr(); + assert_eq!((p as u64) % 64, 0); +}