use std::{
array::TryFromSliceError,
fmt::{self, Debug, Display},
hash::Hash,
};
#[doc(hidden)]
pub use lexe_hex;
use lexe_hex::hex::{self, FromHex, HexDisplay};
pub trait ByteArray<const N: usize>: Copy + Debug + Eq + Hash + Sized {
fn from_array(array: [u8; N]) -> Self;
fn to_array(&self) -> [u8; N];
fn as_array(&self) -> &[u8; N];
fn as_slice(&self) -> &[u8] {
self.as_array().as_slice()
}
fn to_vec(&self) -> Vec<u8> {
self.as_slice().to_vec()
}
fn try_from_slice(slice: &[u8]) -> Result<Self, TryFromSliceError> {
<[u8; N]>::try_from(slice).map(Self::from_array)
}
fn try_from_vec(vec: Vec<u8>) -> Result<Self, TryFromSliceError> {
Self::try_from_slice(&vec)
}
fn from_hex(s: &str) -> Result<Self, hex::DecodeError> {
<[u8; N]>::from_hex(s).map(Self::from_array)
}
fn to_hex(&self) -> String {
hex::encode(self.as_slice())
}
fn as_hex_display(&self) -> HexDisplay<'_> {
hex::display(self.as_slice())
}
fn fmt_as_hex(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.as_hex_display(), f)
}
}
#[macro_export]
macro_rules! impl_byte_array {
($type:ty, $n:expr) => {
impl ByteArray<$n> for $type {
fn from_array(array: [u8; $n]) -> Self {
Self(array)
}
fn to_array(&self) -> [u8; $n] {
self.0
}
fn as_array(&self) -> &[u8; $n] {
&self.0
}
}
impl AsRef<[u8]> for $type {
fn as_ref(&self) -> &[u8] {
self.0.as_slice()
}
}
impl AsRef<[u8; $n]> for $type {
fn as_ref(&self) -> &[u8; $n] {
&self.0
}
}
};
}
#[macro_export]
macro_rules! impl_fromstr_fromhex {
($type:ty, $n:expr) => {
impl std::str::FromStr for $type {
type Err = $crate::lexe_hex::hex::DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
<Self as ByteArray<$n>>::from_hex(s)
}
}
impl $crate::lexe_hex::hex::FromHex for $type {
fn from_hex(
s: &str,
) -> Result<Self, $crate::lexe_hex::hex::DecodeError> {
<[u8; $n]>::from_hex(s).map(Self::from_array)
}
}
};
}
#[macro_export]
macro_rules! impl_debug_display_as_hex {
($type:ty) => {
impl std::fmt::Debug for $type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}(\"{}\")",
stringify!($type),
self.as_hex_display()
)
}
}
impl std::fmt::Display for $type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Self::fmt_as_hex(self, f)
}
}
};
}
#[macro_export]
macro_rules! impl_debug_display_redacted {
($type:ty) => {
impl std::fmt::Debug for $type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(concat!(stringify!($type), "(..)"))
}
}
impl std::fmt::Display for $type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("..")
}
}
};
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use super::*;
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
struct MyStruct([u8; 4]);
impl_byte_array!(MyStruct, 4);
impl_fromstr_fromhex!(MyStruct, 4);
impl_debug_display_as_hex!(MyStruct);
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
struct MySecret([u8; 4]);
impl_byte_array!(MySecret, 4);
impl_fromstr_fromhex!(MySecret, 4);
impl_debug_display_redacted!(MySecret);
#[test]
fn test_display_and_debug() {
let data = [0xde, 0xad, 0xbe, 0xef];
let my_struct = MyStruct(data);
assert_eq!(my_struct.to_string(), "deadbeef");
assert_eq!(format!("{my_struct}"), "deadbeef");
assert_eq!(format!("{my_struct:#}"), "deadbeef");
assert_eq!(format!("{:?}", my_struct), r#"MyStruct("deadbeef")"#);
assert_eq!(format!("{:#?}", my_struct), r#"MyStruct("deadbeef")"#);
assert_eq!(my_struct.as_hex_display().to_string(), "deadbeef");
assert_eq!(my_struct.to_hex(), "deadbeef");
let my_secret = MySecret(data);
assert_eq!(my_secret.to_string(), "..");
assert_eq!(format!("My secret is {my_secret}"), "My secret is ..");
assert_eq!(format!("My secret is {my_secret:#}"), "My secret is ..");
assert_eq!(format!("{:?}", my_secret), r#"MySecret(..)"#);
assert_eq!(format!("{:#?}", my_secret), r#"MySecret(..)"#);
}
#[test]
fn basic_parse() {
let my_struct = MyStruct::from_str("deadbeef").unwrap();
assert_eq!(my_struct.0, [0xde, 0xad, 0xbe, 0xef]);
let my_secret = MySecret::from_str("deadbeef").unwrap();
assert_eq!(my_secret.0, [0xde, 0xad, 0xbe, 0xef]);
MyStruct::from_str("invalid").unwrap_err();
MyStruct::from_str("deadbee").unwrap_err(); MyStruct::from_str("deadbeefff").unwrap_err(); MyStruct::from_str("wxyz").unwrap_err(); }
}