1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
use crate::{SMBiosStruct, UndefinedStruct};
use std::fmt;

/// # System Boot Information (Type 32)
///
/// The client system firmware (for example, BIOS) communicates the
/// System Boot Status to the client’s Pre1864 boot Execution Environment
/// (PXE) boot image or OS-present management application through this
/// structure.
///
/// Compliant with:
/// DMTF SMBIOS Reference Specification 3.4.0 (DSP0134)
/// Document Date: 2020-07-17
pub struct SMBiosSystemBootInformation<'a> {
    parts: &'a UndefinedStruct,
}

impl<'a> SMBiosStruct<'a> for SMBiosSystemBootInformation<'a> {
    const STRUCT_TYPE: u8 = 32u8;

    fn new(parts: &'a UndefinedStruct) -> Self {
        Self { parts }
    }

    fn parts(&self) -> &'a UndefinedStruct {
        self.parts
    }
}

impl<'a> SMBiosSystemBootInformation<'a> {
    /// offset of boot_status field
    const BOOT_STATUS_OFFSET: usize = 0x0A;

    /// Boot status can be a size from 1 to 10
    const BOOT_STATUS_MAX_SIZE: usize = 0x0A;

    /// Status and Additional Data fields that identify the boot status
    pub fn boot_status_data(&self) -> Option<SystemBootStatusData<'_>> {
        // boot_status is from 1 to 10 bytes in length.  The entire structure must be at least 0xB in length
        // and boot_status starts at offset 0xA;
        // meaning, at least 1 byte of boot_status exists, but not more than 10 bytes total.
        let struct_length = self.parts.header.length() as usize;
        if struct_length < Self::BOOT_STATUS_OFFSET + 1 {
            return None;
        }

        let end_index: usize;
        if struct_length < Self::BOOT_STATUS_OFFSET + Self::BOOT_STATUS_MAX_SIZE {
            end_index = struct_length;
        } else {
            end_index = Self::BOOT_STATUS_OFFSET + Self::BOOT_STATUS_MAX_SIZE;
        }

        self.parts
            .get_field_data(Self::BOOT_STATUS_OFFSET, end_index)
            .map(|raw| SystemBootStatusData { raw })
    }
}

impl fmt::Debug for SMBiosSystemBootInformation<'_> {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt.debug_struct(std::any::type_name::<SMBiosSystemBootInformation<'_>>())
            .field("header", &self.parts.header)
            .field("boot_status_data", &self.boot_status_data())
            .finish()
    }
}

/// # Boot Status data of [SMBiosSystemBootInformation]
pub struct SystemBootStatusData<'a> {
    /// Raw data
    pub raw: &'a [u8],
}

impl<'a> SystemBootStatusData<'a> {
    /// System boot status
    pub fn system_boot_status(&self) -> SystemBootStatus {
        debug_assert!(self.raw.len() > 0);
        match self.raw[0] {
            0x00 => SystemBootStatus::NoErrors,
            0x01 => SystemBootStatus::NoBootableMedia,
            0x02 => SystemBootStatus::NormalOSFailedToLoad,
            0x03 => SystemBootStatus::FirmwareDetectedFailure,
            0x04 => SystemBootStatus::OSDetectedFailure,
            0x05 => SystemBootStatus::UserRequestedBoot,
            0x06 => SystemBootStatus::SystemSecurityViolation,
            0x07 => SystemBootStatus::PreviouslyRequestedImage,
            0x08 => SystemBootStatus::SystemWatchdogTimerExpired,
            _ => SystemBootStatus::None,
        }
    }
}

impl fmt::Debug for SystemBootStatusData<'_> {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt.debug_struct(std::any::type_name::<SMBiosSystemBootInformation<'_>>())
            .field("system_boot_status", &self.system_boot_status())
            .finish()
    }
}

/// # System Boot Status
#[derive(Debug, PartialEq, Eq)]
pub enum SystemBootStatus {
    /// No errors detected
    NoErrors,
    /// No bootable media
    NoBootableMedia,
    /// “normal” operating system failed to load
    NormalOSFailedToLoad,
    /// Firmware-detected hardware failure, including “unknown” failure types
    FirmwareDetectedFailure,
    /// Operating system-detected hardware failure
    /// For ACPI operating systems, the system firmware might set this reason code
    /// when the OS reports a boot failure through interfaces defined in the Simple
    /// Boot Flag Specification.
    OSDetectedFailure,
    /// User-requested boot, usually through a keystroke
    UserRequestedBoot,
    /// System security violation
    SystemSecurityViolation,
    /// Previously-requested image
    /// This reason code allows coordination between OS-present software and the
    /// OS-absent environment. For example, an OS-present application might
    /// enable (through a platform-specific interface) the system to boot to the PXE
    /// and request a specific boot-image.
    PreviouslyRequestedImage,
    /// System watchdog timer expired, causing the system to reboot
    SystemWatchdogTimerExpired,
    /// A value unknown to this standard, check the raw value
    None,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn unit_test() {
        // test a normal structure with a 10 byte boot status field with "NoErrors"
        let struct_type32 = vec![
            0x20, 0x14, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ];

        let parts = UndefinedStruct::new(&struct_type32);
        let test_struct = SMBiosSystemBootInformation::new(&parts);

        let boot_status_data = test_struct.boot_status_data().unwrap();
        assert_eq!(
            boot_status_data.raw,
            &[0x00u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] as &[u8]
        );
        assert_eq!(
            boot_status_data.system_boot_status(),
            SystemBootStatus::NoErrors
        );

        // test a structure with a 2 byte boot status field with "NormalOSFailedToLoad"
        let struct_type32 = vec![
            0x20, 0x0C, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01,
        ];

        let parts = UndefinedStruct::new(&struct_type32);
        let test_struct = SMBiosSystemBootInformation::new(&parts);

        let boot_status_data = test_struct.boot_status_data().unwrap();
        assert_eq!(boot_status_data.raw, &[0x02u8, 0x01] as &[u8]);
        assert_eq!(
            boot_status_data.system_boot_status(),
            SystemBootStatus::NormalOSFailedToLoad
        );

        // test a structure with a 2 byte boot status field but an incorrect header length
        // extending beyond the end of the structure
        let struct_type32 = vec![
            0x20, 0x0F, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01,
        ];

        let parts = UndefinedStruct::new(&struct_type32);
        let test_struct = SMBiosSystemBootInformation::new(&parts);

        assert!(test_struct.boot_status_data().is_none());
    }
}