pciid_parser/
lib.rs

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    /// Attempt to read the database from a list of known file paths
42    ///
43    /// # Errors
44    /// Returns an error when either no file could be found or the parsing fails.
45    pub fn read() -> Result<Self, Error> {
46        let file = Self::open_file()?;
47        Self::parse_db(file)
48    }
49
50    /// Read the database from a given path
51    ///
52    /// # Errors
53    /// Returns an error when the file can't be read or when parsing fails
54    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    /// Fetch a database from an online source
60    ///
61    /// # Errors
62    /// Returns an error when the database either can't be fetched or parsed
63    #[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    /// Parse a database from the given reader
71    ///
72    /// # Errors
73    /// Returns an error whenever there's a parsing error
74    #[allow(clippy::too_many_lines)] // todo
75    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                    // The vendor section is complete so it needs to be pushed to the main list
92                    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                    // Device section is over, write to vendor
113                    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        // Finish writing the last vendor and class
200        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
277/// Try to find the name of a vendor by its id.
278/// This will search the database from one of the known file paths for the name.
279///
280/// # Errors
281/// Returns an error when the file can't be read or when parsing fails
282pub 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
287/// Try to find the name of a vendor by its id.
288/// This will search the database from the given reader for the name.
289///
290/// # Errors
291/// Returns an error when parsing fails
292pub 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
311/// Try to find the name of a device by its vendor and device id.
312/// This will search the database from one of the known file paths for the name.
313///
314/// # Errors
315/// Returns an error when the file can't be read or when parsing fails
316pub 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
321/// Try to find the name of a device by its vendor and device id.
322/// This will search the database from the given reader for the name.
323///
324/// # Errors
325/// Returns an error when parsing fails
326pub 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
359/// Try to find the name of a subdevice by its ids.
360/// This will search the database from the given reader for the name.
361///
362/// # Errors
363/// Returns an error when parsing fails
364pub 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
380/// Try to find the name of a subdevice by its ids.
381/// This will search the database from the given reader for the name.
382///
383/// # Errors
384/// Returns an error when parsing fails
385pub 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}