use std::{
error::Error,
fmt::{
Display,
Formatter
},
fs::File,
io::Read,
path::Path,
};
#[derive(Debug)]
pub enum DmiError {
ShortTable,
ShortData,
UnhandledType(u8),
IoError(std::io::Error),
SliceError(std::array::TryFromSliceError)
}
impl Display for DmiError {
fn fmt(&self,f:&mut Formatter<'_>)->std::result::Result<(),std::fmt::Error> {
write!(f,"{:?}",self)
}
}
impl From<std::io::Error> for DmiError {
fn from(e:std::io::Error)->Self {
Self::IoError(e)
}
}
impl From<std::array::TryFromSliceError> for DmiError {
fn from(e:std::array::TryFromSliceError)->Self {
Self::SliceError(e)
}
}
impl Error for DmiError { }
pub type Result<T> = std::result::Result<T,DmiError>;
#[derive(Debug)]
pub struct DmiRawData {
pub dmi_type:u8,
pub length:u8,
pub handle:u16,
pub data:Vec<u8>
}
#[derive(Debug)]
pub struct DmiSystemInformation {
pub manufacturer:String,
pub product_name:String,
pub version:String,
pub serial_number:String,
pub uuid:Option<DmiUuid>,
pub sku:Option<String>,
pub family:Option<String>
}
pub trait DmiInstance : Sized
{
const TYPE_CODE : u8;
fn get_instance(instance:usize)->Result<Self> {
let raw = DmiRawData::from_sysfs(Self::TYPE_CODE,instance)?;
Self::try_from_raw(&raw)
}
fn try_from_raw(raw:&DmiRawData)->Result<Self>;
}
#[derive(Debug)]
pub struct DmiUuid(pub [u8;16]);
impl Display for DmiUuid {
fn fmt(&self,f:&mut Formatter<'_>)->std::result::Result<(),std::fmt::Error>
{
for j in [3,2,1,0,16,
5,4,16,
7,6,16,
8,9,16,
10,11,12,13,14,15] {
if j == 16 {
write!(f,"-")?;
} else {
write!(f,"{:02x}",self.0[j])?;
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct DmiBaseBoardInformation {
pub manufacturer:String,
pub product_name:String,
pub version:String,
pub serial_number:String,
pub asset_tag:Option<String>,
pub location_in_chassis:Option<String>
}
#[derive(Debug)]
pub struct DmiChassisInformation {
pub manufacturer:String,
pub version:String,
pub serial_number:String,
pub asset_tag:String,
}
#[derive(Debug)]
pub enum DmiEntry {
SI(DmiSystemInformation),
BBI(DmiBaseBoardInformation),
CI(DmiChassisInformation),
}
impl DmiRawData {
pub fn from_sysfs(dmi_type:u8,instance:usize)->Result<Self> {
Self::from_file(format!("/sys/firmware/dmi/entries/{}-{}/raw",
dmi_type,instance))
}
pub fn from_file<P:AsRef<Path>>(path:P)->Result<Self> {
let fd = File::open(path)?;
Self::from_reader(fd)
}
pub fn from_reader<R:Read>(mut r:R)->Result<Self> {
let mut data = Vec::new();
let _ = r.read_to_end(&mut data)?;
let dmi_type = data[0];
let length = data[1];
let handle = u16::from_le_bytes([data[2],data[3]]);
Ok(Self {
dmi_type,
length,
handle,
data
})
}
fn maybe_data(&self,id:usize)->Option<u8> {
if id < self.length as usize {
Some(self.data[id])
} else {
None
}
}
fn data(&self,id:usize)->Result<u8> {
self.maybe_data(id)
.ok_or(DmiError::ShortTable)
}
fn string(&self,id:usize)->Result<String> {
let index = self.data(id)?;
self.get_string(index)
}
fn maybe_string(&self,id:usize)->Result<Option<String>> {
if let Some(index) = self.maybe_data(id) {
Ok(Some(self.get_string(index)?))
} else {
Ok(None)
}
}
fn uuid(&self,offset:usize)->Result<DmiUuid> {
let d = &self.data;
let n = d.len();
if offset + 16 < n {
Ok(DmiUuid(d[offset .. offset + 16].try_into()?))
} else {
Err(DmiError::ShortData)
}
}
fn get_string(&self,mut index:u8)->Result<String> {
let d = &self.data;
let mut i = self.length as usize;
let n = d.len();
while index > 1 {
loop {
if i >= n {
return Err(DmiError::ShortData);
}
if d[i] == 0 {
i += 1;
break;
}
i += 1;
}
index -= 1;
}
let mut out = String::new();
loop {
if i + 1 >= n {
break;
}
let c = d[i];
match c {
0 => break,
32 .. 127 => {
if let Some(c) = char::from_u32(c as u32) {
out.push(c);
}
},
_ => out.push('.')
}
i += 1;
}
Ok(out)
}
}
impl DmiInstance for DmiSystemInformation {
const TYPE_CODE : u8 = 0x01;
fn try_from_raw(raw:&DmiRawData)->Result<Self> {
let manufacturer = raw.string(0x04)?;
let product_name = raw.string(0x05)?;
let version = raw.string(0x06)?;
let serial_number = raw.string(0x07)?;
let uuid =
if raw.length < 0x19 {
None
} else {
Some(raw.uuid(8)?)
};
let sku = raw.maybe_string(0x19)?;
let family = raw.maybe_string(0x1a)?;
Ok(DmiSystemInformation {
manufacturer,
product_name,
version,
serial_number,
uuid,
sku,
family
})
}
}
impl DmiInstance for DmiBaseBoardInformation {
const TYPE_CODE : u8 = 0x02;
fn try_from_raw(raw:&DmiRawData)->Result<Self> {
let manufacturer = raw.string(0x04)?;
let product_name = raw.string(0x05)?;
let version = raw.string(0x06)?;
let serial_number = raw.string(0x07)?;
let asset_tag = raw.maybe_string(0x08)?;
let location_in_chassis = raw.maybe_string(0x0a)?;
Ok(DmiBaseBoardInformation {
manufacturer,
product_name,
version,
serial_number,
asset_tag,
location_in_chassis
})
}
}
impl DmiInstance for DmiChassisInformation {
const TYPE_CODE : u8 = 0x03;
fn try_from_raw(raw:&DmiRawData)->Result<Self> {
let manufacturer = raw.string(0x04)?;
let version = raw.string(0x06)?;
let serial_number = raw.string(0x07)?;
let asset_tag = raw.string(0x08)?;
Ok(DmiChassisInformation {
manufacturer,
version,
serial_number,
asset_tag
})
}
}
impl DmiEntry
{
pub fn try_from_raw(raw:&DmiRawData)->Result<Self> {
match raw.dmi_type {
1 => Ok(DmiEntry::SI(DmiSystemInformation::try_from_raw(raw)?)),
2 => Ok(DmiEntry::BBI(DmiBaseBoardInformation::try_from_raw(raw)?)),
3 => Ok(DmiEntry::CI(DmiChassisInformation::try_from_raw(raw)?)),
t => Err(DmiError::UnhandledType(t))
}
}
}