smbioslib/core/
undefined_struct.rs

1use super::header::{Handle, Header};
2use super::strings::*;
3use crate::structs::{DefinedStruct, SMBiosEndOfTable, SMBiosStruct};
4use serde::{Serialize, Serializer};
5use std::fmt;
6use std::{
7    convert::TryInto,
8    fs::File,
9    io::{prelude::*, Error, ErrorKind, SeekFrom},
10    slice::Iter,
11};
12/// # Embodies the three basic parts of an SMBIOS structure
13///
14/// Every SMBIOS structure contains three distinct sections:
15/// - A header
16/// - A formatted structure of fields (offsets and widths)
17/// - String data
18///
19/// A consumer of BIOS data ultimately wants to work with a [DefinedStruct].
20/// [UndefinedStruct] provides a set of fields and functions that enables
21/// downcasting to a [DefinedStruct].  Further, the OEM is allowed to define
22/// their own structures and in such cases working with UndefinedStruct is
23/// necessary.  Therefore, [UndefinedStruct] is public for the case of OEM,
24/// as well as when working with structures that are defined in an SMBIOS
25/// standard newer than the one this library currently supports.
26#[derive(Serialize)]
27pub struct UndefinedStruct {
28    /// The [Header] of the structure
29    pub header: Header,
30
31    /// The raw data for the header and fields
32    ///
33    /// `fields` is used by the `get_field_*()` functions. `fields` does not
34    /// include _strings_; therefore, preventing accidentally retrieving
35    /// data from the _strings_ area.  This avoids a need to check
36    /// `header.length()` during field retrieval.
37    ///
38    /// Note: A better design is for this to only hold the fields, however,
39    /// that will shift field offsets given in code by 4 (the header size).
40    /// The SMBIOS specification gives offsets relative to the start of the
41    /// header, and therefore maintaining this library code is easier to
42    /// keep the header.
43    ///
44    /// An alternative would be to make the `get_field_*()` functions adjust
45    /// for the header offset though this adds a small cost to every field
46    /// retrieval in comparison to just keeping an extra 4 bytes for every
47    /// structure.
48    pub fields: Vec<u8>,
49
50    /// The strings of the structure
51    #[serde(serialize_with = "ser_strings")]
52    pub strings: SMBiosStringSet,
53}
54
55fn ser_strings<S>(data: &SMBiosStringSet, serializer: S) -> Result<S::Ok, S::Error>
56where
57    S: Serializer,
58{
59    serializer.serialize_str(format!("{:?}", data).as_str())
60}
61
62impl<'a> UndefinedStruct {
63    /// Creates a structure instance of the given byte array slice
64    pub fn new(raw: &Vec<u8>) -> Self {
65        match raw.get(Header::LENGTH_OFFSET) {
66            Some(&header_length) => UndefinedStruct {
67                header: Header::new(raw[..Header::SIZE].try_into().expect("4 bytes")),
68                fields: raw.get(..(header_length as usize)).unwrap_or(&[]).to_vec(),
69                strings: {
70                    SMBiosStringSet::new(
71                        raw.get((header_length as usize)..raw.len() - 2)
72                            .unwrap_or(&[])
73                            .to_vec(),
74                    )
75                },
76            },
77            None => UndefinedStruct {
78                ..Default::default()
79            },
80        }
81    }
82
83    /// Retrieve a byte at the given offset from the structure's data section
84    pub fn get_field_byte(&self, offset: usize) -> Option<u8> {
85        match self.fields.get(offset..offset + 1) {
86            Some(val) => Some(val[0]),
87            None => None,
88        }
89    }
90
91    /// Retrieve a WORD at the given offset from the structure's data section
92    pub fn get_field_word(&self, offset: usize) -> Option<u16> {
93        match self.fields.get(offset..offset + 2) {
94            Some(val) => Some(u16::from_le_bytes(val.try_into().expect("u16 is 2 bytes"))),
95            None => None,
96        }
97    }
98
99    /// Retrieve a [Handle] at the given offset from the structure's data section
100    pub fn get_field_handle(&self, offset: usize) -> Option<Handle> {
101        match self.fields.get(offset..offset + Handle::SIZE) {
102            Some(val) => Some(Handle(u16::from_le_bytes(
103                val.try_into().expect("u16 is 2 bytes"),
104            ))),
105            None => None,
106        }
107    }
108
109    /// Retrieve a DWORD at the given offset from the structure's data section
110    pub fn get_field_dword(&self, offset: usize) -> Option<u32> {
111        match self.fields.get(offset..offset + 4) {
112            Some(val) => Some(u32::from_le_bytes(val.try_into().expect("u32 is 4 bytes"))),
113            None => None,
114        }
115    }
116
117    /// Retrieve a QWORD at the given offset from the structure's data section
118    pub fn get_field_qword(&self, offset: usize) -> Option<u64> {
119        match self.fields.get(offset..offset + 8) {
120            Some(val) => Some(u64::from_le_bytes(val.try_into().expect("u64 is 8 bytes"))),
121            None => None,
122        }
123    }
124
125    /// Retrieve a String of the given offset
126    ///
127    /// Retrieval of strings is a two part operation. The given offset
128    /// contains a byte whose value is a 1 based index into the strings section.
129    /// The string is thus retrieved from the strings section based on the
130    /// byte value at the given offset.
131    pub fn get_field_string(&self, offset: usize) -> SMBiosString {
132        match self.get_field_byte(offset) {
133            Some(val) => self.strings.get_string(val),
134            None => Err(SMBiosStringError::FieldOutOfBounds).into(),
135        }
136    }
137
138    // todo: learn how to pass an index range (SliceIndex?) rather than start/end indices.
139    // This would better conform to the Rust design look and feel.
140
141    /// Retrieve a block of bytes from the structure's data section
142    pub fn get_field_data(&self, start_index: usize, end_index: usize) -> Option<&[u8]> {
143        return self.fields.get(start_index..end_index);
144    }
145
146    /// Cast to a given structure
147    ///
148    /// When this library does not contain a [DefinedStruct] variant
149    /// matching the SMBiosStruct::STRUCT_TYPE, this function affords a cast to the
150    /// given type. Such would be the case with OEM structure type T
151    /// (which implements the [SMBiosStruct] trait).
152    ///
153    /// TODO: This should panic (not be Option) when the STRUCT_TYPE does not match because
154    /// this would be a logic error in code, not a runtime error.
155    ///
156    /// Make this a "try_into"
157    pub fn as_type<T: SMBiosStruct<'a>>(&'a self) -> Option<T> {
158        if T::STRUCT_TYPE == self.header.struct_type() {
159            Some(T::new(self))
160        } else {
161            None
162        }
163    }
164
165    /// Down casts the current structure to its specific defined BIOS structure type
166    pub fn defined_struct(&self) -> DefinedStruct<'_> {
167        self.into()
168    }
169}
170
171impl fmt::Debug for UndefinedStruct {
172    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
173        let fields = &self.fields[Header::SIZE..];
174        fmt.debug_struct(std::any::type_name::<UndefinedStruct>())
175            .field("header", &self.header)
176            .field("fields", &fields)
177            .field("strings", &self.strings)
178            .finish()
179    }
180}
181
182impl Default for UndefinedStruct {
183    fn default() -> Self {
184        let v: [u8; 4] = [0; 4];
185        UndefinedStruct {
186            header: Header::new(v),
187            fields: (&[]).to_vec(),
188            strings: { SMBiosStringSet::new((&[]).to_vec()) },
189        }
190    }
191}
192
193/// # Undefined Struct Table
194///
195/// A collection of [UndefinedStruct] items.
196#[derive(Debug, Serialize)]
197pub struct UndefinedStructTable(Vec<UndefinedStruct>);
198
199impl<'a> UndefinedStructTable {
200    fn new() -> UndefinedStructTable {
201        UndefinedStructTable(Vec::new())
202    }
203
204    fn add(&mut self, elem: UndefinedStruct) {
205        self.0.push(elem);
206    }
207
208    /// Iterator of the contained [UndefinedStruct] items.
209    pub fn iter(&self) -> Iter<'_, UndefinedStruct> {
210        self.0.iter()
211    }
212
213    /// An iterator over the defined type instances within the table.
214    pub fn defined_struct_iter<T>(&'a self) -> impl Iterator<Item = T> + 'a
215    where
216        T: SMBiosStruct<'a>,
217    {
218        self.iter()
219            .take_while(|undefined_struct| {
220                undefined_struct.header.struct_type() != SMBiosEndOfTable::STRUCT_TYPE
221            })
222            .filter_map(|undefined_struct| {
223                if undefined_struct.header.struct_type() == T::STRUCT_TYPE {
224                    Some(T::new(undefined_struct))
225                } else {
226                    None
227                }
228            })
229    }
230
231    /// Tests if every element of the defined struct iterator matches a predicate.
232    pub fn all<T, F>(&'a self, f: F) -> bool
233    where
234        T: SMBiosStruct<'a>,
235        F: FnMut(T) -> bool,
236    {
237        self.defined_struct_iter().all(f)
238    }
239
240    /// Tests if any element of the defined struct iterator matches a predicate.
241    pub fn any<T, F>(&'a self, f: F) -> bool
242    where
243        T: SMBiosStruct<'a>,
244        F: FnMut(T) -> bool,
245    {
246        self.defined_struct_iter().any(f)
247    }
248
249    /// Finds the first occurance of the structure
250    pub fn first<T>(&'a self) -> Option<T>
251    where
252        T: SMBiosStruct<'a>,
253    {
254        self.defined_struct_iter().next()
255    }
256
257    /// Finds the first occurance of the structure that satisfies a predicate.
258    pub fn find<T, P>(&'a self, predicate: P) -> Option<T>
259    where
260        T: SMBiosStruct<'a>,
261        P: FnMut(&T) -> bool,
262    {
263        self.defined_struct_iter().find(predicate)
264    }
265
266    /// Applies function to the defined struct elements and returns the first non-none result.
267    pub fn find_map<A, B, F>(&'a self, f: F) -> Option<B>
268    where
269        A: SMBiosStruct<'a>,
270        F: FnMut(A) -> Option<B>,
271    {
272        self.defined_struct_iter().find_map(f)
273    }
274
275    /// Creates an iterator of the defined structure which uses a closure to determine if an element should be yielded.
276    pub fn filter<T: 'a, P: 'a>(&'a self, predicate: P) -> impl Iterator<Item = T> + 'a
277    where
278        T: SMBiosStruct<'a>,
279        P: FnMut(&T) -> bool,
280    {
281        self.defined_struct_iter().filter(predicate)
282    }
283
284    /// Maps the defined struct to another type given by the closure.
285    pub fn map<A: 'a, B, F: 'a>(&'a self, f: F) -> impl Iterator<Item = B> + 'a
286    where
287        A: SMBiosStruct<'a>,
288        F: FnMut(A) -> B,
289    {
290        self.defined_struct_iter().map(f)
291    }
292
293    /// Creates an iterator that both filters and maps from the defined struct iterator.
294    pub fn filter_map<A: 'a, B, F: 'a>(&'a self, f: F) -> impl Iterator<Item = B> + 'a
295    where
296        A: SMBiosStruct<'a>,
297        F: FnMut(A) -> Option<B>,
298    {
299        self.defined_struct_iter().filter_map(f)
300    }
301
302    /// Finds the structure matching the given handle
303    ///
304    /// To downcast to the defined struct, call .defined_struct() on the result.
305    pub fn find_by_handle(&'a self, handle: &Handle) -> Option<&'a UndefinedStruct> {
306        self.iter()
307            .find(|smbios_struct| smbios_struct.header.handle() == *handle)
308            .and_then(|undefined_struct| Some(undefined_struct))
309    }
310
311    /// Returns all occurances of the structure
312    pub fn collect<T>(&'a self) -> Vec<T>
313    where
314        T: SMBiosStruct<'a>,
315    {
316        self.defined_struct_iter().collect()
317    }
318
319    /// Load an [UndefinedStructTable] by seeking and reading the file offsets.
320    pub fn try_load_from_file_offset(
321        file: &mut File,
322        table_offset: u64,
323        table_len: usize,
324    ) -> Result<Self, Error> {
325        if table_len < Header::SIZE + 2 {
326            return Err(Error::new(
327                ErrorKind::InvalidData,
328                format!("The table has an invalid size: {}", table_len),
329            ));
330        }
331
332        file.seek(SeekFrom::Start(table_offset))?;
333        let mut table = Vec::with_capacity(table_len);
334        table.resize(table_len, 0);
335        file.read_exact(&mut table)?;
336        Ok(table.into())
337    }
338}
339
340impl From<Vec<u8>> for UndefinedStructTable {
341    fn from(data: Vec<u8>) -> Self {
342        const DOUBLE_ZERO_SIZE: usize = 2usize;
343        const MIN_STRUCT_SIZE: usize = Header::SIZE + DOUBLE_ZERO_SIZE;
344        let mut result = Self::new();
345        let mut current_index = 0usize;
346
347        loop {
348            // Is the next structure long enough?
349            match data.get(current_index..current_index + MIN_STRUCT_SIZE) {
350                Some(min_struct) => {
351                    // Read the structure's self-reported length in its header
352                    let struct_len = min_struct[Header::LENGTH_OFFSET] as usize;
353
354                    // Bad reported length
355                    if struct_len < Header::SIZE {
356                        break;
357                    }
358
359                    // Beyond the structure length are the structure's strings
360                    // Find the /0/0 which marks the end of this structure and the
361                    // beginning of the next.
362                    match data.get(current_index + struct_len..) {
363                        Some(strings_etc) => {
364                            match strings_etc
365                                .windows(DOUBLE_ZERO_SIZE)
366                                .position(|x| x[0] == x[1] && x[1] == 0)
367                            {
368                                Some(double_zero_position) => {
369                                    // The next structure will start at this index
370                                    let next_index = current_index
371                                        + struct_len
372                                        + double_zero_position
373                                        + DOUBLE_ZERO_SIZE;
374
375                                    // Copy the current structure to the collection
376                                    result.add(UndefinedStruct::new(
377                                        &data[current_index..next_index].to_vec(),
378                                    ));
379                                    current_index = next_index;
380                                }
381                                None => break,
382                            }
383                        }
384                        None => break,
385                    };
386                }
387                None => break,
388            }
389        }
390
391        result
392    }
393}
394
395impl IntoIterator for UndefinedStructTable {
396    type Item = UndefinedStruct;
397    type IntoIter = std::vec::IntoIter<Self::Item>;
398
399    fn into_iter(self) -> Self::IntoIter {
400        self.0.into_iter()
401    }
402}