use std::fs::{self, File};
use std::io;
use std::{iter, str};
use simple_bytes::{Bytes, BytesRead, BytesReadRef};
use memchr::memmem;
use uuid::Uuid;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
EntryPointNotFound,
AnchorStringIncorrect,
EntryPointMalformed,
StructuresNotFound,
StructuresMalformed
}
impl From<Error> for io::Error {
fn from(e: Error) -> Self {
let kind = match e {
Error::EntryPointNotFound |
Error::StructuresNotFound => io::ErrorKind::NotFound,
_ => io::ErrorKind::Other
};
Self::new(kind, format!("{:?}", e))
}
}
const ANCHOR_STRING: [u8; 5] = [0x5f, 0x53, 0x4d, 0x33, 0x5f];
const ENTRY_POINT_PATH: &str = "/sys/firmware/dmi/tables/smbios_entry_point";
const ENTRY_POINT_MIN_LEN: usize = 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 4 + 8;
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub struct EntryPoint {
pub checksum: u8,
pub len: u8,
pub major: u8,
pub minor: u8,
pub docrev: u8,
pub revision: u8,
pub reserved: u8,
pub table_max: u32,
pub table_addr: u64
}
macro_rules! structure_kind {
($($name:ident = $val:expr),*) => {
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub enum StructureKind {
$($name),*,
Unknown
}
impl StructureKind {
fn from_u8(num: u8) -> Self {
match num {
$($val => Self::$name),*,
_ => Self::Unknown
}
}
}
}
}
structure_kind! {
BiosInformation = 0,
SystemInformation = 1,
SystemEnclosure = 3,
ProcessorInformation = 4,
CacheInformation = 7,
SystemSlots = 9,
PhysicalMemoryArray = 16,
MemoryDevice = 17,
MemoryArrayMappedAddress = 19,
SystemBootInformation = 32
}
const STRUCTURE_HEADER_LEN: usize = 1 + 1 + 2;
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub struct StructureHeader {
pub kind: StructureKind, pub len: u8,
pub handle: u16
}
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub struct Structure<'a> {
pub header: StructureHeader,
pub formatted: &'a [u8],
pub strings: &'a [u8]
}
const STRUCTURES_PATH: &str = "/sys/firmware/dmi/tables/DMI";
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub struct Structures {
bytes: Vec<u8>
}
const BIOS_INFO_MIN_LEN: usize = 1 + 1 + 2 + 1 + 1 + 4 + 0 + 1 + 1 + 1 + 1;
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub struct BiosInformation<'a> {
pub vendor: u8,
pub version: u8,
pub starting_addr: u16,
pub release_date: u8,
pub rom_size: u8,
pub characteristics: u32,
pub characteristics_extension: &'a [u8],
pub major: u8,
pub minor: u8,
pub emc_major: u8,
pub emc_minor: u8
}
const SYSTEM_INFO_MIN_LEN: usize = 1 + 1 + 1 + 1 + 16 + 1 + 1 + 1;
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub struct SystemInformation {
pub manufacturer: u8,
pub product_name: u8,
pub version: u8,
pub serial_number: u8,
pub uuid: Uuid,
pub wake_up_kind: u8,
pub sku_number: u8,
pub family: u8
}
impl EntryPoint {
pub fn read() -> Result<Self> {
let mut buf = [0u8; ENTRY_POINT_MIN_LEN];
{
let mut file = File::open(ENTRY_POINT_PATH)
.map_err(|_| Error::EntryPointNotFound)?;
io::Read::read_exact(&mut file, &mut buf)
.map_err(|_| Error::EntryPointMalformed)?;
}
let mut bytes = Bytes::from(buf.as_ref());
if bytes.read(ANCHOR_STRING.len()) != ANCHOR_STRING {
return Err(Error::AnchorStringIncorrect)
}
Ok(EntryPoint {
checksum: bytes.read_le_u8(),
len: bytes.read_le_u8(),
major: bytes.read_le_u8(),
minor: bytes.read_le_u8(),
docrev: bytes.read_le_u8(),
revision: bytes.read_le_u8(),
reserved: bytes.read_le_u8(),
table_max: bytes.read_le_u32(),
table_addr: bytes.read_le_u64()
})
}
}
impl Structures {
pub fn read(table_max: u32) -> Result<Self> {
let buf = fs::read(STRUCTURES_PATH)
.map_err(|_| Error::StructuresNotFound)?;
if table_max != 0 && buf.len() > table_max as usize {
return Err(Error::StructuresMalformed)
}
Ok(Self { bytes: buf })
}
pub fn structures(&self) -> impl Iterator<Item=Structure> {
let mut bytes = Bytes::from(self.bytes.as_ref());
iter::from_fn(move || {
Structure::read(&mut bytes)
})
}
}
impl<'a> Structure<'a> {
fn read(reader: &mut impl BytesReadRef<'a>) -> Option<Self> {
if reader.remaining().len() < STRUCTURE_HEADER_LEN {
return None
}
let header = StructureHeader {
kind: StructureKind::from_u8(reader.read_le_u8()),
len: reader.read_le_u8(),
handle: reader.read_le_u16()
};
let formatted_len = (header.len as usize).max(STRUCTURE_HEADER_LEN)
- STRUCTURE_HEADER_LEN;
if reader.remaining_ref().len() < formatted_len + 2 {
return None
}
let formatted = reader.read_ref(formatted_len);
let end_pos = memmem::find(reader.remaining(), &[0u8, 0u8])?;
let strings = reader.read_ref(end_pos);
let _null_null = reader.read(2);
Some(Structure { header, formatted, strings })
}
}
impl<'a> Structure<'a> {
pub fn get_str(&self, num: u8) -> Option<&'a str> {
self.strings.split(|b| *b == 0)
.nth((num.max(1) as usize) - 1)
.map(str::from_utf8)?
.ok()
}
}
impl<'a> BiosInformation<'a> {
pub fn from(stru: &Structure<'a>) -> Option<Self> {
debug_assert_eq!(stru.header.kind, StructureKind::BiosInformation);
debug_assert_eq!(BIOS_INFO_MIN_LEN + STRUCTURE_HEADER_LEN, 0x12);
if (stru.header.len as usize) < BIOS_INFO_MIN_LEN + STRUCTURE_HEADER_LEN {
return None
}
let char_ext_len = stru.header.len - 0x12;
let mut bytes = Bytes::from(stru.formatted);
Some(Self {
vendor: bytes.read_le_u8(),
version: bytes.read_le_u8(),
starting_addr: bytes.read_le_u16(),
release_date: bytes.read_le_u8(),
rom_size: bytes.read_le_u8(),
characteristics: bytes.read_le_u32(),
characteristics_extension: bytes.read_ref(char_ext_len as usize),
major: bytes.read_le_u8(),
minor: bytes.read_le_u8(),
emc_major: bytes.read_le_u8(),
emc_minor: bytes.read_le_u8()
})
}
}
impl SystemInformation {
pub fn from(stru: &Structure) -> Option<Self> {
debug_assert_eq!(stru.header.kind, StructureKind::SystemInformation);
if (stru.header.len as usize) < SYSTEM_INFO_MIN_LEN
+ STRUCTURE_HEADER_LEN
{
return None
}
let mut bytes = Bytes::from(stru.formatted);
Some(Self {
manufacturer: bytes.read_le_u8(),
product_name: bytes.read_le_u8(),
version: bytes.read_le_u8(),
serial_number: bytes.read_le_u8(),
uuid: Uuid::from_fields_le(
bytes.read_le_u32().to_be(),
bytes.read_le_u16().to_be(),
bytes.read_le_u16().to_be(),
bytes.read(8)
).unwrap(),
wake_up_kind: bytes.read_le_u8(),
sku_number: bytes.read_le_u8(),
family: bytes.read_le_u8()
})
}
}