use std::{
error::Error,
fmt::{
Display,
Formatter
},
io::Read,
};
#[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>
}
#[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 enum DmiEntry {
SI(DmiSystemInformation),
BBI(DmiBaseBoardInformation)
}
impl DmiRawData {
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
})
}
pub fn maybe_data(&self,id:usize)->Option<u8> {
if id < self.length as usize {
Some(self.data[id])
} else {
None
}
}
pub fn data(&self,id:usize)->Result<u8> {
self.maybe_data(id)
.ok_or(DmiError::ShortTable)
}
pub fn string(&self,id:usize)->Result<String> {
let index = self.data(id)?;
self.get_string(index)
}
pub 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 TryFrom<&DmiRawData> for DmiSystemInformation {
type Error = DmiError;
fn try_from(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 TryFrom<&DmiRawData> for DmiBaseBoardInformation {
type Error = DmiError;
fn try_from(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 TryFrom<&DmiRawData> for DmiEntry
{
type Error = DmiError;
fn try_from(raw:&DmiRawData)->Result<Self> {
match raw.dmi_type {
1 => Ok(DmiEntry::SI(DmiSystemInformation::try_from(raw)?)),
2 => Ok(DmiEntry::BBI(DmiBaseBoardInformation::try_from(raw)?)),
t => Err(DmiError::UnhandledType(t))
}
}
}