use std::{fmt::Display, ops::RangeInclusive};
use bytes::Bytes;
use x509_parser::prelude::X509Certificate;
use crate::{cached_string_repr::CachedStringRepr, hex::colon_string};
const VALID_LENGTHS: RangeInclusive<usize> = 1..=20;
#[derive(Debug, Clone)] pub struct SerialNumber {
bytes: Bytes,
rendered: CachedStringRepr,
}
impl SerialNumber {
fn new(value: impl Into<Bytes>) -> Self {
let bytes = value.into();
assert!(
VALID_LENGTHS.contains(&bytes.len()),
"serial number of length {} is invalid",
bytes.len()
);
Self {
bytes,
rendered: Default::default(),
}
}
pub fn as_hex_str(&self) -> &str {
self.rendered.get_or_init(|| colon_string(&self.bytes))
}
pub fn as_bytes(&self) -> &[u8] {
self.bytes.as_ref()
}
}
impl Display for SerialNumber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_hex_str())
}
}
impl<'a> From<&'a SerialNumber> for rcgen::SerialNumber {
fn from(value: &'a SerialNumber) -> Self {
Self::from_slice(value.as_bytes())
}
}
impl<'a> From<&'a X509Certificate<'a>> for SerialNumber {
fn from(cert: &'a X509Certificate<'a>) -> Self {
Self::new(cert.raw_serial().to_vec())
}
}
impl valuable::Valuable for SerialNumber {
fn as_value(&self) -> valuable::Value<'_> {
valuable::Value::String(self.as_hex_str())
}
fn visit(&self, visit: &mut dyn valuable::Visit) {
visit.visit_value(self.as_value());
}
}
#[cfg(test)]
mod tests {
use rc_x509_test_helpers::assert_valuable_repr;
use super::*;
use proptest::prelude::*;
use static_assertions::assert_not_impl_any;
assert_not_impl_any!(SerialNumber: PartialEq, Eq);
#[test]
fn test_fixture() {
let hex_str = "cc:cb:0f:63:f1:63:5e:f1:0e:26:e8:82:f7:7a:6e:f9";
let raw = hex::decode(hex_str.replace(':', "")).expect("valid hex");
let sn = SerialNumber::new(raw.clone());
assert_eq!(sn.to_string(), hex_str);
assert_eq!(sn.as_hex_str(), hex_str);
assert_eq!(sn.as_bytes(), &raw);
}
#[test]
#[should_panic(expected = "serial number of length 0 is invalid")]
fn test_empty() {
let _sn = SerialNumber::new([].as_slice());
}
#[test]
#[should_panic(expected = "serial number of length 21 is invalid")]
fn test_too_long() {
let _sn = SerialNumber::new(
[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
]
.as_slice(),
);
}
#[test]
fn test_valuable_repr() {
let sn = SerialNumber::new(
[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
]
.as_slice(),
);
assert_valuable_repr(
&sn,
"01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14\n",
);
}
proptest! {
#[test]
fn prop_render_serial_number(
value in prop::collection::vec(any::<u8>(), 1..20), ) {
let serial = SerialNumber::new(value.clone());
let rendered = serial.as_hex_str();
let rcgen_serial = rcgen::SerialNumber::from(&serial);
let rcgen_rendered = rcgen_serial.to_string();
assert_eq!(rendered, rcgen_rendered);
assert_eq!(serial.to_string(), rendered);
assert_eq!(serial.as_bytes(), &value);
}
}
}