Skip to main content

tig_core/
oid.rs

1//! object id (SHA-1)。
2//!
3//! 内部表現は 20 byte 固定とするが、型として分離してあるため、将来の SHA-256
4//! 対応は本モジュールの拡張で吸収する。
5
6use core::fmt;
7
8use crate::err::{Error, Result};
9
10/// SHA-1 の object id。
11#[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    /// 40 桁の 16 進表記から変換する。
20    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}