1use core::fmt;
7
8use crate::err::{Error, Result};
9
10#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct Oid([u8; 20]);
13
14impl Oid {
15 pub const fn from_bytes(bytes: [u8; 20]) -> Self {
16 Self(bytes)
17 }
18
19 pub fn from_hex(hex: &[u8]) -> Result<Self> {
21 if hex.len() != 40 {
22 return Err(Error::Corrupt("oid hex length"));
23 }
24 let mut bytes = [0u8; 20];
25 for (i, chunk) in hex.chunks_exact(2).enumerate() {
26 bytes[i] = (hex_val(chunk[0])? << 4) | hex_val(chunk[1])?;
27 }
28 Ok(Self(bytes))
29 }
30
31 pub const fn as_bytes(&self) -> &[u8; 20] {
32 &self.0
33 }
34}
35
36fn hex_val(c: u8) -> Result<u8> {
37 match c {
38 b'0'..=b'9' => Ok(c - b'0'),
39 b'a'..=b'f' => Ok(c - b'a' + 10),
40 b'A'..=b'F' => Ok(c - b'A' + 10),
41 _ => Err(Error::Corrupt("oid hex digit")),
42 }
43}
44
45fn fmt_hex(oid: &Oid, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 for b in oid.0 {
47 write!(f, "{b:02x}")?;
48 }
49 Ok(())
50}
51
52impl fmt::Display for Oid {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 fmt_hex(self, f)
55 }
56}
57
58impl fmt::Debug for Oid {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 fmt_hex(self, f)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn hex_roundtrip() {
70 let hex = "a9993e364706816aba3e25717850c26c9cd0d89d";
71 let oid = Oid::from_hex(hex.as_bytes()).unwrap();
72 assert_eq!(format!("{oid}"), hex);
73 }
74
75 #[test]
76 fn rejects_bad_hex() {
77 assert!(Oid::from_hex(b"xyz").is_err());
78 assert!(Oid::from_hex(&[b'g'; 40]).is_err());
79 }
80}