Skip to main content

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