1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! OID Names Database
//!
//! The contents of this database are generated from the official IANA
//! [Object Identifier Descriptors] Registry CSV file and from [RFC 5280].
//! If we are missing values you care about, please contribute a patch to
//! `oiddbgen` (a subcrate in the source code) to generate the values from
//! the relevant standard.
//!
//! [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280
//! [Object Identifier Descriptors]: https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xhtml#ldap-parameters-3

#![allow(clippy::integer_arithmetic, missing_docs)]

mod gen;

pub use gen::*;

use crate::{Error, ObjectIdentifier};

/// A const implementation of byte equals.
const fn eq(lhs: &[u8], rhs: &[u8]) -> bool {
    if lhs.len() != rhs.len() {
        return false;
    }

    let mut i = 0usize;
    while i < lhs.len() {
        if lhs[i] != rhs[i] {
            return false;
        }

        i += 1;
    }

    true
}

/// A const implementation of case-insensitive ASCII equals.
const fn eq_case(lhs: &[u8], rhs: &[u8]) -> bool {
    if lhs.len() != rhs.len() {
        return false;
    }

    let mut i = 0usize;
    while i < lhs.len() {
        if !lhs[i].eq_ignore_ascii_case(&rhs[i]) {
            return false;
        }

        i += 1;
    }

    true
}

/// A query interface for OIDs/Names.
#[derive(Copy, Clone)]
pub struct Database<'a>(&'a [(&'a ObjectIdentifier, &'a str)]);

impl<'a> Database<'a> {
    /// Looks up a name for an OID.
    ///
    /// Errors if the input is not a valid OID.
    /// Returns the input if no name is found.
    pub fn resolve<'b>(&self, oid: &'b str) -> Result<&'b str, Error>
    where
        'a: 'b,
    {
        Ok(self.by_oid(&oid.parse()?).unwrap_or(oid))
    }

    /// Finds a named oid by its associated OID.
    pub const fn by_oid(&self, oid: &ObjectIdentifier) -> Option<&'a str> {
        let mut i = 0;

        while i < self.0.len() {
            let lhs = self.0[i].0;
            if lhs.length == oid.length && eq(&lhs.bytes, &oid.bytes) {
                return Some(self.0[i].1);
            }

            i += 1;
        }

        None
    }

    /// Finds a named oid by its associated name.
    pub const fn by_name(&self, name: &str) -> Option<&'a ObjectIdentifier> {
        let mut i = 0;

        while i < self.0.len() {
            let lhs = self.0[i].1;
            if eq_case(lhs.as_bytes(), name.as_bytes()) {
                return Some(self.0[i].0);
            }

            i += 1;
        }

        None
    }

    /// Return the list of matched name for the OID.
    pub const fn find_names_for_oid(&self, oid: ObjectIdentifier) -> Names<'a> {
        Names {
            database: *self,
            oid,
            position: 0,
        }
    }
}

/// Iterator returning the multiple names that may be associated with an OID.
pub struct Names<'a> {
    database: Database<'a>,
    oid: ObjectIdentifier,
    position: usize,
}

impl<'a> Iterator for Names<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        let mut i = self.position;

        while i < self.database.0.len() {
            let lhs = self.database.0[i].0;

            if lhs.as_bytes().eq(self.oid.as_bytes()) {
                self.position = i + 1;
                return Some(self.database.0[i].1);
            }

            i += 1;
        }

        None
    }
}

#[cfg(test)]
mod tests {
    use crate::ObjectIdentifier;

    use super::rfc4519::CN;

    #[test]
    fn by_oid() {
        let cn = super::DB.by_oid(&CN).expect("cn not found");
        assert_eq!("cn", cn);

        let none = ObjectIdentifier::new_unwrap("0.1.2.3.4.5.6.7.8.9");
        assert_eq!(None, super::DB.by_oid(&none));
    }

    #[test]
    fn by_name() {
        let cn = super::DB.by_name("CN").expect("cn not found");
        assert_eq!(&CN, cn);

        assert_eq!(None, super::DB.by_name("purplePeopleEater"));
    }
}