diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c918ea5..fdd3a5e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -16,4 +16,4 @@ jobs: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} run: | cargo install cargo-tarpaulin - cargo tarpaulin --ciserver github-ci --coveralls $COVERALLS_REPO_TOKEN + cargo tarpaulin --all-features --ciserver github-ci --coveralls $COVERALLS_REPO_TOKEN diff --git a/Cargo.toml b/Cargo.toml index 9134602..496bcd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,17 @@ license = "Apache-2.0" exclude = ["/.github/*"] [dependencies.num-traits] -version = "0.2.15" +version = "0.2.19" default-features = false [dependencies.num-integer] version = "0.1.45" default-features = false +[dependencies.zeroize] +version = "1.8.1" +optional = true + [profile.dev] opt-level = 0 debug = true @@ -33,3 +37,7 @@ lto = false panic = 'unwind' incremental = false codegen-units = 16 + +[features] +use-unsafe = [] +default = [] diff --git a/src/fixeduint.rs b/src/fixeduint.rs index 78969b3..0501444 100644 --- a/src/fixeduint.rs +++ b/src/fixeduint.rs @@ -17,12 +17,19 @@ use num_traits::{Bounded, One, PrimInt, ToPrimitive, Zero}; use num_integer; +#[cfg(feature = "use-unsafe")] +use core::borrow::{Borrow, BorrowMut}; use core::convert::TryFrom; use core::fmt::Write; +#[cfg(feature = "use-unsafe")] +use core::hash::Hash; use crate::machineword::MachineWord; use crate::patch_num_traits::{OverflowingShl, OverflowingShr}; +#[cfg(feature = "zeroize")] +use zeroize::DefaultIsZeroes; + #[allow(unused_imports)] use num_traits::{FromPrimitive, Num}; @@ -81,6 +88,55 @@ impl FixedUInt { ret } + /// Create a little-endian integer value from its representation as a byte array in big endian. + pub fn from_be_bytes(bytes: &[u8]) -> Self { + let iter: usize = core::cmp::min(bytes.len() / Self::WORD_SIZE, N); + let total_bytes = iter * Self::WORD_SIZE; + let mut ret = Self::new(); + for word in 0..iter { + let mut v = T::zero(); + let mut next = T::zero(); + for i in 0..Self::WORD_SIZE { + let byte_index = total_bytes - (word + 1) * Self::WORD_SIZE + i; + let byte = bytes[byte_index]; + v = next | byte.into(); + next = if Self::WORD_BITS == 8 { v } else { v.shl(8) }; + } + ret.array[word] = v; + } + ret + } + + // Converts the FixedUInt into a little-endian byte array. + pub fn to_le_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> { + let total_bytes = N * Self::WORD_SIZE; + if output_buffer.len() < total_bytes { + return Err(false); // Buffer too small + } + for (i, word) in self.array.iter().enumerate() { + let start = i * Self::WORD_SIZE; + let end = start + Self::WORD_SIZE; + let word_bytes = word.to_le_bytes(); + output_buffer[start..end].copy_from_slice(word_bytes.as_ref()); + } + Ok(&output_buffer[..total_bytes]) + } + + // Converts the FixedUInt into a big-endian byte array. + pub fn to_be_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> { + let total_bytes = N * Self::WORD_SIZE; + if output_buffer.len() < total_bytes { + return Err(false); // Buffer too small + } + for (i, word) in self.array.iter().rev().enumerate() { + let start = i * Self::WORD_SIZE; + let end = start + Self::WORD_SIZE; + let word_bytes = word.to_be_bytes(); + output_buffer[start..end].copy_from_slice(word_bytes.as_ref()); + } + Ok(&output_buffer[..total_bytes]) + } + /// Converts to hex string, given a buffer. CAVEAT: This method removes any leading zero pub fn to_hex_str<'a>(&self, result: &'a mut [u8]) -> Result<&'a str, core::fmt::Error> { type Error = core::fmt::Error; @@ -1341,6 +1397,89 @@ impl num_integer::Integer for FixedUInt { // #endregion num-integer::Integer +#[cfg(feature = "zeroize")] +impl DefaultIsZeroes for FixedUInt {} + +// Helper, holds an owned copy of returned bytes +#[derive(Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Debug)] +pub struct BytesHolder { + array: [T; N], +} + +#[cfg(feature = "use-unsafe")] +impl BytesHolder { + // Converts internal storage to a mutable byte slice + fn as_byte_slice_mut(&mut self) -> &mut [u8] { + // SAFETY: This is safe because the size of the array is the same as the size of the slice + unsafe { + core::slice::from_raw_parts_mut( + &mut self.array as *mut T as *mut u8, + N * core::mem::size_of::(), + ) + } + } + fn as_byte_slice(&self) -> &[u8] { + // SAFETY: This is safe because the size of the array is the same as the size of the slice + unsafe { + core::slice::from_raw_parts( + &self.array as *const T as *const u8, + N * core::mem::size_of::(), + ) + } + } +} + +#[cfg(feature = "use-unsafe")] +impl Borrow<[u8]> for BytesHolder { + fn borrow(&self) -> &[u8] { + self.as_byte_slice() + } +} +#[cfg(feature = "use-unsafe")] +impl BorrowMut<[u8]> for BytesHolder { + fn borrow_mut(&mut self) -> &mut [u8] { + self.as_byte_slice_mut() + } +} +#[cfg(feature = "use-unsafe")] +impl AsRef<[u8]> for BytesHolder { + fn as_ref(&self) -> &[u8] { + self.as_byte_slice() + } +} +#[cfg(feature = "use-unsafe")] +impl AsMut<[u8]> for BytesHolder { + fn as_mut(&mut self) -> &mut [u8] { + self.as_byte_slice_mut() + } +} +#[cfg(feature = "use-unsafe")] +impl Hash for BytesHolder { + fn hash(&self, _state: &mut H) { + todo!() + } +} + +#[cfg(feature = "use-unsafe")] +impl num_traits::ToBytes for FixedUInt +where + T: core::fmt::Debug, +{ + type Bytes = BytesHolder; + + fn to_be_bytes(&self) -> Self::Bytes { + let mut ret = Self::Bytes { array: self.array }; + let _ = self.to_be_bytes(ret.as_byte_slice_mut()); + ret + } + + fn to_le_bytes(&self) -> Self::Bytes { + let mut ret = Self::Bytes { array: self.array }; + let _ = self.to_le_bytes(ret.as_byte_slice_mut()); + ret + } +} + #[cfg(test)] mod tests { use super::FixedUInt as Bn; @@ -1558,4 +1697,62 @@ mod tests { let f4 = Bn8::from_u64(990224).unwrap(); assert!(f4 > Bn8::from_u64(990223).unwrap()); } + + #[test] + fn test_le_be_bytes() { + let le_bytes = [1, 2, 3, 4]; + let be_bytes = [4, 3, 2, 1]; + let u8_ver = FixedUInt::::from_le_bytes(&le_bytes); + let u16_ver = FixedUInt::::from_le_bytes(&le_bytes); + let u32_ver = FixedUInt::::from_le_bytes(&le_bytes); + let u8_ver_be = FixedUInt::::from_be_bytes(&be_bytes); + let u16_ver_be = FixedUInt::::from_be_bytes(&be_bytes); + let u32_ver_be = FixedUInt::::from_be_bytes(&be_bytes); + + assert_eq!(u8_ver.array, [1, 2, 3, 4]); + assert_eq!(u16_ver.array, [0x0201, 0x0403]); + assert_eq!(u32_ver.array, [0x04030201]); + assert_eq!(u8_ver_be.array, [1, 2, 3, 4]); + assert_eq!(u16_ver_be.array, [0x0201, 0x0403]); + assert_eq!(u32_ver_be.array, [0x04030201]); + + let mut output_buffer = [0u8; 16]; + assert_eq!(u8_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes); + assert_eq!(u8_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes); + assert_eq!(u16_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes); + assert_eq!(u16_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes); + assert_eq!(u32_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes); + assert_eq!(u32_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes); + } + + fn test_helper(input: &T, expected_be: &[u8]) { + let mut buffer = [0u8; 256]; + buffer[..expected_be.len()].copy_from_slice(expected_be); + let expected_le = &mut buffer[..expected_be.len()]; + expected_le.reverse(); + + let out = input.to_be_bytes(); + assert_eq!(out.as_ref(), expected_be); + let out = input.to_le_bytes(); + assert_eq!(out.as_ref(), expected_le); + } + + #[cfg(feature = "use-unsafe")] + #[test] + fn test_to_bytes() { + test_helper(&0xAB_u8, &[0xAB_u8]); + test_helper(&0xABCD_u16, &[0xAB, 0xCD]); + test_helper( + &FixedUInt::::from_u32(0x12345678).unwrap(), + &[0x12, 0x34, 0x56, 0x78], + ); + test_helper( + &FixedUInt::::from_u32(0x12345678).unwrap(), + &[0x12, 0x34, 0x56, 0x78], + ); + test_helper( + &FixedUInt::::from_u32(0x12345678).unwrap(), + &[0x12, 0x34, 0x56, 0x78], + ); + } } diff --git a/src/machineword.rs b/src/machineword.rs index 307f45d..c3647d4 100644 --- a/src/machineword.rs +++ b/src/machineword.rs @@ -22,6 +22,8 @@ pub trait MachineWord: + core::ops::BitAndAssign + core::ops::BitOrAssign + core::ops::BitXorAssign + + num_traits::FromBytes + + num_traits::ToBytes { type DoubleWord: num_traits::PrimInt; fn to_double(self) -> Self::DoubleWord;