use serde_json::Value;
use crate::{
Decode,
de::Error,
error::{DecodeError, JerDecodeErrorKind},
types::{
Any, BitString, BmpString, Constraints, Constructed, Date, DecodeChoice, Enumerated,
GeneralString, GeneralizedTime, GraphicString, Ia5String, NumericString, ObjectIdentifier,
Oid, PrintableString, SequenceOf, SetOf, Tag, TeletexString, UtcTime, Utf8String,
VisibleString, variants,
},
};
macro_rules! decode_jer_value {
($decoder_fn:expr, $input:expr) => {
$input
.pop()
.flatten()
.ok_or_else(|| DecodeError::from(JerDecodeErrorKind::eoi()))
.and_then($decoder_fn)
};
}
pub struct Decoder {
stack: alloc::vec::Vec<Option<Value>>,
}
impl Decoder {
pub fn new(input: &str) -> Result<Self, <Decoder as crate::de::Decoder>::Error> {
let root = serde_json::from_str(input).map_err(|e| {
DecodeError::parser_fail(
alloc::format!("Error parsing JER JSON {e:?}"),
crate::Codec::Jer,
)
})?;
Ok(Self {
stack: alloc::vec![Some(root)],
})
}
}
impl From<Value> for Decoder {
fn from(value: Value) -> Self {
Self {
stack: alloc::vec![Some(value)],
}
}
}
impl crate::Decoder for Decoder {
type Ok = ();
type Error = DecodeError;
type AnyDecoder<const R: usize, const E: usize> = Self;
fn decode_any(&mut self, _tag: Tag) -> Result<Any, Self::Error> {
decode_jer_value!(Self::any_from_value, self.stack)
}
fn decode_bit_string(
&mut self,
_t: Tag,
constraints: Constraints,
) -> Result<BitString, Self::Error> {
let (mut padded, bitstring_length) = if let Some(size) = constraints
.size()
.and_then(|s| s.constraint.is_fixed().then_some(s.constraint.as_start()))
.flatten()
{
let value = BitString::try_from_vec(decode_jer_value!(
Self::octet_string_from_value,
self.stack
)?)
.map_err(|e| {
DecodeError::custom(
alloc::format!("Failed to create BitString from bytes: {e:02x?}"),
self.codec(),
)
})?;
(value, *size)
} else {
let last = self
.stack
.pop()
.flatten()
.ok_or_else(JerDecodeErrorKind::eoi)?;
let value_map = last
.as_object()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "object",
found: "unknown".into(),
})?;
let (value, length) = value_map
.get("value")
.and_then(|v| v.as_str())
.zip(
value_map
.get("length")
.and_then(|l| l.as_number())
.and_then(|i| i.as_u64())
.map(|i| i as usize),
)
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "JSON object containing 'value' and 'length' properties.",
found: alloc::format!("{value_map:#?}"),
})?;
let value = bytes_from_hexstring(value).ok_or(DecodeError::custom(
alloc::format!("Failed to create BitString from bytes: {value:02x?}"),
self.codec(),
))?;
let value = BitString::try_from_vec(value).map_err(|e| {
DecodeError::custom(
alloc::format!("Failed to create BitString from bytes: {e:02x?}"),
self.codec(),
)
})?;
(value, length)
};
let padding_length = if bitstring_length % 8 == 0 {
0
} else {
8 - (bitstring_length % 8)
};
for _ in 0..padding_length {
padded.pop();
}
if bitstring_length == padded.len() {
Ok(padded)
} else {
Err(DecodeError::custom(
alloc::format!(
"Failed to create BitString from bytes: invalid value length (was: {}, expected: {})",
padded.len(),
bitstring_length
),
self.codec(),
))
}
}
fn decode_bool(&mut self, _t: Tag) -> Result<bool, Self::Error> {
decode_jer_value!(Self::boolean_from_value, self.stack)
}
fn decode_enumerated<E: Enumerated>(&mut self, _t: Tag) -> Result<E, Self::Error> {
decode_jer_value!(Self::enumerated_from_value, self.stack)
}
fn decode_integer<I: crate::types::IntegerType>(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<I, Self::Error> {
decode_jer_value!(Self::integer_from_value::<I>, self.stack)
}
fn decode_real<R: crate::types::RealType>(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<R, Self::Error> {
decode_jer_value!(Self::real_from_value::<R>, self.stack)
}
fn decode_null(&mut self, _t: Tag) -> Result<(), Self::Error> {
decode_jer_value!(Self::null_from_value, self.stack)
}
fn decode_object_identifier(&mut self, _t: Tag) -> Result<ObjectIdentifier, Self::Error> {
decode_jer_value!(Self::object_identifier_from_value, self.stack)
}
fn decode_sequence<const RC: usize, const EC: usize, D, DF, F>(
&mut self,
_: Tag,
_: Option<DF>,
decode_fn: F,
) -> Result<D, Self::Error>
where
D: Constructed<RC, EC>,
F: FnOnce(&mut Self) -> Result<D, Self::Error>,
{
let mut last = self
.stack
.pop()
.flatten()
.ok_or_else(JerDecodeErrorKind::eoi)?;
let value_map = last
.as_object_mut()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "object",
found: "unknown".into(),
})?;
let mut field_names = D::FIELDS
.iter()
.map(|f| f.name)
.collect::<alloc::vec::Vec<&str>>();
if let Some(extended_fields) = D::EXTENDED_FIELDS {
field_names.extend(extended_fields.iter().map(|f| f.name));
}
field_names.reverse();
for name in field_names {
self.stack.push(value_map.remove(name));
}
(decode_fn)(self)
}
fn decode_sequence_of<D: crate::Decode>(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<SequenceOf<D>, Self::Error> {
decode_jer_value!(|v| self.sequence_of_from_value(v), self.stack)
}
fn decode_set_of<D: crate::Decode + Eq + core::hash::Hash>(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<SetOf<D>, Self::Error> {
decode_jer_value!(|v| self.set_of_from_value(v), self.stack)
}
fn decode_octet_string<'b, T: From<alloc::vec::Vec<u8>> + From<&'b [u8]>>(
&'b mut self,
_: Tag,
_c: Constraints,
) -> Result<T, Self::Error> {
decode_jer_value!(Self::octet_string_from_value, self.stack).map(T::from)
}
fn decode_utf8_string(&mut self, _t: Tag, _c: Constraints) -> Result<Utf8String, Self::Error> {
decode_jer_value!(Self::string_from_value, self.stack)
}
fn decode_visible_string(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<VisibleString, Self::Error> {
decode_jer_value!(Self::string_from_value, self.stack)?
.try_into()
.map_err(|e| {
DecodeError::string_conversion_failed(
Tag::VISIBLE_STRING,
alloc::format!("Error transforming VisibleString: {e:?}"),
crate::Codec::Jer,
)
})
}
fn decode_general_string(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<GeneralString, Self::Error> {
decode_jer_value!(Self::string_from_value, self.stack)?
.try_into()
.map_err(|e| {
DecodeError::string_conversion_failed(
Tag::GENERAL_STRING,
alloc::format!("Error transforming GeneralString: {e:?}"),
crate::Codec::Jer,
)
})
}
fn decode_graphic_string(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<GraphicString, Self::Error> {
decode_jer_value!(Self::string_from_value, self.stack)?
.try_into()
.map_err(|e| {
DecodeError::string_conversion_failed(
Tag::GRAPHIC_STRING,
alloc::format!("Error transforming GeneralString: {e:?}"),
crate::Codec::Jer,
)
})
}
fn decode_ia5_string(&mut self, _t: Tag, _c: Constraints) -> Result<Ia5String, Self::Error> {
decode_jer_value!(Self::string_from_value, self.stack)?
.try_into()
.map_err(|e| {
DecodeError::string_conversion_failed(
Tag::IA5_STRING,
alloc::format!("Error transforming IA5String: {e:?}"),
crate::Codec::Jer,
)
})
}
fn decode_printable_string(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<PrintableString, Self::Error> {
decode_jer_value!(Self::string_from_value, self.stack)?
.try_into()
.map_err(|e| {
DecodeError::string_conversion_failed(
Tag::PRINTABLE_STRING,
alloc::format!("Error transforming PrintableString: {e:?}"),
crate::Codec::Jer,
)
})
}
fn decode_numeric_string(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<NumericString, Self::Error> {
decode_jer_value!(Self::string_from_value, self.stack)?
.try_into()
.map_err(|e| {
DecodeError::string_conversion_failed(
Tag::NUMERIC_STRING,
alloc::format!("Error transforming NumericString: {e:?}"),
crate::Codec::Jer,
)
})
}
fn decode_teletex_string(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<TeletexString, Self::Error> {
todo!()
}
fn decode_bmp_string(&mut self, _t: Tag, _c: Constraints) -> Result<BmpString, Self::Error> {
decode_jer_value!(Self::string_from_value, self.stack)?
.try_into()
.map_err(|e| {
DecodeError::string_conversion_failed(
Tag::BMP_STRING,
alloc::format!("Error transforming BMPString: {e:?}"),
crate::Codec::Jer,
)
})
}
fn decode_optional_with_explicit_prefix<D: Decode>(
&mut self,
_: Tag,
) -> Result<Option<D>, Self::Error> {
self.decode_optional()
}
fn decode_explicit_prefix<D: crate::Decode>(&mut self, _t: Tag) -> Result<D, Self::Error> {
D::decode(self)
}
fn decode_utc_time(&mut self, _t: Tag) -> Result<UtcTime, Self::Error> {
decode_jer_value!(Self::utc_time_from_value, self.stack)
}
fn decode_generalized_time(&mut self, _t: Tag) -> Result<GeneralizedTime, Self::Error> {
decode_jer_value!(Self::general_time_from_value, self.stack)
}
fn decode_date(&mut self, _t: Tag) -> Result<Date, Self::Error> {
decode_jer_value!(Self::date_from_value, self.stack)
}
fn decode_set<const RC: usize, const EC: usize, FIELDS, SET, D, F>(
&mut self,
_t: Tag,
decode_fn: D,
field_fn: F,
) -> Result<SET, Self::Error>
where
SET: crate::Decode + Constructed<RC, EC>,
FIELDS: crate::Decode,
D: Fn(&mut Self::AnyDecoder<RC, EC>, usize, Tag) -> Result<FIELDS, Self::Error>,
F: FnOnce(alloc::vec::Vec<FIELDS>) -> Result<SET, Self::Error>,
{
let mut last = self
.stack
.pop()
.flatten()
.ok_or_else(JerDecodeErrorKind::eoi)?;
let value_map = last
.as_object_mut()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "object",
found: "unknown".into(),
})?;
let mut field_indices = SET::FIELDS
.iter()
.enumerate()
.collect::<alloc::vec::Vec<_>>();
let mut fields = alloc::vec![];
field_indices
.sort_by(|(_, a), (_, b)| a.tag_tree.smallest_tag().cmp(&b.tag_tree.smallest_tag()));
for (index, field) in field_indices.into_iter() {
self.stack.push(value_map.remove(field.name));
fields.push((decode_fn)(self, index, field.tag)?);
}
for (index, field) in SET::EXTENDED_FIELDS
.iter()
.flat_map(|fields| fields.iter())
.enumerate()
{
self.stack.push(value_map.remove(field.name));
fields.push((decode_fn)(self, index + SET::FIELDS.len(), field.tag)?);
}
(field_fn)(fields)
}
fn decode_choice<D>(&mut self, _c: Constraints) -> Result<D, Self::Error>
where
D: DecodeChoice,
{
decode_jer_value!(|v| self.choice_from_value::<D>(v), self.stack)
}
fn decode_optional<D: crate::Decode>(&mut self) -> Result<Option<D>, Self::Error> {
match self.stack.pop() {
None => Err(DecodeError::from(JerDecodeErrorKind::eoi())),
Some(None) => Ok(None),
Some(Some(v)) => {
self.stack.push(Some(v));
Some(D::decode(self)).transpose()
}
}
}
fn decode_optional_with_tag<D: crate::Decode>(
&mut self,
_: Tag,
) -> Result<Option<D>, Self::Error> {
self.decode_optional()
}
fn decode_optional_with_constraints<D: crate::Decode>(
&mut self,
_: Constraints,
) -> Result<Option<D>, Self::Error> {
self.decode_optional()
}
fn decode_optional_with_tag_and_constraints<D: crate::Decode>(
&mut self,
_t: Tag,
_c: Constraints,
) -> Result<Option<D>, Self::Error> {
self.decode_optional()
}
fn decode_extension_addition_with_explicit_tag_and_constraints<D>(
&mut self,
tag: Tag,
constraints: Constraints,
) -> core::result::Result<Option<D>, Self::Error>
where
D: Decode,
{
self.decode_extension_addition_with_tag_and_constraints::<D>(tag, constraints)
}
fn decode_extension_addition_with_tag_and_constraints<D>(
&mut self,
_: Tag,
_: Constraints,
) -> Result<Option<D>, Self::Error>
where
D: crate::Decode,
{
self.decode_optional()
}
fn decode_extension_addition_group<
const RC: usize,
const EC: usize,
D: crate::Decode + Constructed<RC, EC>,
>(
&mut self,
) -> Result<Option<D>, Self::Error> {
self.decode_optional()
}
fn codec(&self) -> crate::Codec {
crate::Codec::Jer
}
}
impl Decoder {
fn any_from_value(value: Value) -> Result<Any, <Self as crate::de::Decoder>::Error> {
Ok(Any::new(alloc::format!("{value}").as_bytes().to_vec()))
}
fn boolean_from_value(value: Value) -> Result<bool, DecodeError> {
Ok(value
.as_bool()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "boolean",
found: alloc::format!("{value}"),
})?)
}
fn enumerated_from_value<E: Enumerated>(value: Value) -> Result<E, DecodeError> {
let identifier = value
.as_str()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "enumerated item as string",
found: alloc::format!("{value}"),
})?;
Ok(E::from_identifier(identifier).ok_or_else(|| {
JerDecodeErrorKind::InvalidEnumDiscriminant {
discriminant: alloc::string::String::from(identifier),
}
})?)
}
fn integer_from_value<I: crate::types::IntegerType>(value: Value) -> Result<I, DecodeError> {
value
.as_i64()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "number (supported range -2^63..2^63)",
found: alloc::format!("{value}"),
})?
.try_into()
.map_err(|_| DecodeError::integer_overflow(I::WIDTH, crate::Codec::Jer))
}
fn real_from_value<R: crate::types::RealType>(value: Value) -> Result<R, DecodeError> {
if let Some(as_f64) = value.as_f64() {
return R::try_from_float(as_f64).ok_or_else(|| {
JerDecodeErrorKind::TypeMismatch {
needed: "number (double precision floating point)",
found: alloc::format!("{value}"),
}
.into()
});
}
value
.as_str()
.and_then(|s| match s {
"-0" => R::try_from_float(-0.0),
"INF" => R::try_from_float(f64::INFINITY),
"-INF" => R::try_from_float(f64::NEG_INFINITY),
"NAN" => R::try_from_float(f64::NAN),
_ => None,
})
.ok_or_else(|| {
JerDecodeErrorKind::TypeMismatch {
needed: "number (double precision floating point)",
found: alloc::format!("{value}"),
}
.into()
})
}
fn null_from_value(value: Value) -> Result<(), DecodeError> {
Ok(value
.is_null()
.then_some(())
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "null",
found: alloc::format!("{value}"),
})?)
}
fn object_identifier_from_value(value: Value) -> Result<ObjectIdentifier, DecodeError> {
#[allow(clippy::unnecessary_lazy_evaluations)]
Ok(value
.as_str()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "number array",
found: alloc::format!("{value}"),
})?
.split('.')
.map(|arc| {
arc.parse::<u32>()
.map_err(|_| JerDecodeErrorKind::TypeMismatch {
needed: "OID arc number",
found: arc.into(),
})
})
.collect::<Result<alloc::vec::Vec<u32>, _>>()
.ok()
.and_then(|arcs| Oid::new(&arcs).map(ObjectIdentifier::from))
.ok_or_else(|| JerDecodeErrorKind::InvalidOIDString { value })?)
}
fn sequence_of_from_value<D: Decode>(
&mut self,
value: Value,
) -> Result<SequenceOf<D>, DecodeError> {
value
.as_array()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "array",
found: alloc::format!("{value}"),
})?
.clone()
.into_iter()
.map(|v| {
self.stack.push(Some(v));
D::decode(self)
})
.collect()
}
fn set_of_from_value<D: Decode + Eq + core::hash::Hash>(
&mut self,
value: Value,
) -> Result<SetOf<D>, DecodeError> {
value
.as_array()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "array",
found: alloc::format!("{value}"),
})?
.clone()
.into_iter()
.try_fold(SetOf::new(), |mut acc, v| {
self.stack.push(Some(v));
acc.insert(D::decode(self)?);
Ok(acc)
})
}
fn string_from_value(value: Value) -> Result<alloc::string::String, DecodeError> {
Ok(value
.as_str()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "string",
found: alloc::format!("{value}"),
})
.map(|n| n.into())?)
}
fn choice_from_value<D>(&mut self, value: Value) -> Result<D, DecodeError>
where
D: DecodeChoice,
{
let tag = value
.as_object()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "object",
found: alloc::format!("{value}"),
})?
.iter()
.next()
.and_then(|(k, v)| {
D::IDENTIFIERS
.iter()
.enumerate()
.find(|id| id.1.eq_ignore_ascii_case(k))
.map(|(i, _)| (i, v))
})
.map_or(Tag::EOC, |(i, v)| {
match variants::Variants::from_slice(
&[D::VARIANTS, D::EXTENDED_VARIANTS.unwrap_or(&[])].concat(),
)
.get(i)
{
Some(t) => {
self.stack.push(Some(v.clone()));
*t
}
None => Tag::EOC,
}
});
D::from_tag(self, tag)
}
fn octet_string_from_value(value: Value) -> Result<alloc::vec::Vec<u8>, DecodeError> {
let octet_string = value
.as_str()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "hex string",
found: alloc::format!("{value}"),
})?;
bytes_from_hexstring(octet_string)
.ok_or(JerDecodeErrorKind::InvalidJerOctetString {}.into())
}
fn utc_time_from_value(value: Value) -> Result<chrono::DateTime<chrono::Utc>, DecodeError> {
crate::ber::de::Decoder::parse_any_utc_time_string(
value
.as_str()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "time string",
found: alloc::format!("{value}"),
})?
.into(),
)
}
fn general_time_from_value(
value: Value,
) -> Result<chrono::DateTime<chrono::FixedOffset>, DecodeError> {
crate::ber::de::Decoder::parse_any_generalized_time_string(
value
.as_str()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
needed: "time string",
found: alloc::format!("{value}"),
})?
.into(),
)
}
fn date_from_value(value: Value) -> Result<chrono::NaiveDate, DecodeError> {
crate::ber::de::Decoder::parse_date_string(value.as_str().ok_or_else(|| {
JerDecodeErrorKind::TypeMismatch {
needed: "date string",
found: alloc::format!("{value}"),
}
})?)
}
}
fn bytes_from_hexstring(hex_string: &str) -> Option<alloc::vec::Vec<u8>> {
if !hex_string.len().is_multiple_of(2) {
return None;
}
let mut bytes = alloc::vec::Vec::<u8>::with_capacity(hex_string.len() / 2);
for (i, c) in hex_string.char_indices() {
let n = nibble_from_hexdigit(c)?;
if i % 2 == 0 {
bytes.push(n << 4);
} else {
bytes[i / 2] |= n;
}
}
Some(bytes)
}
fn nibble_from_hexdigit(c: char) -> Option<u8> {
match c {
'0'..='9' => Some(c as u8 - b'0'),
'a'..='f' => Some(c as u8 - b'a' + 0xA),
'A'..='F' => Some(c as u8 - b'A' + 0xA),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bytes_from_hexstring() {
assert_eq!(bytes_from_hexstring(""), Some(vec![]));
assert_eq!(bytes_from_hexstring("00"), Some(vec![0]));
assert_eq!(bytes_from_hexstring("FF"), Some(vec![0xFF]));
assert_eq!(bytes_from_hexstring("0000"), Some(vec![0, 0]));
assert_eq!(bytes_from_hexstring("FFFF"), Some(vec![0xFF, 0xFF]));
assert_eq!(bytes_from_hexstring(" "), None);
assert_eq!(bytes_from_hexstring("!"), None);
assert_eq!(bytes_from_hexstring("0"), None);
assert_eq!(bytes_from_hexstring(" 0"), None);
assert_eq!(bytes_from_hexstring("0 "), None);
assert_eq!(bytes_from_hexstring("0!"), None);
assert_eq!(bytes_from_hexstring(" "), None);
assert_eq!(bytes_from_hexstring("00 "), None);
assert_eq!(bytes_from_hexstring(" 00"), None);
assert_eq!(bytes_from_hexstring("000"), None);
assert_eq!(bytes_from_hexstring("Œ"), None);
assert_eq!(bytes_from_hexstring("ŒŒ"), None);
assert_eq!(bytes_from_hexstring("ŒŒŒ"), None);
assert_eq!(bytes_from_hexstring("ABCDEFG"), None);
assert_eq!(bytes_from_hexstring(" ABCDEF"), None);
assert_eq!(bytes_from_hexstring("\u{0000}"), None);
assert_eq!(bytes_from_hexstring("\u{FFFF}"), None);
assert_eq!(bytes_from_hexstring("\u{0123}"), None);
assert_eq!(bytes_from_hexstring("\u{30}"), None);
assert_eq!(bytes_from_hexstring("\\u0030"), None);
assert_eq!(bytes_from_hexstring("\\u202E\\u0030\\u0030"), None);
assert_eq!(bytes_from_hexstring("⣐⡄"), None);
assert_eq!(bytes_from_hexstring("😎"), None);
assert_eq!(bytes_from_hexstring("🙈🙉🙊"), None);
}
#[test]
fn test_nibble_from_hexdigit() {
for c in '\u{0}'..'\u{1024}' {
match c {
'0' => assert_eq!(Some(0x00), nibble_from_hexdigit(c)),
'1' => assert_eq!(Some(0x01), nibble_from_hexdigit(c)),
'2' => assert_eq!(Some(0x02), nibble_from_hexdigit(c)),
'3' => assert_eq!(Some(0x03), nibble_from_hexdigit(c)),
'4' => assert_eq!(Some(0x04), nibble_from_hexdigit(c)),
'5' => assert_eq!(Some(0x05), nibble_from_hexdigit(c)),
'6' => assert_eq!(Some(0x06), nibble_from_hexdigit(c)),
'7' => assert_eq!(Some(0x07), nibble_from_hexdigit(c)),
'8' => assert_eq!(Some(0x08), nibble_from_hexdigit(c)),
'9' => assert_eq!(Some(0x09), nibble_from_hexdigit(c)),
'A' => assert_eq!(Some(0x0A), nibble_from_hexdigit(c)),
'B' => assert_eq!(Some(0x0B), nibble_from_hexdigit(c)),
'C' => assert_eq!(Some(0x0C), nibble_from_hexdigit(c)),
'D' => assert_eq!(Some(0x0D), nibble_from_hexdigit(c)),
'E' => assert_eq!(Some(0x0E), nibble_from_hexdigit(c)),
'F' => assert_eq!(Some(0x0F), nibble_from_hexdigit(c)),
'a' => assert_eq!(Some(0x0A), nibble_from_hexdigit(c)),
'b' => assert_eq!(Some(0x0B), nibble_from_hexdigit(c)),
'c' => assert_eq!(Some(0x0C), nibble_from_hexdigit(c)),
'd' => assert_eq!(Some(0x0D), nibble_from_hexdigit(c)),
'e' => assert_eq!(Some(0x0E), nibble_from_hexdigit(c)),
'f' => assert_eq!(Some(0x0F), nibble_from_hexdigit(c)),
_ => assert_eq!(None, nibble_from_hexdigit(c)),
}
}
}
}