Skip to main content

macaddr_ouidb/
oui.rs

1//! # Organizationally Unique Identifiers (OUI) database
2//!
3//! ## 查询
4//! 1. 取 mac 前 3 个字节,二分查找 `OuiDb::oui_24` `prefix`, 找不到返回 None,获取 `loc` 对应值
5//! 2. 如 loc bit-31-30 = 0,则计算 offset/length,返回 `OuiDb::names[offset..offset+length]`
6//! 2. 如 loc bit-31-30 = 1,取 mac [第 4 个字节 & 0xF0], 查找 `OuiDb::oui_28`
7//! 2. 如 loc bit-31-30 = 2,取 mac [第 4 个字节,第 5 个字节 & 0xF0], 查找 `OuiDb::oui_36`
8//!
9use crate::MacAddress;
10
11const OUI_SUBTABLE: &str = "Ieee Registration Authority";
12
13const OUI_VIRTUAL: [&'static str; 7] = [
14    "VMware",
15    "QEMU virtual NIC",
16    "Bochs virtual NIC",
17    "PearPC virtual NIC",
18    "Cooperative Linux virtual NIC",
19    "Oracle VirtualBox virtual NIC",
20    "OpenStack",
21];
22
23/// # Organizationally Unique Identifiers (OUI) database
24///
25
26pub struct OuiDb {
27    pub(crate) names: &'static str,
28
29    /// MA-L(24 bit prefix) 24 bit ≈1677 万 传统大厂/运营商
30    pub(crate) oui_24: &'static OuiTable<3>,
31
32    /// MA-M(28 bit prefix) 20 bit ≈104 万 中型设备商/模组厂
33    pub(crate) oui_28: &'static [OuiSubtable<u8>],
34
35    /// MA-S(36 bit prefix) 12 bit =4096 IoT/小微/专用硬件
36    pub(crate) oui_36: &'static [OuiSubtable<u16>],
37}
38
39/// # Locate org name or subtable offset
40///
41/// ## Type 00 - 指向 names 字符串
42/// ```text
43/// bit:  31 30 | 29 28 ... 11 10 09 08 | 07 06 ... 01 00
44///       ─────   ─────────────────────   ───────────────
45///       type       offset (22 bits)        length
46/// ```
47///
48/// ## Type 01/02/03 - 指向子表偏移量
49/// ```text
50/// bit:  31 30 | 29 28 ... 17 16 | 15 14 ... 01 00
51///       ─────   ───────────────   ───────────────
52///       type       reserved        subtable index
53/// ```
54///
55/// * bit 31-30: type
56///   - 00 指向 OuiDb::names 中的偏移量和长度
57///       - bit-29-08: offset (22 bits)
58///       - bit-07-00: length (8 bits)
59///   - 01 指向 OuiDb::oui_28 偏移量
60///       - bit-15-00: subtable index (16 bits)
61///   - 02 指向 OuiDb::oui_36 偏移量
62///       - bit-15-00: subtable index (16 bits)
63///
64type OuiLoc = u32;
65
66/// 列存储(节约内存,提升缓存利用率)
67pub(crate) struct OuiTable<const N: usize> {
68    pub(crate) prefix: &'static [[u8; N]],
69    pub(crate) loc: &'static [OuiLoc],
70}
71
72/// 列存储(节约内存,提升缓存利用率)
73pub(crate) struct OuiSubtable<T: 'static> {
74    pub(crate) prefix: &'static [T],
75    pub(crate) loc: &'static [OuiLoc],
76}
77
78impl OuiDb {
79    /// Checks if the given OUI name corresponds to a virtual NIC.
80    ///
81    /// # Arguments
82    ///
83    /// * `name` - The OUI name to check.
84    ///
85    /// # Returns
86    ///
87    /// Returns `true` if the name is a virtual NIC (e.g., QEMU, VirtualBox), otherwise `false`.
88    pub fn is_virtual_nic(name: &str) -> bool {
89        OUI_VIRTUAL.contains(&name)
90    }
91
92    /// Returns the name identifier of the subtable (`oui_28`, `oui_32`, `oui_36`) in the database.
93    ///
94    /// # Returns
95    ///
96    /// Returns the subtable name string `"Ieee Registration Authority"`.
97    pub fn oui_subtable_name() -> &'static str {
98        OUI_SUBTABLE
99    }
100
101    /// Looks up the organization name (OUI vendor information) for a given MAC address.
102    ///
103    /// # Arguments
104    ///
105    /// * `mac` - The MAC address as a [`MacAddress`] type.
106    ///
107    /// # Returns
108    ///
109    /// Returns `Some(&'static str)` if the organization name is found, otherwise `None`.
110    ///
111    /// # Example
112    ///
113    /// ```
114    /// use macaddr_ouidb::*;
115    /// let mac = MacAddress::new([0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E]);
116    /// let org = OUI_DB.lookup_mac(mac);
117    /// ```
118    pub fn lookup_mac(&self, mac: MacAddress) -> Option<&'static str> {
119        self.lookup(mac.octets())
120    }
121
122    /// Looks up the organization name (OUI vendor information) for a given MAC address byte array.
123    ///
124    /// Supports 24-bit, 28-bit, and 36-bit OUI queries.
125    ///
126    /// # Arguments
127    ///
128    /// * `mac` - A 6-byte MAC address byte array.
129    ///
130    /// # Returns
131    ///
132    /// Returns `Some(&'static str)` if the organization name is found. Returns `None` if the MAC
133    /// address length is not 6 bytes or no match is found.
134    ///
135    // # Query Logic
136    //
137    // 1. Extract the first 3 bytes and perform a binary search in `oui_24.prefix`
138    // 2. Based on `loc` bits 31-30, determine the type:
139    //    - `00`: Retrieve string from `names` using offset and length
140    //    - `01`: Look up in `oui_28` subtable using `mac[3] & 0xF0`
141    //    - `02`: Look up in `oui_36` subtable using `mac[3]` and `mac[4] & 0xF0` combined
142    pub fn lookup(&self, mac: [u8; 6]) -> Option<&'static str> {
143        // 1. 提取前 3 字节作为 oui_24 的查找键
144        let key3 = [mac[0], mac[1], mac[2]];
145
146        // 2. 二分查找 oui_24
147        let idx = match self.oui_24.prefix.binary_search(&key3) {
148            Ok(idx) => idx,
149            Err(_) => return None,
150        };
151
152        // 3. 获取 loc 值
153        let loc = self.oui_24.loc[idx];
154
155        // 4. 根据 bit-31-30 判断类型
156        match (loc >> 30) & 0x03 {
157            0 => {
158                // names 偏移量和长度
159                // bit-29-08: offset (22 bits), bit-07-00: length (8 bits)
160                let offset = ((loc >> 8) & 0x3FFFFF) as usize;
161                let length = (loc & 0xFF) as usize;
162                self.names.get(offset..offset + length)
163            }
164            1 => {
165                // oui_28: 取 mac[3] & 0xF0
166                let sub_idx = (loc & 0xFFFF) as usize;
167                let key4 = mac[3] & 0xF0;
168                self.lookup_subtable(&self.oui_28[sub_idx], key4)
169            }
170            2 => {
171                // oui_36: 取 mac[3] 和 mac[4] & 0xF0
172                // 组合为 16 位:高 8 位是 mac[3], 低 8 位是 mac[4] & 0xF0
173                let sub_idx = (loc & 0xFFFF) as usize;
174                let key5 = ((mac[3] as u16) << 8) | ((mac[4] & 0xF0) as u16);
175                self.lookup_subtable(&self.oui_36[sub_idx], key5)
176            }
177            _ => None,
178        }
179    }
180
181    fn lookup_subtable<T: Ord + Copy>(
182        &self,
183        subtable: &OuiSubtable<T>,
184        key: T,
185    ) -> Option<&'static str> {
186        let idx = match subtable.prefix.binary_search(&key) {
187            Ok(idx) => idx,
188            Err(_) => return None,
189        };
190        let loc = subtable.loc[idx];
191
192        // subtable 的 loc 只可能是 type 0 (指向 names)
193        // bit-29-08: offset (22 bits), bit-07-00: length (8 bits)
194        let offset = ((loc >> 8) & 0x3FFFFF) as usize;
195        let length = (loc & 0xFF) as usize;
196        self.names.get(offset..offset + length)
197    }
198}