#![doc(
html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
html_favicon_url = "https://commonware.xyz/favicon.ico"
)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]
commonware_macros::stability_mod!(BETA, pub mod hex_literal);
commonware_macros::stability_scope!(BETA {
extern crate alloc;
use alloc::{string::String, vec::Vec};
use core::fmt;
pub fn hex(bytes: &[u8]) -> String {
const_hex::encode(bytes)
}
pub fn from_hex(s: &str) -> Option<Vec<u8>> {
let s = s.replace(['\t', '\n', '\r', ' '], "");
let stripped = s
.strip_prefix("0X")
.unwrap_or(&s);
const_hex::decode(stripped).ok()
}
pub struct Hex<T: AsRef<[u8]>>(pub T);
impl<T: AsRef<[u8]>> From<T> for Hex<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T: AsRef<[u8]>> fmt::Display for Hex<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_hex(self.0.as_ref(), f)
}
}
impl<T: AsRef<[u8]>> fmt::Debug for Hex<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_hex(self.0.as_ref(), f)
}
}
fn write_hex(bytes: &[u8], f: &mut fmt::Formatter<'_>) -> fmt::Result {
const CHUNK: usize = 64;
let mut buf = [0u8; CHUNK * 2];
for slice in bytes.chunks(CHUNK) {
let out = &mut buf[..slice.len() * 2];
const_hex::encode_to_slice(slice, out).expect("slice fits in buffer");
let s = unsafe { core::str::from_utf8_unchecked(out) };
f.write_str(s)?;
}
Ok(())
}
});
#[cfg(test)]
mod tests {
use crate::{from_hex, Hex};
#[test]
fn test_hex_roundtrip() {
for (bytes, encoded) in [
(&[][..], ""),
(&[0x01][..], "01"),
(&[0x01, 0x02, 0x03][..], "010203"),
] {
assert_eq!(crate::hex(bytes), encoded);
assert_eq!(from_hex(encoded).unwrap(), bytes.to_vec());
}
}
#[test]
fn test_from_hex() {
let expected: Vec<u8> = vec![0x01, 0x02, 0x03];
assert_eq!(from_hex("010203").unwrap(), expected);
assert_eq!(from_hex("01 02 03").unwrap(), expected);
assert_eq!(from_hex("0x010203").unwrap(), expected);
assert_eq!(from_hex("0X010203").unwrap(), expected);
let h = " \n\n0x\r\n01
02\t03\n";
assert_eq!(from_hex(h).unwrap(), expected);
assert_eq!(from_hex(""), Some(vec![]));
assert!(from_hex("0102030").is_none());
assert!(from_hex("01g3").is_none());
assert!(from_hex("+123").is_none());
}
#[test]
fn test_from_hex_utf8_char_boundaries() {
const MISALIGNMENT_CASE: &str = "쀘\n";
assert!(from_hex(MISALIGNMENT_CASE).is_none());
}
#[test]
fn test_hex_newtype_display() {
let bytes = [0x01u8, 0x02, 0xab, 0xcd];
let s = format!("{}", Hex(&bytes[..]));
assert_eq!(s, "0102abcd");
let v = bytes.to_vec();
assert_eq!(format!("{}", Hex(v)), "0102abcd");
assert_eq!(format!("{}", Hex::<&[u8]>(&[])), "");
let big: Vec<u8> = (0..200u16).map(|i| i as u8).collect();
let formatted = format!("{}", Hex(&big));
assert_eq!(formatted, super::hex(&big));
}
#[test]
fn test_hex_newtype_debug() {
let bytes = [0xff, 0x00];
assert_eq!(format!("{:?}", Hex(&bytes[..])), "ff00");
}
#[test]
fn test_hex_newtype_from() {
let bytes = [0x01u8, 0x02, 0xab, 0xcd];
let from_slice: Hex<&[u8]> = (&bytes[..]).into();
assert_eq!(format!("{from_slice}"), "0102abcd");
let from_owned: Hex<Vec<u8>> = bytes.to_vec().into();
assert_eq!(format!("{from_owned}"), "0102abcd");
}
}