1#![warn(clippy::pedantic)]
2#![doc = include_str!("../README.md")]
3mod error;
4mod parser;
5pub mod schema;
6
7use crate::parser::Parser;
8use error::Error;
9use parser::Event;
10use schema::{Class, Device, DeviceInfo, SubClass, SubDeviceId, Vendor};
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13use std::{
14 collections::HashMap,
15 fs::File,
16 io::{BufReader, Read},
17 path::{Path, PathBuf},
18};
19
20const DB_PATHS: &[&str] = &[
21 "/usr/share/hwdata/pci.ids",
22 "/usr/share/misc/pci.ids",
23 "@hwdata@/share/hwdata/pci.ids",
24];
25#[cfg(feature = "online")]
26const URL: &str = "https://pci-ids.ucw.cz/v2.2/pci.ids";
27
28#[derive(Debug)]
29pub enum VendorDataError {
30 MissingIdsFile,
31}
32
33#[derive(Debug)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35pub struct Database {
36 pub vendors: HashMap<u16, Vendor>,
37 pub classes: HashMap<u8, Class>,
38}
39
40impl Database {
41 pub fn read() -> Result<Self, Error> {
46 let file = Self::open_file()?;
47 Self::parse_db(file)
48 }
49
50 pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
55 let file = File::open(path)?;
56 Self::parse_db(file)
57 }
58
59 #[cfg(feature = "online")]
64 pub fn get_online() -> Result<Self, Error> {
65 let response = ureq::get(URL).call()?;
66
67 Self::parse_db(response.into_body().into_reader())
68 }
69
70 #[allow(clippy::too_many_lines)] pub fn parse_db<R: Read>(reader: R) -> Result<Self, Error> {
76 let reader = BufReader::new(reader);
77 let mut parser = Parser::new(reader);
78
79 let mut current_vendor: Option<(u16, Vendor)> = None;
80 let mut current_device: Option<(u16, Device)> = None;
81
82 let mut current_class: Option<(u8, Class)> = None;
83 let mut current_subclass: Option<(u8, SubClass)> = None;
84
85 let mut vendors: HashMap<u16, Vendor> = HashMap::with_capacity(2500);
86 let mut classes: HashMap<u8, Class> = HashMap::with_capacity(200);
87
88 while let Some(event) = parser.next_event()? {
89 match event {
90 Event::Vendor { id, name } => {
91 if let Some((device_id, device)) = current_device.take() {
93 let (_, vendor) = current_vendor
94 .as_mut()
95 .ok_or_else(Error::no_current_vendor)?;
96 vendor.devices.insert(device_id, device);
97 }
98 if let Some((vendor_id, vendor)) = current_vendor.take() {
99 vendors.insert(vendor_id, vendor);
100 }
101
102 let vendor = Vendor {
103 name: name.to_owned(),
104 devices: HashMap::new(),
105 };
106 current_vendor = Some((
107 u16::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
108 vendor,
109 ));
110 }
111 Event::Device { id, name } => {
112 if let Some((device_id, device)) = current_device.take() {
114 let (_, current_vendor) = current_vendor
115 .as_mut()
116 .ok_or_else(Error::no_current_vendor)?;
117
118 current_vendor.devices.insert(device_id, device);
119 }
120
121 let device = Device {
122 name: name.to_owned(),
123 subdevices: HashMap::new(),
124 };
125
126 current_device = Some((
127 u16::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
128 device,
129 ));
130 }
131 Event::Subdevice {
132 subvendor,
133 subdevice,
134 subsystem_name,
135 } => {
136 let (_, current_device) = current_device
137 .as_mut()
138 .ok_or_else(Error::no_current_device)?;
139
140 let subdevice_id = SubDeviceId {
141 subvendor: u16::from_str_radix(subvendor, 16)
142 .map_err(|_| Error::invalid_int(subvendor))?,
143 subdevice: u16::from_str_radix(subdevice, 16)
144 .map_err(|_| Error::invalid_int(subdevice))?,
145 };
146 current_device
147 .subdevices
148 .insert(subdevice_id, subsystem_name.to_owned());
149 }
150 Event::Class { id, name } => {
151 if let Some((subclass_id, subclass)) = current_subclass.take() {
152 let (_, class) =
153 current_class.as_mut().ok_or_else(Error::no_current_class)?;
154
155 class.subclasses.insert(subclass_id, subclass);
156 }
157 if let Some((class_id, class)) = current_class.take() {
158 classes.insert(class_id, class);
159 }
160
161 let class = Class {
162 name: name.to_owned(),
163 subclasses: HashMap::new(),
164 };
165 current_class = Some((
166 u8::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
167 class,
168 ));
169 }
170 Event::SubClass { id, name } => {
171 if let Some((subclass_id, subclass)) = current_subclass {
172 let (_, class) =
173 current_class.as_mut().ok_or_else(Error::no_current_class)?;
174
175 class.subclasses.insert(subclass_id, subclass);
176 }
177
178 let subclass = SubClass {
179 name: name.to_owned(),
180 prog_ifs: HashMap::new(),
181 };
182 current_subclass = Some((
183 u8::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
184 subclass,
185 ));
186 }
187 Event::ProgIf { id, name } => {
188 let (_, subclass) = current_subclass
189 .as_mut()
190 .ok_or_else(Error::no_current_subclass)?;
191
192 subclass.prog_ifs.insert(
193 u8::from_str_radix(id, 16).map_err(|_| Error::invalid_int(id))?,
194 name.to_owned(),
195 );
196 }
197 }
198 }
199 if let Some((device_id, device)) = current_device.take() {
201 let (_, vendor) = current_vendor
202 .as_mut()
203 .ok_or_else(Error::no_current_vendor)?;
204 vendor.devices.insert(device_id, device);
205 }
206 if let Some((vendor_id, vendor)) = current_vendor.take() {
207 vendors.insert(vendor_id, vendor);
208 }
209
210 if let Some((subclass_id, subclass)) = current_subclass.take() {
211 let (_, class) = current_class.as_mut().ok_or_else(Error::no_current_class)?;
212
213 class.subclasses.insert(subclass_id, subclass);
214 }
215 if let Some((class_id, class)) = current_class.take() {
216 classes.insert(class_id, class);
217 }
218
219 vendors.shrink_to_fit();
220 classes.shrink_to_fit();
221
222 Ok(Self { vendors, classes })
223 }
224
225 fn open_file() -> Result<File, Error> {
226 if let Some(path) = DB_PATHS
227 .iter()
228 .find(|path| Path::exists(&PathBuf::from(path)))
229 {
230 Ok(File::open(path)?)
231 } else {
232 Err(Error::FileNotFound)
233 }
234 }
235
236 #[must_use]
237 pub fn get_device_info(
238 &self,
239 vendor_id: u16,
240 model_id: u16,
241 subsys_vendor_id: u16,
242 subsys_model_id: u16,
243 ) -> DeviceInfo<'_> {
244 let mut vendor_name = None;
245 let mut device_name = None;
246 let mut subvendor_name = None;
247 let mut subdevice_name = None;
248
249 if let Some(vendor) = self.vendors.get(&vendor_id) {
250 vendor_name = Some(vendor.name.as_str());
251
252 if let Some(device) = vendor.devices.get(&model_id) {
253 device_name = Some(device.name.as_str());
254
255 if let Some(subvendor) = self.vendors.get(&subsys_vendor_id) {
256 subvendor_name = Some(subvendor.name.as_str());
257 }
258
259 let subdevice_id = SubDeviceId {
260 subvendor: subsys_vendor_id,
261 subdevice: subsys_model_id,
262 };
263
264 subdevice_name = device.subdevices.get(&subdevice_id).map(String::as_str);
265 }
266 }
267
268 DeviceInfo {
269 vendor_name,
270 device_name,
271 subvendor_name,
272 subdevice_name,
273 }
274 }
275}
276
277pub fn find_vendor_name(vendor_id: u16) -> Result<Option<String>, Error> {
283 let reader = Database::open_file()?;
284 find_vendor_name_with_reader(reader, vendor_id)
285}
286
287pub fn find_vendor_name_with_reader<R: Read>(
293 reader: R,
294 vendor_id: u16,
295) -> Result<Option<String>, Error> {
296 let vendor_id = format!("{vendor_id:x?}");
297
298 let mut parser = Parser::new(BufReader::new(reader));
299
300 while let Some(event) = parser.next_event()? {
301 if let Event::Vendor { id, name } = event {
302 if id == vendor_id {
303 return Ok(Some(name.to_owned()));
304 }
305 }
306 }
307
308 Ok(None)
309}
310
311pub fn find_device_name(vendor_id: u16, device_id: u16) -> Result<Option<String>, Error> {
317 let reader = Database::open_file()?;
318 find_device_name_with_reader(reader, vendor_id, device_id)
319}
320
321pub fn find_device_name_with_reader<R: Read>(
327 reader: R,
328 vendor_id: u16,
329 device_id: u16,
330) -> Result<Option<String>, Error> {
331 let vendor_id = format!("{vendor_id:x?}");
332 let device_id = format!("{device_id:x?}");
333
334 let mut parser = Parser::new(BufReader::new(reader));
335
336 while let Some(event) = parser.next_event()? {
337 if let Event::Vendor { id, .. } = event {
338 if id == vendor_id {
339 while let Some(event) = parser.next_event()? {
340 match event {
341 Event::Device { id, name } => {
342 if id == device_id {
343 return Ok(Some(name.to_owned()));
344 }
345 }
346 Event::Vendor { .. } => break,
347 _ => (),
348 }
349 }
350
351 break;
352 }
353 }
354 }
355
356 Ok(None)
357}
358
359pub fn find_subdevice_name(
365 parent_vendor_id: u16,
366 parent_device_id: u16,
367 subvendor_id: u16,
368 subdevice_id: u16,
369) -> Result<Option<String>, Error> {
370 let reader = Database::open_file()?;
371 find_subdevice_name_with_reader(
372 reader,
373 parent_vendor_id,
374 parent_device_id,
375 subvendor_id,
376 subdevice_id,
377 )
378}
379
380pub fn find_subdevice_name_with_reader<R: Read>(
386 reader: R,
387 parent_vendor_id: u16,
388 parent_device_id: u16,
389 subvendor_id: u16,
390 subdevice_id: u16,
391) -> Result<Option<String>, Error> {
392 let parent_vendor_id = format!("{parent_vendor_id:x?}");
393 let parent_device_id = format!("{parent_device_id:x?}");
394 let subvendor_id = format!("{subvendor_id:x?}");
395 let subdevice_id = format!("{subdevice_id:x?}");
396
397 let mut parser = Parser::new(BufReader::new(reader));
398
399 while let Some(event) = parser.next_event()? {
400 if let Event::Vendor { id, .. } = event {
401 if id == parent_vendor_id {
402 while let Some(event) = parser.next_event()? {
403 match event {
404 Event::Device { id, .. } => {
405 if id == parent_device_id {
406 while let Some(event) = parser.next_event()? {
407 match event {
408 Event::Subdevice {
409 subvendor,
410 subdevice,
411 subsystem_name,
412 } => {
413 if subvendor == subvendor_id
414 && subdevice == subdevice_id
415 {
416 return Ok(Some(subsystem_name.to_owned()));
417 }
418 }
419 _ => break,
420 }
421 }
422
423 break;
424 }
425 }
426 Event::Vendor { .. } => break,
427 _ => (),
428 }
429 }
430
431 break;
432 }
433 }
434 }
435
436 Ok(None)
437}