Skip to content

Commit

Permalink
core: Add Id::clean_hex_path
Browse files Browse the repository at this point in the history
  • Loading branch information
robin-nitrokey committed Dec 17, 2024
1 parent 793a10c commit 5c19c3a
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved hex formatting of `types::Id`:
- Removed the unused `Id::hex`.
- Deprecated `Id::hex_path` and added `Id::legacy_hex_path` as a replacement.
- Added `Id::clean_hex_path` as an alternative to `Id::legacy_hex_path`.

### Fixed

Expand Down
94 changes: 88 additions & 6 deletions core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ impl Id {
/// compatibility with old Trussed versions, this implementation also skips inner bytes that
/// are zero, except for the trailing byte. This means that for example 4096 and 1048576 both
/// are formatted as `"1000"`.
///
/// For new features that don’t need backwards-compatibility, use [`Id::clean_hex_path`][]
/// instead.
pub fn legacy_hex_path(&self) -> PathBuf {
const HEX_CHARS: &[u8] = b"0123456789abcdef";
let mut buffer = [0; PathBuf::MAX_SIZE_PLUS_ONE];
Expand Down Expand Up @@ -120,7 +123,29 @@ impl Id {
}
}

/// skips leading zeros
/// Hex path of this ID without leading zero bytes.
///
/// This uses the same format as [`Id::hex_clean`][]. Note that the first `hex_path`
/// implementation, now available as [`Id::legacy_hex_path`][], skipped all non-trailing zero
/// bytes and should only be used if backwards compatibility is required.
pub fn clean_hex_path(&self) -> PathBuf {
let mut buffer = [0; PathBuf::MAX_SIZE_PLUS_ONE];

let array = self.0.to_be_bytes();
for (i, c) in HexCleanBytes::new(&array).enumerate() {
buffer[i] = c as _;
}

// SAFETY:
// 1. We only add characters from HEX_CHARS which only contains ASCII characters.
// 2. We initialized the buffer with zeroes so there is still a trailing zero.
unsafe { PathBuf::from_buffer_unchecked(buffer) }
}

/// Hex representation of this ID without leading zeroes.
///
/// This implementation skips all leading bytes that are zero so that the resulting hex string
/// always has an even number of characters and does not start with more than one zero.
pub fn hex_clean(&self) -> HexClean {
HexClean(self.0)
}
Expand Down Expand Up @@ -171,20 +196,62 @@ impl<'de> Deserialize<'de> for Id {
}

/// Hex representation of an `u128` without leading zeroes.
///
/// This implementation skips all leading bytes that are zero so that the resulting hex string
/// always has an even number of characters and does not start with more than one zero.
pub struct HexClean(pub u128);

impl core::fmt::Display for HexClean {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
const HEX_CHARS: &[u8] = b"0123456789abcdef";
// skip leading zeros
for v in self.0.to_be_bytes().into_iter().skip_while(|v| *v == 0) {
write!(f, "{}", HEX_CHARS[(v >> 4) as usize] as char)?;
write!(f, "{}", HEX_CHARS[(v & 0xf) as usize] as char)?;
let array = self.0.to_be_bytes();
for c in HexCleanBytes::new(&array) {
write!(f, "{}", char::from(c))?;
}
Ok(())
}
}

struct HexCleanBytes<'a> {
array: &'a [u8],
upper: bool,
}

impl<'a> HexCleanBytes<'a> {
fn new(array: &'a [u8]) -> Self {
if let Some(i) = array.iter().position(|&v| v != 0) {
Self {
array: &array[i..],
upper: true,
}
} else {
Self {
array: &[],
upper: true,
}
}
}
}

impl Iterator for HexCleanBytes<'_> {
type Item = u8;

fn next(&mut self) -> Option<u8> {
const HEX_CHARS: &[u8] = b"0123456789abcdef";
if let Some((v, rest)) = self.array.split_first() {
if self.upper {
self.upper = false;
Some(HEX_CHARS[(v >> 4) as usize])
} else {
self.upper = true;
self.array = rest;
Some(HEX_CHARS[(v & 0xf) as usize])
}
} else {
None
}
}
}

pub trait ObjectId: Deref<Target = Id> {}

macro_rules! impl_id {
Expand Down Expand Up @@ -460,6 +527,21 @@ mod tests {
);
}

#[test]
fn test_id_clean_hex_path() {
assert_eq!(Id(0).clean_hex_path().as_str(), "");
assert_eq!(Id(1).clean_hex_path().as_str(), "01");
assert_eq!(Id(10).clean_hex_path().as_str(), "0a");
assert_eq!(Id(16).clean_hex_path().as_str(), "10");
assert_eq!(Id(256).clean_hex_path().as_str(), "0100");
assert_eq!(Id(4096).clean_hex_path().as_str(), "1000");
assert_eq!(Id(1048576).clean_hex_path().as_str(), "100000");
assert_eq!(
Id(u128::MAX).clean_hex_path().as_str(),
"ffffffffffffffffffffffffffffffff"
);
}

#[test]
fn test_id_hex_clean() {
assert_eq!(Id(0).hex_clean().to_string(), "");
Expand Down

0 comments on commit 5c19c3a

Please sign in to comment.