Skip to content

Commit

Permalink
Adds ToBytes implementation, optional zeroize support (#37)
Browse files Browse the repository at this point in the history
Unfortunately relies on unsafe to return byte slices for `to_be_bytes`
  • Loading branch information
kaidokert authored Sep 23, 2024
1 parent 873dcc8 commit 13a7fdb
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,3 +37,7 @@ lto = false
panic = 'unwind'
incremental = false
codegen-units = 16

[features]
use-unsafe = []
default = []
197 changes: 197 additions & 0 deletions src/fixeduint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -81,6 +88,55 @@ impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
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;
Expand Down Expand Up @@ -1341,6 +1397,89 @@ impl<T: MachineWord, const N: usize> num_integer::Integer for FixedUInt<T, N> {

// #endregion num-integer::Integer

#[cfg(feature = "zeroize")]
impl<T: MachineWord, const N: usize> DefaultIsZeroes for FixedUInt<T, N> {}

// Helper, holds an owned copy of returned bytes
#[derive(Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Debug)]
pub struct BytesHolder<T: MachineWord, const N: usize> {
array: [T; N],
}

#[cfg(feature = "use-unsafe")]
impl<T: MachineWord, const N: usize> BytesHolder<T, N> {
// 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::<T>(),
)
}
}
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::<T>(),
)
}
}
}

#[cfg(feature = "use-unsafe")]
impl<T: MachineWord, const N: usize> Borrow<[u8]> for BytesHolder<T, N> {
fn borrow(&self) -> &[u8] {
self.as_byte_slice()
}
}
#[cfg(feature = "use-unsafe")]
impl<T: MachineWord, const N: usize> BorrowMut<[u8]> for BytesHolder<T, N> {
fn borrow_mut(&mut self) -> &mut [u8] {
self.as_byte_slice_mut()
}
}
#[cfg(feature = "use-unsafe")]
impl<T: MachineWord, const N: usize> AsRef<[u8]> for BytesHolder<T, N> {
fn as_ref(&self) -> &[u8] {
self.as_byte_slice()
}
}
#[cfg(feature = "use-unsafe")]
impl<T: MachineWord, const N: usize> AsMut<[u8]> for BytesHolder<T, N> {
fn as_mut(&mut self) -> &mut [u8] {
self.as_byte_slice_mut()
}
}
#[cfg(feature = "use-unsafe")]
impl<T: MachineWord, const N: usize> Hash for BytesHolder<T, N> {
fn hash<H: core::hash::Hasher>(&self, _state: &mut H) {
todo!()
}
}

#[cfg(feature = "use-unsafe")]
impl<T: MachineWord, const N: usize> num_traits::ToBytes for FixedUInt<T, N>
where
T: core::fmt::Debug,
{
type Bytes = BytesHolder<T, N>;

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;
Expand Down Expand Up @@ -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::<u8, 4>::from_le_bytes(&le_bytes);
let u16_ver = FixedUInt::<u16, 2>::from_le_bytes(&le_bytes);
let u32_ver = FixedUInt::<u32, 1>::from_le_bytes(&le_bytes);
let u8_ver_be = FixedUInt::<u8, 4>::from_be_bytes(&be_bytes);
let u16_ver_be = FixedUInt::<u16, 2>::from_be_bytes(&be_bytes);
let u32_ver_be = FixedUInt::<u32, 1>::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<T: num_traits::ToBytes>(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::<u8, 4>::from_u32(0x12345678).unwrap(),
&[0x12, 0x34, 0x56, 0x78],
);
test_helper(
&FixedUInt::<u16, 2>::from_u32(0x12345678).unwrap(),
&[0x12, 0x34, 0x56, 0x78],
);
test_helper(
&FixedUInt::<u32, 1>::from_u32(0x12345678).unwrap(),
&[0x12, 0x34, 0x56, 0x78],
);
}
}
2 changes: 2 additions & 0 deletions src/machineword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 13a7fdb

Please sign in to comment.