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