#![allow(clippy::indexing_slicing)]
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum InvalidHexError {
InvalidLength,
InvalidChar {
byte: u8,
index: usize,
},
}
impl fmt::Display for InvalidHexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength => f.write_str("invalid hex length"),
Self::InvalidChar { byte, index } => {
write!(f, "invalid hex character 0x{byte:02x} at index {index}")
}
}
}
}
impl core::error::Error for InvalidHexError {}
#[inline]
const fn decode_nibble(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
b'A'..=b'F' => Some(byte - b'A' + 10),
_ => None,
}
}
pub fn from_hex(hex: &str, out: &mut [u8]) -> Result<(), InvalidHexError> {
let hex = hex.as_bytes();
if hex.len() != out.len().strict_mul(2) {
return Err(InvalidHexError::InvalidLength);
}
let mut i = 0;
while i < out.len() {
let hi_idx = i.strict_mul(2);
let lo_idx = hi_idx.strict_add(1);
let hi = match decode_nibble(hex[hi_idx]) {
Some(v) => v,
None => {
return Err(InvalidHexError::InvalidChar {
byte: hex[hi_idx],
index: hi_idx,
});
}
};
let lo = match decode_nibble(hex[lo_idx]) {
Some(v) => v,
None => {
return Err(InvalidHexError::InvalidChar {
byte: hex[lo_idx],
index: lo_idx,
});
}
};
out[i] = (hi << 4) | lo;
i = i.strict_add(1);
}
Ok(())
}
pub fn fmt_hex_lower(bytes: &[u8], f: &mut fmt::Formatter<'_>) -> fmt::Result {
for &b in bytes {
write!(f, "{b:02x}")?;
}
Ok(())
}
pub fn fmt_hex_upper(bytes: &[u8], f: &mut fmt::Formatter<'_>) -> fmt::Result {
for &b in bytes {
write!(f, "{b:02X}")?;
}
Ok(())
}
pub struct DisplaySecret<'a>(pub(crate) &'a [u8]);
impl fmt::Display for DisplaySecret<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_hex_lower(self.0, f)
}
}
impl fmt::Debug for DisplaySecret<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DisplaySecret(\"")?;
fmt_hex_lower(self.0, f)?;
write!(f, "\")")
}
}
macro_rules! impl_hex_fmt {
($type:ty) => {
impl core::fmt::LowerHex for $type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
crate::hex::fmt_hex_lower(self.as_bytes(), f)
}
}
impl core::fmt::UpperHex for $type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
crate::hex::fmt_hex_upper(self.as_bytes(), f)
}
}
impl core::fmt::Display for $type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::LowerHex::fmt(self, f)
}
}
impl core::str::FromStr for $type {
type Err = crate::hex::InvalidHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut buf = [0u8; Self::LENGTH];
crate::hex::from_hex(s, &mut buf)?;
Ok(Self::from_bytes(buf))
}
}
};
}
macro_rules! impl_hex_fmt_secret {
($type:ty) => {
impl core::str::FromStr for $type {
type Err = crate::hex::InvalidHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut buf = [0u8; Self::LENGTH];
crate::hex::from_hex(s, &mut buf)?;
Ok(Self::from_bytes(buf))
}
}
impl $type {
#[must_use]
pub fn display_secret(&self) -> crate::hex::DisplaySecret<'_> {
crate::hex::DisplaySecret(self.as_bytes())
}
}
};
}
#[cfg(feature = "serde")]
macro_rules! impl_serde_bytes_inner {
($type:ty, $feature:literal) => {
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
impl serde::Serialize for $type {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_bytes(self.as_bytes())
}
}
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
impl<'de> serde::Deserialize<'de> for $type {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct ByteVisitor;
impl<'de> serde::de::Visitor<'de> for ByteVisitor {
type Value = $type;
fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} bytes", <$type>::LENGTH)
}
fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
let arr: [u8; <$type>::LENGTH] = v.try_into().map_err(|_| E::invalid_length(v.len(), &self))?;
Ok(<$type>::from_bytes(arr))
}
fn visit_seq<A: serde::de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let mut arr = [0u8; <$type>::LENGTH];
for (i, byte) in arr.iter_mut().enumerate() {
*byte = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(i, &self))?;
}
Ok(<$type>::from_bytes(arr))
}
}
deserializer.deserialize_bytes(ByteVisitor)
}
}
};
}
#[cfg(feature = "serde")]
macro_rules! impl_serde_bytes {
($type:ty) => {
impl_serde_bytes_inner!($type, "serde");
};
}
#[cfg(not(feature = "serde"))]
macro_rules! impl_serde_bytes {
($type:ty) => {};
}
#[cfg(feature = "serde-secrets")]
macro_rules! impl_serde_secret_bytes {
($type:ty) => {
impl_serde_bytes_inner!($type, "serde-secrets");
};
}
#[cfg(not(feature = "serde-secrets"))]
macro_rules! impl_serde_secret_bytes {
($type:ty) => {};
}
macro_rules! impl_ct_eq {
($type:ty) => {
impl crate::traits::ConstantTimeEq for $type {
#[inline]
fn ct_eq(&self, other: &Self) -> bool {
crate::traits::ct::constant_time_eq(&self.0, &other.0)
}
}
};
($type:ty, $field:ident) => {
impl crate::traits::ConstantTimeEq for $type {
#[inline]
fn ct_eq(&self, other: &Self) -> bool {
crate::traits::ct::constant_time_eq(&self.$field, &other.$field)
}
}
};
}
macro_rules! impl_getrandom {
() => {
#[cfg(feature = "getrandom")]
#[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))]
#[inline]
#[must_use]
pub fn random() -> Self {
match Self::try_random() {
Ok(value) => value,
Err(e) => panic!("getrandom failed: {e}"),
}
}
#[cfg(feature = "getrandom")]
#[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))]
#[inline]
pub fn try_random() -> Result<Self, getrandom::Error> {
let mut bytes = [0u8; Self::LENGTH];
getrandom::fill(&mut bytes).map(|()| Self(bytes))
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_lower_round_trip() {
let bytes = [0xde, 0xad, 0xbe, 0xef];
let hex = alloc::format!("{}", DisplaySecret(&bytes));
assert_eq!(hex, "deadbeef");
let mut out = [0u8; 4];
from_hex(&hex, &mut out).unwrap();
assert_eq!(out, bytes);
}
#[test]
fn hex_upper() {
struct W([u8; 2]);
impl fmt::Display for W {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_hex_upper(&self.0, f)
}
}
assert_eq!(alloc::format!("{}", W([0xab, 0xcd])), "ABCD");
}
#[test]
fn from_hex_mixed_case() {
let mut out = [0u8; 3];
from_hex("aAbBcC", &mut out).unwrap();
assert_eq!(out, [0xaa, 0xbb, 0xcc]);
}
#[test]
fn from_hex_invalid_length() {
let mut out = [0u8; 2];
assert_eq!(from_hex("abc", &mut out), Err(InvalidHexError::InvalidLength));
}
#[test]
fn from_hex_invalid_char() {
let mut out = [0u8; 2];
let err = from_hex("abzz", &mut out).unwrap_err();
assert_eq!(err, InvalidHexError::InvalidChar { byte: b'z', index: 2 });
}
#[test]
fn display_secret_debug() {
let bytes = [0x42; 4];
let d = DisplaySecret(&bytes);
assert_eq!(alloc::format!("{d:?}"), "DisplaySecret(\"42424242\")");
}
#[test]
fn error_display() {
assert_eq!(
alloc::format!("{}", InvalidHexError::InvalidLength),
"invalid hex length"
);
assert_eq!(
alloc::format!("{}", InvalidHexError::InvalidChar { byte: b'z', index: 5 }),
"invalid hex character 0x7a at index 5"
);
}
}