dmidecode/
lib.rs

1//! # DMIDECODE
2//!
3//! This library reports information about system's hardware as described in system BIOS according
4//! to the SMBIOS/DMI standard. Each SMBIOS type refers to separate struct.
5//!
6//! SMBIOS specification defines the following data structures:
7//! - [BIOS Information](structures::bios "structures::bios") (Type 0)
8//! - [System Information](structures::system "structures::system") (Type 1)
9//! - [Baseboard (or Module) Information](structures::baseboard "structures::baseboard") (Type 2)
10//! - [System Enclosure or Chassis](structures::enclosure "structures::enclosure") (Type 3)
11//! - [Processor Information](structures::processor "structures::processor") (Type 4)
12//! - Memory Controller Information (Type 5, Obsolete)
13//! - Memory Module Information (Type 6, Obsolete)
14//! - [Cache Information](structures::cache "structures::cache") (Type 7)
15//! - [Port Connector Information](structures::port_connector "structures::port_connector") (Type 8)
16//! - [System Slots](structures::system_slots "structures::system_slots") (Type 9)
17//! - On Board Devices Information (Type 10, Obsolete)
18//! - [OEM Strings](structures::oem_strings "structures::oem_strings") (Type 11)
19//! - [System Configuration Options](structures::system_configuration_options "structures::system_configuration_options") (Type 12)
20//! - [BIOS Language Information](structures::bios_language "structures::bios_language") (Type 13)
21//! - [Group Associations](structures::group_associations "structures::group_associations") (Type 14)
22//! - [System Event Log](structures::system_event_log "structures::system_event_log") (Type 15)
23//! - [Physical Memory Array](structures::physical_memory_array "structures::physical_memory_array") (Type 16)
24//! - [Memory Device](structures::memory_device "structures::memory_device") (Type 17)
25//! - [32-Bit Memory Error Information](structures::memory_error_32 "structures::memory_error_32") (Type 18)
26//! - [Memory Array Mapped Address](structures::memory_array_mapped_address "structures::memory_array_mapped_address") (Type 19)
27//! - [Memory Device Mapped Address](structures::memory_device_mapped_address
28//! "structures::memory_device_mapped_address") (Type 20)
29//! - [Built-in Pointing Device](structures::built_in_pointing_device
30//! "structures::built_in_pointing_device") (Type 21)
31//! - [Portable Battery](structures::portable_battery "structures::portable_battery") (Type 22)
32//! - System Reset (Type 23)
33//! - Hardware Security (Type 24)
34//! - System Power Controls (Type 25)
35//! - Voltage Probe (Type 26)
36//! - Cooling Device (Type 27)
37//! - Temperature Probe (Type 28)
38//! - Electrical Current Probe (Type 29)
39//! - Out-of-Band Remote Access (Type 30)
40//! - Boot Integrity Services (BIS) Entry Point (Type 31)
41//! - System Boot Information (Type 32)
42//! - 64-Bit Memory Error Information (Type 33)
43//! - Management Device (Type 34)
44//! - Management Device Component (Type 35)
45//! - Management Device Threshold Data (Type 36)
46//! - Memory Channel (Type 37)
47//! - IPMI Device Information (Type 38)
48//! - System Power Supply (Type 39)
49//! - Additional Information (Type 40)
50//! - Onboard Devices Extended Information (Type 41)
51//! - Management Controller Host Interface (Type 42)
52//! - TPM Device (Type 43)
53//! - Processor Additional Information (Type 44)
54//! - Inactive (Type 126)
55//! - End-of-Table (Type 127)
56
57#![no_std]
58
59#[cfg(any(feature = "std", test))]
60#[macro_use]
61extern crate std;
62#[macro_use]
63extern crate bitflags;
64#[cfg(test)]
65extern crate lazy_static;
66#[cfg(test)]
67extern crate pretty_assertions;
68
69use core::array::TryFromSliceError;
70use core::convert::TryInto;
71use core::fmt;
72use core::mem;
73use core::str;
74
75#[macro_export]
76#[doc(hidden)]
77macro_rules! let_as_struct {
78    ($name:ident, $ty:ty, $data:expr) => {
79        use core::ptr;
80        let $name: $ty = unsafe { ptr::read($data.as_ptr() as *const _) };
81    };
82}
83
84#[doc(hidden)]
85macro_rules! lib_ensure {
86    ($cond:expr, $e:expr) => {
87        if !($cond) {
88            return Err($e);
89        }
90    };
91}
92
93#[macro_use]
94pub mod bitfield;
95
96pub mod structures;
97pub use structures::*;
98
99#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
100enum EntryPointFormat {
101    V2,
102    V3,
103}
104
105#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
106pub enum EntryPoint {
107    V2(EntryPointV2),
108    V3(EntryPointV3),
109}
110
111impl EntryPoint {
112    #[allow(clippy::len_without_is_empty)]
113    pub fn len(&self) -> u8 {
114        match self {
115            EntryPoint::V2(point) => point.len,
116            EntryPoint::V3(point) => point.len,
117        }
118    }
119    pub fn major(&self) -> u8 {
120        match self {
121            EntryPoint::V2(point) => point.major,
122            EntryPoint::V3(point) => point.major,
123        }
124    }
125    pub fn minor(&self) -> u8 {
126        match self {
127            EntryPoint::V2(point) => point.minor,
128            EntryPoint::V3(point) => point.minor,
129        }
130    }
131    pub fn revision(&self) -> u8 {
132        match self {
133            EntryPoint::V2(point) => point.revision,
134            EntryPoint::V3(point) => point.revision,
135        }
136    }
137    pub fn smbios_address(&self) -> u64 {
138        match self {
139            EntryPoint::V2(point) => point.smbios_address as u64,
140            EntryPoint::V3(point) => point.smbios_address,
141        }
142    }
143    pub fn smbios_len(&self) -> u32 {
144        match self {
145            EntryPoint::V2(point) => point.smbios_len as u32,
146            EntryPoint::V3(point) => point.smbios_len_max,
147        }
148    }
149    pub fn to_version(&self) -> SmbiosVersion {
150        SmbiosVersion {
151            major: self.major(),
152            minor: self.minor(),
153        }
154    }
155
156    /// Create an iterator across the SMBIOS structures found in `buffer`.
157    ///
158    /// # Example
159    ///
160    /// ```
161    /// # extern crate dmidecode;
162    /// # use std::error::Error;
163    /// use dmidecode::EntryPoint;
164    /// # fn try_main() -> Result<(), Box<dyn Error>> {
165    /// #
166    /// const DMIDECODE_BIN: &'static [u8] = include_bytes!("../tests/data/dmidecode.bin");
167    ///
168    /// let entry_point = EntryPoint::search(DMIDECODE_BIN)?;
169    /// for s in entry_point.structures(&DMIDECODE_BIN[entry_point.smbios_address() as usize..]) {
170    ///   let table = s?;
171    /// }
172    /// Ok(())
173    /// # }
174    /// ```
175    pub fn structures<'buffer>(&self, buffer: &'buffer [u8]) -> Structures<'buffer> {
176        Structures {
177            smbios_version: self.to_version(),
178            smbios_len: self.smbios_len(),
179            idx: 0u32,
180            buffer,
181        }
182    }
183
184    /// Search for an instance of an SMBIOS `EntryPoint` in a memory `buffer`.
185    ///
186    /// # Example
187    ///
188    /// ```
189    /// # extern crate dmidecode;
190    /// use dmidecode::EntryPoint;
191    ///
192    /// const ENTRY_BIN: &'static [u8] = include_bytes!("../tests/data/entry.bin");
193    ///
194    /// let entry_point = EntryPoint::search(ENTRY_BIN);
195    /// ```
196    ///
197    /// # Errors
198    ///
199    /// If this function fails to find a valid SMBIOS `EntryPoint`, it will return
200    /// an `InvalidEntryPointError` variant.
201    pub fn search(buffer: &[u8]) -> Result<EntryPoint, InvalidEntryPointError> {
202        find_signature(buffer)
203            .ok_or(InvalidEntryPointError::NotFound)
204            .and_then(|(kind, start)| {
205                let sub_buffer = &buffer[start..];
206
207                let entry_point = match kind {
208                    EntryPointFormat::V2 => {
209                        lib_ensure!(
210                            sub_buffer.len() >= mem::size_of::<EntryPointV2>(),
211                            InvalidEntryPointError::BadSize(sub_buffer.len() as u8)
212                        );
213                        let_as_struct!(entry_point, EntryPointV2, sub_buffer);
214                        lib_ensure!(
215                            entry_point.len as usize >= mem::size_of::<EntryPointV2>(),
216                            InvalidEntryPointError::BadSize(entry_point.len)
217                        );
218                        EntryPoint::V2(entry_point)
219                    }
220                    EntryPointFormat::V3 => {
221                        lib_ensure!(
222                            sub_buffer.len() >= mem::size_of::<EntryPointV3>(),
223                            InvalidEntryPointError::BadSize(sub_buffer.len() as u8)
224                        );
225                        let_as_struct!(entry_point, EntryPointV3, sub_buffer);
226                        lib_ensure!(
227                            entry_point.len as usize >= mem::size_of::<EntryPointV3>(),
228                            InvalidEntryPointError::BadSize(entry_point.len)
229                        );
230                        EntryPoint::V3(entry_point)
231                    }
232                };
233
234                lib_ensure!(
235                    entry_point.major() >= 2,
236                    InvalidEntryPointError::TooOldVersion(entry_point.major())
237                );
238
239                lib_ensure!(
240                    sub_buffer.len() as u8 >= entry_point.len(),
241                    InvalidEntryPointError::BadSize(sub_buffer.len() as u8)
242                );
243
244                let mut sum = 0u8;
245                for val in &sub_buffer[0..(entry_point.len() as usize)] {
246                    sum = sum.wrapping_add(*val);
247                }
248                lib_ensure!(sum == 0, InvalidEntryPointError::BadChecksum(sum));
249
250                Ok(entry_point)
251            })
252    }
253}
254
255///
256/// An SMBIOSv2 `EntryPoint` structure.
257///
258/// The SMBIOS `EntryPoint` structure is used to verify that a set of SMBIOS tables exist
259/// in memory and what version of the SMBIOS specification should be used to
260/// access the tables.
261///
262#[repr(C)]
263#[repr(packed)]
264#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
265pub struct EntryPointV2 {
266    pub signature: u32,
267    pub checksum: u8,
268    pub len: u8,
269    pub major: u8,
270    pub minor: u8,
271    pub struct_max: u16,
272    pub revision: u8,
273    pub formatted: [u8; 5],
274    pub dmi_signature: [u8; 5],
275    pub dmi_checksum: u8,
276    pub smbios_len: u16,
277    pub smbios_address: u32,
278    pub smbios_count: u16,
279    pub bcd_revision: u8,
280}
281
282///
283/// An SMBIOSv3 `EntryPoint` structure.
284///
285#[repr(C)]
286#[repr(packed)]
287#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
288pub struct EntryPointV3 {
289    pub signature: [u8; 5],
290    pub checksum: u8,
291    pub len: u8,
292    pub major: u8,
293    pub minor: u8,
294    pub docrev: u8,
295    pub revision: u8,
296    _reserved: u8,
297    pub smbios_len_max: u32,
298    pub smbios_address: u64,
299}
300
301/// The version number associated with the Smbios `EntryPoint`
302#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
303pub struct SmbiosVersion {
304    pub major: u8,
305    pub minor: u8,
306}
307
308impl From<(usize, usize)> for SmbiosVersion {
309    fn from(other: (usize, usize)) -> SmbiosVersion {
310        SmbiosVersion {
311            major: other.0 as u8,
312            minor: other.1 as u8,
313        }
314    }
315}
316
317/// Failure type for trying to find the SMBIOS `EntryPoint` structure in memory.
318#[derive(Debug)]
319pub enum InvalidEntryPointError {
320    /// The SMBIOS `EntryPoint` structure was not found in the memory buffer.
321    NotFound,
322    /// The SMBIOS `EntryPoint` structure was versioned before 2.0.
323    TooOldVersion(u8),
324    /// The SMBIOS `EntryPoint` structure was smaller than the size of the SMBIOS 2.1 structure.
325    BadSize(u8),
326    /// The SMBIOS `EntryPoint` structure had an invalid checksum.
327    BadChecksum(u8),
328}
329
330impl fmt::Display for InvalidEntryPointError {
331    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332        match self {
333            InvalidEntryPointError::NotFound => write!(f, "Input did not contain a valid SMBIOS entry point"),
334            InvalidEntryPointError::TooOldVersion(version) => {
335                write!(f, "Input version number was below 2.0: {}", version)
336            }
337            InvalidEntryPointError::BadSize(size) => {
338                write!(f, "Input contained an invalid-sized SMBIOS entry: {}", size)
339            }
340            InvalidEntryPointError::BadChecksum(checksum) => {
341                write!(f, "SMBIOS entry point has an invalid checksum: {}", checksum)
342            }
343        }
344    }
345}
346
347#[cfg(feature = "std")]
348impl std::error::Error for InvalidEntryPointError {}
349
350fn find_signature(buffer: &[u8]) -> Option<(EntryPointFormat, usize)> {
351    static STRIDE: usize = 16;
352    static V2_SIG: &[u8; 4] = &[0x5f, 0x53, 0x4d, 0x5f];
353    static V3_SIG: &[u8; 5] = &[0x5f, 0x53, 0x4d, 0x33, 0x5f];
354
355    for (idx, chunk) in buffer.chunks(STRIDE).enumerate() {
356        if chunk.starts_with(V2_SIG) {
357            return Some((EntryPointFormat::V2, idx * STRIDE));
358        } else if chunk.starts_with(V3_SIG) {
359            return Some((EntryPointFormat::V3, idx * STRIDE));
360        }
361    }
362
363    None
364}
365
366/// An iterator that traverses the SMBIOS structure tables.
367/// This struct is produced by the `structures` method on `EntryPoint`. See its documentation for more details.
368#[derive(Clone, Debug, Eq, Hash, PartialEq)]
369pub struct Structures<'buffer> {
370    smbios_version: SmbiosVersion,
371    smbios_len: u32,
372    idx: u32,
373    buffer: &'buffer [u8],
374}
375
376/// Variant structure for decoding the SMBIOS table types.
377#[derive(Clone, Debug, Eq, Hash, PartialEq)]
378pub enum Structure<'buffer> {
379    Bios(Bios<'buffer>),
380    System(System<'buffer>),
381    BaseBoard(BaseBoard<'buffer>),
382    Enclosure(Enclosure<'buffer>),
383    Processor(Processor<'buffer>),
384    Cache(Cache<'buffer>),
385    PortConnector(PortConnector<'buffer>),
386    SystemSlots(SystemSlots<'buffer>),
387    OemStrings(OemStrings<'buffer>),
388    SystemConfigurationOptions(SystemConfigurationOptions<'buffer>),
389    BiosLanguage(BiosLanguage<'buffer>),
390    GroupAssociations(GroupAssociations<'buffer>),
391    SystemEventLog(SystemEventLog<'buffer>),
392    MemoryDevice(MemoryDevice<'buffer>),
393    MemoryError32(MemoryError32),
394    MemoryArrayMappedAddress(MemoryArrayMappedAddress),
395    MemoryDeviceMappedAddress(MemoryDeviceMappedAddress),
396    BuiltInPointingDevice(BuiltInPointingDevice),
397    PortableBattery(PortableBattery<'buffer>),
398    PhysicalMemoryArray(PhysicalMemoryArray),
399    Other(RawStructure<'buffer>),
400}
401
402/// Failure type for trying to decode the SMBIOS `Structures` iterator into the `Structure` variant type.
403
404#[derive(Debug)]
405pub enum MalformedStructureError {
406    /// The SMBIOS structure exceeds the end of the memory buffer given to the `EntryPoint::structures` method.
407    BadSize(u32, u8),
408    /// The SMBIOS structure contains an unterminated strings section.
409    UnterminatedStrings(u32),
410    /// The SMBIOS structure contains an invalid string index.
411    InvalidStringIndex(InfoType, u16, u8),
412    /// This error returned when a conversion from a slice to an array fails.
413    InvalidSlice(core::array::TryFromSliceError),
414    /// The SMBIOS structure formatted section length does not correspond to SMBIOS reference
415    /// specification
416    InvalidFormattedSectionLength(InfoType, u16, &'static str, u8),
417}
418
419impl fmt::Display for MalformedStructureError {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        match self {
422            MalformedStructureError::BadSize(offset, length) => {
423                write!(
424                    f,
425                    "Structure at offset {} with length {} extends beyond SMBIOS",
426                    offset, length
427                )
428            }
429            MalformedStructureError::UnterminatedStrings(offset) => {
430                write!(f, "Structure at offset {} with unterminated strings", offset)
431            }
432            MalformedStructureError::InvalidStringIndex(info_type, handle, index) => {
433                write!(
434                    f,
435                    "Structure {:?} with handle {} has invalid string index {}",
436                    info_type, handle, index
437                )
438            }
439            MalformedStructureError::InvalidSlice(cause) => {
440                write!(f, "{}", cause)
441            }
442            MalformedStructureError::InvalidFormattedSectionLength(info_type, handle, spec, length) => {
443                write!(
444                    f,
445                    "Formatted section length of structure {:?} with handle {} should be {}{} bytes",
446                    info_type, handle, spec, length
447                )
448            }
449        }
450    }
451}
452
453#[cfg(feature = "std")]
454impl std::error::Error for MalformedStructureError {
455    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
456        match self {
457            MalformedStructureError::InvalidSlice(ref cause) => Some(cause),
458            _ => None,
459        }
460    }
461}
462
463#[doc(hidden)]
464/// Finds the final nul nul terminator of a buffer and returns the index of the final nul
465fn find_nulnul(buf: &[u8]) -> Option<usize> {
466    for i in 0..buf.len() {
467        if i + 1 >= buf.len() {
468            return None;
469        }
470
471        if buf[i] == 0 && buf[i + 1] == 0 {
472            return Some(i + 1);
473        }
474    }
475
476    None
477}
478
479impl<'buffer> Iterator for Structures<'buffer> {
480    type Item = Result<Structure<'buffer>, MalformedStructureError>;
481
482    fn next(&mut self) -> Option<Self::Item> {
483        let structure = match self.next_raw()? {
484            Ok(s) => s,
485            Err(e) => {
486                // make any errors to get the raw structure stop
487                // future iterations. This will avoid any nfinite
488                // iterations when skipping errors
489                self.smbios_len = self.idx;
490                return Some(Err(e));
491            }
492        };
493
494        /*
495         * For SMBIOS v3 we have no exact table length and no item count,
496         * so stop at the end-of-table marker.
497         */
498        if self.smbios_version.major >= 3 && structure.info == InfoType::End {
499            self.smbios_len = self.idx;
500        }
501
502        Some(match structure.info {
503            InfoType::Bios => Bios::try_from(structure).map(Structure::Bios),
504            InfoType::System => System::try_from(structure).map(Structure::System),
505            InfoType::BaseBoard => BaseBoard::try_from(structure).map(Structure::BaseBoard),
506            InfoType::Enclosure => Enclosure::try_from(structure).map(Structure::Enclosure),
507            InfoType::Processor => Processor::try_from(structure).map(Structure::Processor),
508            InfoType::Cache => Cache::try_from(structure).map(Structure::Cache),
509            InfoType::PortConnector => PortConnector::try_from(structure).map(Structure::PortConnector),
510            InfoType::SystemSlots => SystemSlots::try_from(structure).map(Structure::SystemSlots),
511            InfoType::OemStrings => OemStrings::try_from(structure).map(Structure::OemStrings),
512            InfoType::SystemConfigurationOptions => {
513                SystemConfigurationOptions::try_from(structure).map(Structure::SystemConfigurationOptions)
514            }
515            InfoType::BiosLanguage => BiosLanguage::try_from(structure).map(Structure::BiosLanguage),
516            InfoType::GroupAssociations => GroupAssociations::try_from(structure).map(Structure::GroupAssociations),
517            InfoType::SystemEventLog => SystemEventLog::try_from(structure).map(Structure::SystemEventLog),
518            InfoType::PhysicalMemoryArray => {
519                PhysicalMemoryArray::try_from(structure).map(Structure::PhysicalMemoryArray)
520            }
521            InfoType::MemoryDevice => MemoryDevice::try_from(structure).map(Structure::MemoryDevice),
522            InfoType::MemoryError32 => MemoryError32::try_from(structure).map(Structure::MemoryError32),
523            InfoType::MemoryArrayMappedAddress => {
524                MemoryArrayMappedAddress::try_from(structure).map(Structure::MemoryArrayMappedAddress)
525            }
526            InfoType::MemoryDeviceMappedAddress => {
527                MemoryDeviceMappedAddress::try_from(structure).map(Structure::MemoryDeviceMappedAddress)
528            }
529            InfoType::BuiltInPointingDevice => {
530                BuiltInPointingDevice::try_from(structure).map(Structure::BuiltInPointingDevice)
531            }
532            InfoType::PortableBattery => PortableBattery::try_from(structure).map(Structure::PortableBattery),
533            _ => Ok(Structure::Other(structure)),
534        })
535    }
536}
537
538impl<'buffer> Structures<'buffer> {
539    fn next_raw(&mut self) -> Option<Result<RawStructure<'buffer>, MalformedStructureError>> {
540        if (self.idx + mem::size_of::<HeaderPacked>() as u32) > self.smbios_len {
541            return None;
542        }
543
544        let working = &self.buffer[(self.idx as usize)..];
545        let_as_struct!(header, HeaderPacked, working);
546
547        let strings_idx: u32 = self.idx + header.len as u32;
548        if strings_idx >= self.smbios_len {
549            return Some(Err(MalformedStructureError::BadSize(self.idx, header.len)));
550        }
551
552        let term = find_nulnul(&self.buffer[(strings_idx as usize)..]);
553        let strings_len = match term {
554            Some(terminator) => (terminator + 1) as u32,
555            None => {
556                return Some(Err(MalformedStructureError::UnterminatedStrings(self.idx)));
557            }
558        };
559
560        let structure = RawStructure {
561            version: self.smbios_version,
562            info: header.kind.into(),
563            length: header.len,
564            handle: header.handle,
565            data: &self.buffer[(self.idx + mem::size_of::<HeaderPacked>() as u32) as usize..strings_idx as usize],
566            strings: &self.buffer[strings_idx as usize..(strings_idx + strings_len) as usize],
567        };
568
569        self.idx = strings_idx + strings_len;
570
571        Some(Ok(structure))
572    }
573}
574
575#[doc(hidden)]
576#[repr(C)]
577#[repr(packed)]
578struct HeaderPacked {
579    kind: u8,
580    len: u8,
581    handle: u16,
582}
583
584/// The raw SMBIOS structure information for structures that are not handled by this crate, such as Oem structures.
585#[derive(Clone, Debug, Eq, Hash, PartialEq)]
586pub struct RawStructure<'buffer> {
587    pub version: SmbiosVersion,
588    pub info: InfoType,
589    pub length: u8,
590    pub handle: u16,
591    pub data: &'buffer [u8],
592    strings: &'buffer [u8],
593}
594
595/// General trait for slice -> unsigned conversion
596pub trait TryFromBytes<'a, T>: Sized {
597    fn try_from_bytes(_: &'a [u8]) -> Result<Self, TryFromSliceError>;
598}
599
600impl<'a> TryFromBytes<'a, u8> for u8 {
601    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
602        bytes.try_into().map(u8::from_le_bytes)
603    }
604}
605impl<'a> TryFromBytes<'a, u16> for u16 {
606    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
607        bytes.try_into().map(u16::from_le_bytes)
608    }
609}
610impl<'a> TryFromBytes<'a, u32> for u32 {
611    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
612        bytes.try_into().map(u32::from_le_bytes)
613    }
614}
615impl<'a> TryFromBytes<'a, u64> for u64 {
616    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
617        bytes.try_into().map(u64::from_le_bytes)
618    }
619}
620impl<'a> TryFromBytes<'a, u128> for u128 {
621    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
622        bytes.try_into().map(u128::from_le_bytes)
623    }
624}
625
626impl<'buffer> RawStructure<'buffer> {
627    /// Return an iterator over the strings in the strings table.
628    fn strings(&self) -> StructureStrings<'buffer> {
629        StructureStrings::new(self.strings)
630    }
631
632    /// Find a string in the strings table by the string index.
633    /// If the string index is 0, the empty string is returned. Otherwise, the string corresponding
634    /// to that string index in the strings table is returned.
635    ///
636    /// # Errors
637    /// Returns a `MalformedStructureError::InvalidStringIndex` if the index is outside of the strings table.
638    pub fn find_string(&self, idx: u8) -> Result<&'buffer str, MalformedStructureError> {
639        if idx == 0 {
640            Ok("")
641        } else {
642            self.strings()
643                .nth((idx - 1) as usize)
644                .ok_or(MalformedStructureError::InvalidStringIndex(self.info, self.handle, idx))
645        }
646    }
647    /// Get value by offset declared in SMBIOS Reference Specification.\
648    /// Type meaning data length is mandatory:
649    /// - *BYTE*: u8
650    /// - *WORD*: u16
651    /// - *DWORD*: u32
652    /// - *QWORD*: u64
653    ///
654    /// The only error this method returned: [MalformedStructureError::InvalidSlice] (actually is
655    /// [core::array::TryFromSliceError]). If getting value index exceedes length of *Formatted
656    /// section* it may be ignored to return [None] value of structure field. In this case *Formatted
657    /// section* length automatically hide non-existing values
658    pub fn get<T: TryFromBytes<'buffer, T>>(&self, offset: usize) -> Result<T, MalformedStructureError> {
659        // Ignore header
660        let start = offset - 4;
661        let size = core::mem::size_of::<T>();
662        let slice = self.data.get(start..(start + size)).unwrap_or(&[]);
663        TryFromBytes::try_from_bytes(slice).map_err(MalformedStructureError::InvalidSlice)
664    }
665    /// Wrapper to self.data.get(..) with header offset correction
666    pub fn get_slice(&self, offset: usize, size: usize) -> Option<&'buffer [u8]> {
667        self.data.get(offset - 4..offset - 4 + size)
668    }
669    /// Get *STRING* by offset declared in SMBIOS Reference Specification
670    pub fn get_string(&self, offset: usize) -> Result<&'buffer str, MalformedStructureError> {
671        self.get::<u8>(offset).and_then(|idx| self.find_string(idx))
672    }
673}
674
675/// An iterator over structure strings
676#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
677pub struct StructureStrings<'a> {
678    bytes: &'a [u8],
679    start: usize,
680}
681
682impl<'a> StructureStrings<'a> {
683    fn new(bytes: &'a [u8]) -> Self {
684        Self { bytes, start: 0 }
685    }
686}
687impl<'a> Iterator for StructureStrings<'a> {
688    type Item = &'a str;
689
690    fn next(&mut self) -> Option<Self::Item> {
691        let slice = self
692            .bytes
693            .get(self.start..)?
694            .split(|elm| *elm == 0)
695            .nth(0)
696            .filter(|slice| !slice.is_empty())?;
697        self.start += slice.len() + 1;
698        str::from_utf8(slice).ok()
699    }
700}
701
702/// SMBIOS Table information variant
703#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
704pub enum InfoType {
705    Bios,
706    System,
707    BaseBoard,
708    Enclosure,
709    Processor,
710    Cache,
711    PortConnector,
712    SystemSlots,
713    OemStrings,
714    SystemConfigurationOptions,
715    GroupAssociations,
716    SystemEventLog,
717    BiosLanguage,
718    PhysicalMemoryArray,
719    MemoryDevice,
720    MemoryError32,
721    MemoryArrayMappedAddress,
722    MemoryDeviceMappedAddress,
723    BuiltInPointingDevice,
724    PortableBattery,
725    SystemBoot,
726    Oem(u8),
727    End,
728}
729
730impl From<u8> for InfoType {
731    fn from(kind: u8) -> InfoType {
732        match kind {
733            0 => InfoType::Bios,
734            1 => InfoType::System,
735            2 => InfoType::BaseBoard,
736            3 => InfoType::Enclosure,
737            4 => InfoType::Processor,
738            7 => InfoType::Cache,
739            8 => InfoType::PortConnector,
740            9 => InfoType::SystemSlots,
741            11 => InfoType::OemStrings,
742            12 => InfoType::SystemConfigurationOptions,
743            13 => InfoType::BiosLanguage,
744            14 => InfoType::GroupAssociations,
745            15 => InfoType::SystemEventLog,
746            16 => InfoType::PhysicalMemoryArray,
747            17 => InfoType::MemoryDevice,
748            18 => InfoType::MemoryError32,
749            19 => InfoType::MemoryArrayMappedAddress,
750            20 => InfoType::MemoryDeviceMappedAddress,
751            21 => InfoType::BuiltInPointingDevice,
752            22 => InfoType::PortableBattery,
753            32 => InfoType::SystemBoot,
754            127 => InfoType::End,
755            t => InfoType::Oem(t),
756        }
757    }
758}
759impl fmt::Display for InfoType {
760    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
761        match self {
762            InfoType::Bios => write!(f, "BIOS Information"),
763            InfoType::System => write!(f, "System Information"),
764            InfoType::BaseBoard => write!(f, "Baseboard (or Module) Information"),
765            InfoType::Enclosure => write!(f, "System Enclosure or Chassis"),
766            InfoType::Processor => write!(f, "Processor Information"),
767            //InfoType::                          => write!(f, "Memory Controller Information"),
768            //InfoType::                          => write!(f, "Memory Module Information"),
769            InfoType::Cache => write!(f, "Cache Information"),
770            InfoType::PortConnector => write!(f, "Port Connector Information"),
771            InfoType::SystemSlots => write!(f, "System Slots"),
772            //InfoType::                          => write!(f, "On Board Devices Information"),
773            InfoType::OemStrings => write!(f, "OEM Strings"),
774            InfoType::SystemConfigurationOptions => write!(f, "System Configuration Options"),
775            InfoType::BiosLanguage => write!(f, "BIOS Language Information"),
776            InfoType::GroupAssociations => write!(f, "Group Associations"),
777            InfoType::SystemEventLog => write!(f, "System Event Log"),
778            InfoType::PhysicalMemoryArray => write!(f, "Physical Memory Array"),
779            InfoType::MemoryDevice => write!(f, "Memory Device"),
780            InfoType::MemoryError32 => write!(f, "32-Bit Memory Error Information"),
781            InfoType::MemoryArrayMappedAddress => write!(f, "Memory Array Mapped Address"),
782            InfoType::MemoryDeviceMappedAddress => write!(f, "Memory Device Mapped Address"),
783            InfoType::BuiltInPointingDevice => write!(f, "Built-in Pointing Device"),
784            InfoType::PortableBattery => write!(f, "Portable Battery"),
785            //InfoType::                          => write!(f, "System Reset"),
786            //InfoType::                          => write!(f, "Hardware Security"),
787            //InfoType::                          => write!(f, "System Power Controls"),
788            //InfoType::                          => write!(f, "Voltage Probe"),
789            //InfoType::                          => write!(f, "Cooling Device"),
790            //InfoType::                          => write!(f, "Temperature Probe"),
791            //InfoType::                          => write!(f, "Electrical Current Probe"),
792            //InfoType::                          => write!(f, "Out-of-Band Remote Access"),
793            //InfoType::                          => write!(f, "Boot Integrity Services (BIS) Entry Point"),
794            InfoType::SystemBoot => write!(f, "System Boot Information"),
795            //InfoType::                          => write!(f, "64-Bit Memory Error Information"),
796            //InfoType::                          => write!(f, "Management Device"),
797            //InfoType::                          => write!(f, "Management Device Component"),
798            //InfoType::                          => write!(f, "Management Device Threshold Data"),
799            //InfoType::                          => write!(f, "Memory Channel"),
800            //InfoType::                          => write!(f, "IPMI Device Information"),
801            //InfoType::                          => write!(f, "System Power Supply"),
802            //InfoType::                          => write!(f, "Additional Information"),
803            //InfoType::                          => write!(f, "Onboard Devices Extended Information"),
804            //InfoType::                          => write!(f, "Management Controller Host Interface"),
805            //InfoType::                          => write!(f, "TPM Device"),
806            //InfoType::                          => write!(f, "Processor Additional Information"),
807            //InfoType::                          => write!(f, "Inactive"),
808            InfoType::End => write!(f, "End-of-Table"),
809            InfoType::Oem(t) => write!(f, "OEM: {}", t),
810        }
811    }
812}
813
814#[cfg(test)]
815mod tests {
816    use super::*;
817
818    const DMIDECODE_BIN: &[u8] = include_bytes!("../tests/data/dmidecode.bin");
819    const ENTRY_V2_BIN: &[u8] = include_bytes!("../tests/data/entry.bin");
820    const DMI_V2_BIN: &[u8] = include_bytes!("../tests/data/dmi.bin");
821    const ENTRY_V3_BIN: &[u8] = include_bytes!("../tests/data/entry_v3.bin");
822    const DMI_V3_BIN: &[u8] = include_bytes!("../tests/data/dmi_v3.bin");
823    const DMI_V3_SHORT: &[u8] = include_bytes!("../tests/data/dmi_v3_short.bin");
824    const ENTRY_V3_SHORT: &[u8] = include_bytes!("../tests/data/entry_v3_short.bin");
825
826    #[test]
827    fn found_smbios_entry() {
828        EntryPoint::search(ENTRY_V2_BIN).unwrap();
829        EntryPoint::search(DMIDECODE_BIN).unwrap();
830    }
831
832    #[test]
833    fn found_smbios_entry_v3() {
834        EntryPoint::search(ENTRY_V3_BIN).unwrap();
835    }
836
837    #[test]
838    #[should_panic]
839    fn doesnt_find_smbios_entry() {
840        EntryPoint::search(DMI_V2_BIN).unwrap();
841    }
842
843    #[test]
844    fn found_signature() {
845        find_signature(ENTRY_V2_BIN).unwrap();
846        find_signature(ENTRY_V3_BIN).unwrap();
847        find_signature(DMIDECODE_BIN).unwrap();
848    }
849
850    #[test]
851    #[should_panic]
852    fn doesnt_find_signature() {
853        find_signature(DMI_V2_BIN).unwrap();
854        find_signature(DMI_V3_BIN).unwrap();
855    }
856
857    #[test]
858    fn iterator_through_structures() {
859        let entry_point = EntryPoint::search(DMIDECODE_BIN).unwrap();
860        for s in entry_point
861            .structures(&DMIDECODE_BIN[(entry_point.smbios_address() as usize)..])
862            .filter_map(|s| s.ok())
863        {
864            println!("{:?}", s);
865        }
866    }
867
868    #[test]
869    fn iterator_through_structures_v3_short() {
870        let entry_point = EntryPoint::search(ENTRY_V3_SHORT).unwrap();
871        for s in entry_point.structures(DMI_V3_SHORT).filter_map(|s| s.ok()) {
872            println!("{:?}", s);
873        }
874    }
875
876    #[test]
877    fn iterator_through_structures_v3() {
878        let entry_point = EntryPoint::search(ENTRY_V3_BIN).unwrap();
879        for s in entry_point.structures(DMI_V3_BIN).filter_map(|s| s.ok()) {
880            println!("{:?}", s);
881        }
882    }
883
884    #[test]
885    fn find_nulnul_empty() {
886        let buf = [];
887        assert_eq!(find_nulnul(&buf), None);
888    }
889
890    #[test]
891    fn find_nulnul_single_char() {
892        let buf = [0];
893        assert_eq!(find_nulnul(&buf), None);
894    }
895
896    #[test]
897    fn find_nulnul_trivial() {
898        let buf = [0, 0];
899        assert_eq!(find_nulnul(&buf), Some(1));
900    }
901
902    #[test]
903    fn find_nulnul_with_data() {
904        let buf = [1, 2, 3, 4, 0, 5, 4, 3, 2, 1, 0, 0];
905        assert_eq!(find_nulnul(&buf), Some(11));
906    }
907
908    #[test]
909    fn find_nulnul_with_data_more_at_end() {
910        let buf = [1, 2, 3, 4, 0, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3];
911        assert_eq!(find_nulnul(&buf), Some(11));
912    }
913
914    #[test]
915    fn structure_strings() {
916        use pretty_assertions::assert_eq;
917        use std::prelude::v1::*;
918
919        let regular_bytes = &[65, 66, 67, 0, 68, 69, 0, 70, 0, 71, 72, 73, 0, 0];
920        let regular_ss = StructureStrings::new(regular_bytes).collect::<Vec<_>>();
921        assert_eq!(vec!["ABC", "DE", "F", "GHI"], regular_ss, "Regular bytes");
922
923        let zero_bytes = &[0, 0];
924        let zero_ss = StructureStrings::new(zero_bytes).collect::<Vec<_>>();
925        assert_eq!(vec![""; 0], zero_ss, "Zero bytes");
926
927        let no_tail_bytes = &[65, 66, 67, 0, 68, 69, 0, 70, 0, 71, 72, 73];
928        let no_tail_ss = StructureStrings::new(no_tail_bytes).collect::<Vec<_>>();
929        assert_eq!(vec!["ABC", "DE", "F", "GHI"], no_tail_ss, "Regular bytes");
930
931        let invalid_order1_bytes = &[65, 66, 67, 0, 0, 68, 69, 0, 0, 0, 0, 0];
932        let invalid_order1_ss = StructureStrings::new(invalid_order1_bytes).collect::<Vec<_>>();
933        assert_eq!(vec!["ABC"], invalid_order1_ss, "Invalid order 1 bytes");
934
935        let invalid_order2_bytes = &[0, 0, 65, 66, 67];
936        let invalid_order2_ss = StructureStrings::new(invalid_order2_bytes).collect::<Vec<&str>>();
937        assert_eq!(vec![""; 0], invalid_order2_ss, "Invalid order 2 bytes");
938    }
939}