attodmi 0.1.0

Extremely simple code for decoding type 1 and 2 DMI entries from /sys/firmware/dmi/entries
Documentation
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>
}

/// DMI type 1
#[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]);

// Assuming SMBIOS version >= 2.6
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(())
    }
}

/// DMI type 2
#[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))
	}
    }
}