#[inline]
#[must_use]
pub const fn nibble(b: u8) -> Option<u8> {
let d = b.wrapping_sub(b'0');
if d < 10 {
return Some(d);
}
let l = (b | 0x20).wrapping_sub(b'a');
if l < 6 {
return Some(l + 10);
}
None
}
#[inline]
#[must_use]
pub const fn decode_pair(hi: u8, lo: u8) -> Option<u8> {
match (nibble(hi), nibble(lo)) {
(Some(h), Some(l)) => Some((h << 4) | l),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nibble_exhaustive_256_bytes() {
for b in 0u8..=255 {
let got = nibble(b);
let expected = match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
};
assert_eq!(got, expected, "nibble(0x{b:02X})");
}
}
#[test]
fn nibble_boundary_bytes() {
for b in [b'/', b':', b'@', b'G', b'`', b'g', 0x80, 0xFF] {
assert_eq!(nibble(b), None, "boundary byte 0x{b:02X}");
}
}
#[test]
fn decode_pair_round_trip() {
for b in 0u8..=255 {
let hi_nib = b >> 4;
let lo_nib = b & 0x0F;
let hi_char = if hi_nib < 10 {
b'0' + hi_nib
} else {
b'A' + hi_nib - 10
};
let lo_char = if lo_nib < 10 {
b'0' + lo_nib
} else {
b'a' + lo_nib - 10
};
assert_eq!(decode_pair(hi_char, lo_char), Some(b));
}
}
#[test]
fn decode_pair_rejects_non_hex() {
assert_eq!(decode_pair(b'Z', b'0'), None);
assert_eq!(decode_pair(b'0', b'Z'), None);
assert_eq!(decode_pair(b' ', b'0'), None);
}
}