use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CertSerial([u8; 32]);
impl From<[u8; 32]> for CertSerial {
fn from(value: [u8; 32]) -> Self {
Self(value)
}
}
impl CertSerial {
pub fn value(&self) -> [u8; 32] {
self.0
}
fn ascii_value(&self) -> Result<String, ()> {
let value: String = self
.value()
.into_iter()
.skip_while(|&byte| byte == 0 || byte == b'0')
.map(|byte| byte as char)
.collect();
if !value.is_empty() && value.chars().all(|c| c.is_ascii_hexdigit()) {
Ok(value)
} else {
Err(())
}
}
pub fn describe_value(&self) -> String {
match self.ascii_value() {
Ok(value) => format!("0x{}", value.to_lowercase()),
Err(_) => "INVÁLIDO".to_string(),
}
}
pub fn is_valid(&self) -> bool {
self.ascii_value().is_ok()
}
pub fn to_bytes(&self) -> [u8; 32] {
self.value()
}
}
impl fmt::Display for CertSerial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value: String = self
.value()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect();
let desc = self.describe_value();
write!(f, "0x{value} [{desc}]")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value_binary_zero() {
let value = [0; 32];
let sut: CertSerial = value.into();
assert_eq!(sut, CertSerial(value));
assert_eq!(sut, value.into());
assert_eq!(sut.value(), value);
assert_eq!(sut.describe_value(), "INVÁLIDO");
assert!(!sut.is_valid());
assert_eq!(sut.to_bytes(), value);
assert_eq!(
sut.to_string(),
"0x0000000000000000000000000000000000000000000000000000000000000000 [INVÁLIDO]"
);
}
#[test]
fn value_ascii_zero() {
let value = [b'0'; 32];
let sut: CertSerial = value.into();
assert_eq!(sut, CertSerial(value));
assert_eq!(sut, value.into());
assert_eq!(sut.value(), value);
assert_eq!(sut.describe_value(), "INVÁLIDO");
assert!(!sut.is_valid());
assert_eq!(sut.to_bytes(), value);
assert_eq!(
sut.to_string(),
"0x3030303030303030303030303030303030303030303030303030303030303030 [INVÁLIDO]"
);
}
#[test]
fn value_some_bytes() {
let mut value = [0; 32];
for i in 26..32 {
value[i as usize] = b'1' + i - 26;
}
let value = value;
let sut: CertSerial = value.into();
assert_eq!(sut, CertSerial(value));
assert_eq!(sut, value.into());
assert_eq!(sut.value(), value);
assert_eq!(sut.describe_value(), "0x123456");
assert!(sut.is_valid());
assert_eq!(sut.to_bytes(), value);
assert_eq!(
sut.to_string(),
"0x0000000000000000000000000000000000000000000000000000313233343536 [0x123456]"
);
}
#[test]
fn value_invalid_bytes() {
let value = [0xff; 32];
let sut: CertSerial = value.into();
assert_eq!(sut, CertSerial(value));
assert_eq!(sut, value.into());
assert_eq!(sut.value(), value);
assert_eq!(sut.describe_value(), "INVÁLIDO");
assert!(!sut.is_valid());
assert_eq!(sut.to_bytes(), value);
assert_eq!(
sut.to_string(),
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff [INVÁLIDO]"
);
}
#[test]
fn value_full() {
let value: [u8; 32] = (0..16)
.flat_map(|byte| {
format!("{byte:02X}")
.chars()
.map(|c| c as u8)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
let sut: CertSerial = value.into();
assert_eq!(sut, CertSerial(value));
assert_eq!(sut, value.into());
assert_eq!(sut.value(), value);
assert_eq!(sut.describe_value(), "0x102030405060708090a0b0c0d0e0f");
assert!(sut.is_valid());
assert_eq!(sut.to_bytes(), value);
assert_eq!(
sut.to_string(),
"0x3030303130323033303430353036303730383039304130423043304430453046 [0x102030405060708090a0b0c0d0e0f]"
);
}
}