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#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
318struct SmbiosBound {
319    len: u16,
320    count: u16,
321}
322
323/// Failure type for trying to find the SMBIOS `EntryPoint` structure in memory.
324#[derive(Debug)]
325pub enum InvalidEntryPointError {
326    /// The SMBIOS `EntryPoint` structure was not found in the memory buffer.
327    NotFound,
328    /// The SMBIOS `EntryPoint` structure was versioned before 2.0.
329    TooOldVersion(u8),
330    /// The SMBIOS `EntryPoint` structure was smaller than the size of the SMBIOS 2.1 structure.
331    BadSize(u8),
332    /// The SMBIOS `EntryPoint` structure had an invalid checksum.
333    BadChecksum(u8),
334}
335
336impl fmt::Display for InvalidEntryPointError {
337    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        match self {
339            InvalidEntryPointError::NotFound => write!(f, "Input did not contain a valid SMBIOS entry point"),
340            InvalidEntryPointError::TooOldVersion(version) => {
341                write!(f, "Input version number was below 2.0: {}", version)
342            }
343            InvalidEntryPointError::BadSize(size) => {
344                write!(f, "Input contained an invalid-sized SMBIOS entry: {}", size)
345            }
346            InvalidEntryPointError::BadChecksum(checksum) => {
347                write!(f, "SMBIOS entry point has an invalid checksum: {}", checksum)
348            }
349        }
350    }
351}
352
353#[cfg(feature = "std")]
354impl std::error::Error for InvalidEntryPointError {}
355
356fn find_signature(buffer: &[u8]) -> Option<(EntryPointFormat, usize)> {
357    static STRIDE: usize = 16;
358    static V2_SIG: &[u8; 4] = &[0x5f, 0x53, 0x4d, 0x5f];
359    static V3_SIG: &[u8; 5] = &[0x5f, 0x53, 0x4d, 0x33, 0x5f];
360
361    for (idx, chunk) in buffer.chunks(STRIDE).enumerate() {
362        if chunk.starts_with(V2_SIG) {
363            return Some((EntryPointFormat::V2, idx * STRIDE));
364        } else if chunk.starts_with(V3_SIG) {
365            return Some((EntryPointFormat::V3, idx * STRIDE));
366        }
367    }
368
369    None
370}
371
372/// An iterator that traverses the SMBIOS structure tables.
373/// This struct is produced by the `structures` method on `EntryPoint`. See its documentation for more details.
374#[derive(Clone, Debug, Eq, Hash, PartialEq)]
375pub struct Structures<'buffer> {
376    smbios_version: SmbiosVersion,
377    smbios_len: u32,
378    idx: u32,
379    buffer: &'buffer [u8],
380}
381
382/// Variant structure for decoding the SMBIOS table types.
383#[derive(Clone, Debug, Eq, Hash, PartialEq)]
384pub enum Structure<'buffer> {
385    Bios(Bios<'buffer>),
386    System(System<'buffer>),
387    BaseBoard(BaseBoard<'buffer>),
388    Enclosure(Enclosure<'buffer>),
389    Processor(Processor<'buffer>),
390    Cache(Cache<'buffer>),
391    PortConnector(PortConnector<'buffer>),
392    SystemSlots(SystemSlots<'buffer>),
393    OemStrings(OemStrings<'buffer>),
394    SystemConfigurationOptions(SystemConfigurationOptions<'buffer>),
395    BiosLanguage(BiosLanguage<'buffer>),
396    GroupAssociations(GroupAssociations<'buffer>),
397    SystemEventLog(SystemEventLog<'buffer>),
398    MemoryDevice(MemoryDevice<'buffer>),
399    MemoryError32(MemoryError32),
400    MemoryArrayMappedAddress(MemoryArrayMappedAddress),
401    MemoryDeviceMappedAddress(MemoryDeviceMappedAddress),
402    BuiltInPointingDevice(BuiltInPointingDevice),
403    PortableBattery(PortableBattery<'buffer>),
404    PhysicalMemoryArray(PhysicalMemoryArray),
405    Other(RawStructure<'buffer>),
406}
407
408/// Failure type for trying to decode the SMBIOS `Structures` iterator into the `Structure` variant type.
409
410#[derive(Debug)]
411pub enum MalformedStructureError {
412    /// The SMBIOS structure exceeds the end of the memory buffer given to the `EntryPoint::structures` method.
413    BadSize(u32, u8),
414    /// The SMBIOS structure contains an unterminated strings section.
415    UnterminatedStrings(u32),
416    /// The SMBIOS structure contains an invalid string index.
417    InvalidStringIndex(InfoType, u16, u8),
418    /// This error returned when a conversion from a slice to an array fails.
419    InvalidSlice(core::array::TryFromSliceError),
420    /// The SMBIOS structure formatted section length does not correspond to SMBIOS reference
421    /// specification
422    InvalidFormattedSectionLength(InfoType, u16, &'static str, u8),
423    /// The SMBIOS structure contains an invalid processor family
424    InvalidProcessorFamily,
425}
426
427impl fmt::Display for MalformedStructureError {
428    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429        match self {
430            MalformedStructureError::BadSize(offset, length) => {
431                write!(
432                    f,
433                    "Structure at offset {} with length {} extends beyond SMBIOS",
434                    offset, length
435                )
436            }
437            MalformedStructureError::UnterminatedStrings(offset) => {
438                write!(f, "Structure at offset {} with unterminated strings", offset)
439            }
440            MalformedStructureError::InvalidStringIndex(info_type, handle, index) => {
441                write!(
442                    f,
443                    "Structure {:?} with handle {} has invalid string index {}",
444                    info_type, handle, index
445                )
446            }
447            MalformedStructureError::InvalidSlice(cause) => {
448                write!(f, "{}", cause)
449            }
450            MalformedStructureError::InvalidFormattedSectionLength(info_type, handle, spec, length) => {
451                write!(
452                    f,
453                    "Formatted section length of structure {:?} with handle {} should be {}{} bytes",
454                    info_type, handle, spec, length
455                )
456            }
457            MalformedStructureError::InvalidProcessorFamily => {
458                write!(f, "Invalid processor family")
459            }
460        }
461    }
462}
463
464#[cfg(feature = "std")]
465impl std::error::Error for MalformedStructureError {
466    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
467        match self {
468            MalformedStructureError::InvalidSlice(ref cause) => Some(cause),
469            _ => None,
470        }
471    }
472}
473
474#[doc(hidden)]
475/// Finds the final nul nul terminator of a buffer and returns the index of the final nul
476fn find_nulnul(buf: &[u8]) -> Option<usize> {
477    for i in 0..buf.len() {
478        if i + 1 >= buf.len() {
479            return None;
480        }
481
482        if buf[i] == 0 && buf[i + 1] == 0 {
483            return Some(i + 1);
484        }
485    }
486
487    None
488}
489
490impl<'buffer> Iterator for Structures<'buffer> {
491    type Item = Result<Structure<'buffer>, MalformedStructureError>;
492
493    fn next(&mut self) -> Option<Self::Item> {
494        let structure = match self.next_raw()? {
495            Ok(s) => s,
496            Err(e) => {
497                // make any errors to get the raw structure stop
498                // future iterations. This will avoid any nfinite
499                // iterations when skipping errors
500                self.smbios_len = self.idx;
501                return Some(Err(e));
502            }
503        };
504
505        /*
506         * For SMBIOS v3 we have no exact table length and no item count,
507         * so stop at the end-of-table marker.
508         */
509        if self.smbios_version.major >= 3 && structure.info == InfoType::End {
510            self.smbios_len = self.idx;
511        }
512
513        Some(match structure.info {
514            InfoType::Bios => Bios::try_from(structure).map(Structure::Bios),
515            InfoType::System => System::try_from(structure).map(Structure::System),
516            InfoType::BaseBoard => BaseBoard::try_from(structure).map(Structure::BaseBoard),
517            InfoType::Enclosure => Enclosure::try_from(structure).map(Structure::Enclosure),
518            InfoType::Processor => Processor::try_from(structure).map(Structure::Processor),
519            InfoType::Cache => Cache::try_from(structure).map(Structure::Cache),
520            InfoType::PortConnector => PortConnector::try_from(structure).map(Structure::PortConnector),
521            InfoType::SystemSlots => SystemSlots::try_from(structure).map(Structure::SystemSlots),
522            InfoType::OemStrings => OemStrings::try_from(structure).map(Structure::OemStrings),
523            InfoType::SystemConfigurationOptions => {
524                SystemConfigurationOptions::try_from(structure).map(Structure::SystemConfigurationOptions)
525            }
526            InfoType::BiosLanguage => BiosLanguage::try_from(structure).map(Structure::BiosLanguage),
527            InfoType::GroupAssociations => GroupAssociations::try_from(structure).map(Structure::GroupAssociations),
528            InfoType::SystemEventLog => SystemEventLog::try_from(structure).map(Structure::SystemEventLog),
529            InfoType::PhysicalMemoryArray => {
530                PhysicalMemoryArray::try_from(structure).map(Structure::PhysicalMemoryArray)
531            }
532            InfoType::MemoryDevice => MemoryDevice::try_from(structure).map(Structure::MemoryDevice),
533            InfoType::MemoryError32 => MemoryError32::try_from(structure).map(Structure::MemoryError32),
534            InfoType::MemoryArrayMappedAddress => {
535                MemoryArrayMappedAddress::try_from(structure).map(Structure::MemoryArrayMappedAddress)
536            }
537            InfoType::MemoryDeviceMappedAddress => {
538                MemoryDeviceMappedAddress::try_from(structure).map(Structure::MemoryDeviceMappedAddress)
539            }
540            InfoType::BuiltInPointingDevice => {
541                BuiltInPointingDevice::try_from(structure).map(Structure::BuiltInPointingDevice)
542            }
543            InfoType::PortableBattery => PortableBattery::try_from(structure).map(Structure::PortableBattery),
544            _ => Ok(Structure::Other(structure)),
545        })
546    }
547}
548
549impl<'buffer> Structures<'buffer> {
550    fn next_raw(&mut self) -> Option<Result<RawStructure<'buffer>, MalformedStructureError>> {
551        if (self.idx + mem::size_of::<HeaderPacked>() as u32) > self.smbios_len {
552            return None;
553        }
554
555        let working = &self.buffer[(self.idx as usize)..];
556        let_as_struct!(header, HeaderPacked, working);
557
558        let strings_idx: u32 = self.idx + header.len as u32;
559        if strings_idx >= self.smbios_len {
560            return Some(Err(MalformedStructureError::BadSize(self.idx, header.len)));
561        }
562
563        let term = find_nulnul(&self.buffer[(strings_idx as usize)..]);
564        let strings_len = match term {
565            Some(terminator) => (terminator + 1) as u32,
566            None => {
567                return Some(Err(MalformedStructureError::UnterminatedStrings(self.idx)));
568            }
569        };
570
571        let structure = RawStructure {
572            version: self.smbios_version,
573            info: header.kind.into(),
574            length: header.len,
575            handle: header.handle,
576            data: &self.buffer[(self.idx + mem::size_of::<HeaderPacked>() as u32) as usize..strings_idx as usize],
577            strings: &self.buffer[strings_idx as usize..(strings_idx + strings_len) as usize],
578        };
579
580        self.idx = strings_idx + strings_len;
581
582        Some(Ok(structure))
583    }
584}
585
586#[doc(hidden)]
587#[repr(C)]
588#[repr(packed)]
589struct HeaderPacked {
590    kind: u8,
591    len: u8,
592    handle: u16,
593}
594
595/// The raw SMBIOS structure information for structures that are not handled by this crate, such as Oem structures.
596#[derive(Clone, Debug, Eq, Hash, PartialEq)]
597pub struct RawStructure<'buffer> {
598    pub version: SmbiosVersion,
599    pub info: InfoType,
600    pub length: u8,
601    pub handle: u16,
602    pub data: &'buffer [u8],
603    strings: &'buffer [u8],
604}
605
606/// General trait for slice -> unsigned conversion
607pub trait TryFromBytes<'a, T>: Sized {
608    fn try_from_bytes(_: &'a [u8]) -> Result<Self, TryFromSliceError>;
609}
610
611impl<'a> TryFromBytes<'a, u8> for u8 {
612    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
613        bytes.try_into().map(u8::from_le_bytes)
614    }
615}
616impl<'a> TryFromBytes<'a, u16> for u16 {
617    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
618        bytes.try_into().map(u16::from_le_bytes)
619    }
620}
621impl<'a> TryFromBytes<'a, u32> for u32 {
622    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
623        bytes.try_into().map(u32::from_le_bytes)
624    }
625}
626impl<'a> TryFromBytes<'a, u64> for u64 {
627    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
628        bytes.try_into().map(u64::from_le_bytes)
629    }
630}
631impl<'a> TryFromBytes<'a, u128> for u128 {
632    fn try_from_bytes(bytes: &'a [u8]) -> Result<Self, TryFromSliceError> {
633        bytes.try_into().map(u128::from_le_bytes)
634    }
635}
636
637impl<'buffer> RawStructure<'buffer> {
638    /// Return an iterator over the strings in the strings table.
639    fn strings(&self) -> StructureStrings<'buffer> {
640        StructureStrings::new(self.strings)
641    }
642
643    /// Find a string in the strings table by the string index.
644    /// If the string index is 0, the empty string is returned. Otherwise, the string corresponding
645    /// to that string index in the strings table is returned.
646    ///
647    /// # Errors
648    /// Returns a `MalformedStructureError::InvalidStringIndex` if the index is outside of the strings table.
649    pub fn find_string(&self, idx: u8) -> Result<&'buffer str, MalformedStructureError> {
650        if idx == 0 {
651            Ok("")
652        } else {
653            self.strings()
654                .nth((idx - 1) as usize)
655                .ok_or(MalformedStructureError::InvalidStringIndex(self.info, self.handle, idx))
656        }
657    }
658    /// Get value by offset declared in SMBIOS Reference Specification.\
659    /// Type meaning data length is mandatory:
660    /// - *BYTE*: u8
661    /// - *WORD*: u16
662    /// - *DWORD*: u32
663    /// - *QWORD*: u64
664    ///
665    /// The only error this method returned: [MalformedStructureError::InvalidSlice] (actually is
666    /// [core::array::TryFromSliceError]). If getting value index exceedes length of *Formatted
667    /// section* it may be ignored to return [None] value of structure field. In this case *Formatted
668    /// section* length automatically hide non-existing values
669    pub fn get<T: TryFromBytes<'buffer, T>>(&self, offset: usize) -> Result<T, MalformedStructureError> {
670        // Ignore header
671        let start = offset - 4;
672        let size = core::mem::size_of::<T>();
673        let slice = self.data.get(start..(start + size)).unwrap_or(&[]);
674        TryFromBytes::try_from_bytes(slice).map_err(MalformedStructureError::InvalidSlice)
675    }
676    /// Wrapper to self.data.get(..) with header offset correction
677    pub fn get_slice(&self, offset: usize, size: usize) -> Option<&'buffer [u8]> {
678        self.data.get(offset - 4..offset - 4 + size)
679    }
680    /// Get *STRING* by offset declared in SMBIOS Reference Specification
681    pub fn get_string(&self, offset: usize) -> Result<&'buffer str, MalformedStructureError> {
682        self.get::<u8>(offset).and_then(|idx| self.find_string(idx))
683    }
684}
685
686/// An iterator over structure strings
687#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
688pub struct StructureStrings<'a> {
689    bytes: &'a [u8],
690    start: usize,
691}
692
693impl<'a> StructureStrings<'a> {
694    fn new(bytes: &'a [u8]) -> Self {
695        Self { bytes, start: 0 }
696    }
697}
698impl<'a> Iterator for StructureStrings<'a> {
699    type Item = &'a str;
700
701    fn next(&mut self) -> Option<Self::Item> {
702        let slice = self
703            .bytes
704            .get(self.start..)?
705            .split(|elm| *elm == 0)
706            .nth(0)
707            .filter(|slice| !slice.is_empty())?;
708        self.start += slice.len() + 1;
709        str::from_utf8(slice).ok()
710    }
711}
712
713/// SMBIOS Table information variant
714#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
715pub enum InfoType {
716    Bios,
717    System,
718    BaseBoard,
719    Enclosure,
720    Processor,
721    Cache,
722    PortConnector,
723    SystemSlots,
724    OemStrings,
725    SystemConfigurationOptions,
726    GroupAssociations,
727    SystemEventLog,
728    BiosLanguage,
729    PhysicalMemoryArray,
730    MemoryDevice,
731    MemoryError32,
732    MemoryArrayMappedAddress,
733    MemoryDeviceMappedAddress,
734    BuiltInPointingDevice,
735    PortableBattery,
736    SystemBoot,
737    Oem(u8),
738    End,
739}
740
741impl From<u8> for InfoType {
742    fn from(kind: u8) -> InfoType {
743        match kind {
744            0 => InfoType::Bios,
745            1 => InfoType::System,
746            2 => InfoType::BaseBoard,
747            3 => InfoType::Enclosure,
748            4 => InfoType::Processor,
749            7 => InfoType::Cache,
750            8 => InfoType::PortConnector,
751            9 => InfoType::SystemSlots,
752            11 => InfoType::OemStrings,
753            12 => InfoType::SystemConfigurationOptions,
754            13 => InfoType::BiosLanguage,
755            14 => InfoType::GroupAssociations,
756            15 => InfoType::SystemEventLog,
757            16 => InfoType::PhysicalMemoryArray,
758            17 => InfoType::MemoryDevice,
759            18 => InfoType::MemoryError32,
760            19 => InfoType::MemoryArrayMappedAddress,
761            20 => InfoType::MemoryDeviceMappedAddress,
762            21 => InfoType::BuiltInPointingDevice,
763            22 => InfoType::PortableBattery,
764            32 => InfoType::SystemBoot,
765            127 => InfoType::End,
766            t => InfoType::Oem(t),
767        }
768    }
769}
770impl fmt::Display for InfoType {
771    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
772        match self {
773            InfoType::Bios => write!(f, "BIOS Information"),
774            InfoType::System => write!(f, "System Information"),
775            InfoType::BaseBoard => write!(f, "Baseboard (or Module) Information"),
776            InfoType::Enclosure => write!(f, "System Enclosure or Chassis"),
777            InfoType::Processor => write!(f, "Processor Information"),
778            //InfoType::                          => write!(f, "Memory Controller Information"),
779            //InfoType::                          => write!(f, "Memory Module Information"),
780            InfoType::Cache => write!(f, "Cache Information"),
781            InfoType::PortConnector => write!(f, "Port Connector Information"),
782            InfoType::SystemSlots => write!(f, "System Slots"),
783            //InfoType::                          => write!(f, "On Board Devices Information"),
784            InfoType::OemStrings => write!(f, "OEM Strings"),
785            InfoType::SystemConfigurationOptions => write!(f, "System Configuration Options"),
786            InfoType::BiosLanguage => write!(f, "BIOS Language Information"),
787            InfoType::GroupAssociations => write!(f, "Group Associations"),
788            InfoType::SystemEventLog => write!(f, "System Event Log"),
789            InfoType::PhysicalMemoryArray => write!(f, "Physical Memory Array"),
790            InfoType::MemoryDevice => write!(f, "Memory Device"),
791            InfoType::MemoryError32 => write!(f, "32-Bit Memory Error Information"),
792            InfoType::MemoryArrayMappedAddress => write!(f, "Memory Array Mapped Address"),
793            InfoType::MemoryDeviceMappedAddress => write!(f, "Memory Device Mapped Address"),
794            InfoType::BuiltInPointingDevice => write!(f, "Built-in Pointing Device"),
795            InfoType::PortableBattery => write!(f, "Portable Battery"),
796            //InfoType::                          => write!(f, "System Reset"),
797            //InfoType::                          => write!(f, "Hardware Security"),
798            //InfoType::                          => write!(f, "System Power Controls"),
799            //InfoType::                          => write!(f, "Voltage Probe"),
800            //InfoType::                          => write!(f, "Cooling Device"),
801            //InfoType::                          => write!(f, "Temperature Probe"),
802            //InfoType::                          => write!(f, "Electrical Current Probe"),
803            //InfoType::                          => write!(f, "Out-of-Band Remote Access"),
804            //InfoType::                          => write!(f, "Boot Integrity Services (BIS) Entry Point"),
805            InfoType::SystemBoot => write!(f, "System Boot Information"),
806            //InfoType::                          => write!(f, "64-Bit Memory Error Information"),
807            //InfoType::                          => write!(f, "Management Device"),
808            //InfoType::                          => write!(f, "Management Device Component"),
809            //InfoType::                          => write!(f, "Management Device Threshold Data"),
810            //InfoType::                          => write!(f, "Memory Channel"),
811            //InfoType::                          => write!(f, "IPMI Device Information"),
812            //InfoType::                          => write!(f, "System Power Supply"),
813            //InfoType::                          => write!(f, "Additional Information"),
814            //InfoType::                          => write!(f, "Onboard Devices Extended Information"),
815            //InfoType::                          => write!(f, "Management Controller Host Interface"),
816            //InfoType::                          => write!(f, "TPM Device"),
817            //InfoType::                          => write!(f, "Processor Additional Information"),
818            //InfoType::                          => write!(f, "Inactive"),
819            InfoType::End => write!(f, "End-of-Table"),
820            InfoType::Oem(t) => write!(f, "OEM: {}", t),
821        }
822    }
823}
824
825#[cfg(test)]
826mod tests {
827    use super::*;
828
829    const DMIDECODE_BIN: &[u8] = include_bytes!("../tests/data/dmidecode.bin");
830    const ENTRY_V2_BIN: &[u8] = include_bytes!("../tests/data/entry.bin");
831    const DMI_V2_BIN: &[u8] = include_bytes!("../tests/data/dmi.bin");
832    const ENTRY_V3_BIN: &[u8] = include_bytes!("../tests/data/entry_v3.bin");
833    const DMI_V3_BIN: &[u8] = include_bytes!("../tests/data/dmi_v3.bin");
834    const DMI_V3_SHORT: &[u8] = include_bytes!("../tests/data/dmi_v3_short.bin");
835    const ENTRY_V3_SHORT: &[u8] = include_bytes!("../tests/data/entry_v3_short.bin");
836
837    #[test]
838    fn found_smbios_entry() {
839        EntryPoint::search(ENTRY_V2_BIN).unwrap();
840        EntryPoint::search(DMIDECODE_BIN).unwrap();
841    }
842
843    #[test]
844    fn found_smbios_entry_v3() {
845        EntryPoint::search(ENTRY_V3_BIN).unwrap();
846    }
847
848    #[test]
849    #[should_panic]
850    fn doesnt_find_smbios_entry() {
851        EntryPoint::search(DMI_V2_BIN).unwrap();
852    }
853
854    #[test]
855    fn found_signature() {
856        find_signature(ENTRY_V2_BIN).unwrap();
857        find_signature(ENTRY_V3_BIN).unwrap();
858        find_signature(DMIDECODE_BIN).unwrap();
859    }
860
861    #[test]
862    #[should_panic]
863    fn doesnt_find_signature() {
864        find_signature(DMI_V2_BIN).unwrap();
865        find_signature(DMI_V3_BIN).unwrap();
866    }
867
868    #[test]
869    fn iterator_through_structures() {
870        let entry_point = EntryPoint::search(DMIDECODE_BIN).unwrap();
871        for s in entry_point
872            .structures(&DMIDECODE_BIN[(entry_point.smbios_address() as usize)..])
873            .filter_map(|s| s.ok())
874        {
875            println!("{:?}", s);
876        }
877    }
878
879    #[test]
880    fn iterator_through_structures_v3_short() {
881        let entry_point = EntryPoint::search(ENTRY_V3_SHORT).unwrap();
882        for s in entry_point.structures(DMI_V3_SHORT).filter_map(|s| s.ok()) {
883            println!("{:?}", s);
884        }
885    }
886
887    #[test]
888    fn iterator_through_structures_v3() {
889        let entry_point = EntryPoint::search(ENTRY_V3_BIN).unwrap();
890        for s in entry_point.structures(DMI_V3_BIN).filter_map(|s| s.ok()) {
891            println!("{:?}", s);
892        }
893    }
894
895    #[test]
896    fn find_nulnul_empty() {
897        let buf = [];
898        assert_eq!(find_nulnul(&buf), None);
899    }
900
901    #[test]
902    fn find_nulnul_single_char() {
903        let buf = [0];
904        assert_eq!(find_nulnul(&buf), None);
905    }
906
907    #[test]
908    fn find_nulnul_trivial() {
909        let buf = [0, 0];
910        assert_eq!(find_nulnul(&buf), Some(1));
911    }
912
913    #[test]
914    fn find_nulnul_with_data() {
915        let buf = [1, 2, 3, 4, 0, 5, 4, 3, 2, 1, 0, 0];
916        assert_eq!(find_nulnul(&buf), Some(11));
917    }
918
919    #[test]
920    fn find_nulnul_with_data_more_at_end() {
921        let buf = [1, 2, 3, 4, 0, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3];
922        assert_eq!(find_nulnul(&buf), Some(11));
923    }
924
925    #[test]
926    fn structure_strings() {
927        use pretty_assertions::assert_eq;
928        use std::prelude::v1::*;
929
930        let regular_bytes = &[65, 66, 67, 0, 68, 69, 0, 70, 0, 71, 72, 73, 0, 0];
931        let regular_ss = StructureStrings::new(regular_bytes).collect::<Vec<_>>();
932        assert_eq!(vec!["ABC", "DE", "F", "GHI"], regular_ss, "Regular bytes");
933
934        let zero_bytes = &[0, 0];
935        let zero_ss = StructureStrings::new(zero_bytes).collect::<Vec<_>>();
936        assert_eq!(vec![""; 0], zero_ss, "Zero bytes");
937
938        let no_tail_bytes = &[65, 66, 67, 0, 68, 69, 0, 70, 0, 71, 72, 73];
939        let no_tail_ss = StructureStrings::new(no_tail_bytes).collect::<Vec<_>>();
940        assert_eq!(vec!["ABC", "DE", "F", "GHI"], no_tail_ss, "Regular bytes");
941
942        let invalid_order1_bytes = &[65, 66, 67, 0, 0, 68, 69, 0, 0, 0, 0, 0];
943        let invalid_order1_ss = StructureStrings::new(invalid_order1_bytes).collect::<Vec<_>>();
944        assert_eq!(vec!["ABC"], invalid_order1_ss, "Invalid order 1 bytes");
945
946        let invalid_order2_bytes = &[0, 0, 65, 66, 67];
947        let invalid_order2_ss = StructureStrings::new(invalid_order2_bytes).collect::<Vec<&str>>();
948        assert_eq!(vec![""; 0], invalid_order2_ss, "Invalid order 2 bytes");
949    }
950}