use snafu::prelude::*;
use heterob::{endianness::Le, P5};
use crate::header::Header;
#[derive(Snafu, Debug, Clone, PartialEq, Eq)]
pub enum VendorSpecificError {
#[snafu(display("length byte is unreadable"))]
LengthUnreadable,
#[snafu(display("length should be > 2, not {val}"))]
Length { val: u8 },
#[snafu(display("unable to get {size} bytes data"))]
Data { size: usize },
#[snafu(display("Virtio size shold be > 12"))]
Virtio,
}
#[derive(Debug, PartialEq, Eq)]
pub enum VendorSpecific<'a> {
Unspecified(&'a [u8]),
Virtio(Virtio),
}
impl<'a> VendorSpecific<'a> {
pub fn try_new(slice: &'a [u8], header: &'a Header) -> Result<Self, VendorSpecificError> {
let size: usize = slice
.first()
.ok_or(VendorSpecificError::LengthUnreadable)
.and_then(|l| {
l.checked_sub(2)
.ok_or(VendorSpecificError::Length { val: *l })
})?
.into();
let slice = slice
.get(1..size)
.ok_or(VendorSpecificError::Data { size })?;
let result = match (header.vendor_id, header.device_id) {
(0x1af4, 0x1000..=0x107f) => slice.try_into().map(Self::Virtio)?,
_ => Self::Unspecified(slice),
};
Ok(result)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Virtio {
CommonCfg { bar: u8, offset: u32, size: u32 },
Notify {
bar: u8,
offset: u32,
size: u32,
multiplier: Option<u32>,
},
Isr { bar: u8, offset: u32, size: u32 },
DeviceCfg { bar: u8, offset: u32, size: u32 },
Unknown { bar: u8, offset: u32, size: u32 },
}
impl Virtio {
pub const SIZE: usize = 1 + 1 + 3 + 4 + 4; }
impl<'a> TryFrom<&'a [u8]> for Virtio {
type Error = VendorSpecificError;
fn try_from(slice: &'a [u8]) -> Result<Virtio, Self::Error> {
let bytes = slice.get(..Virtio::SIZE)
.and_then(|slice| <[u8;Virtio::SIZE]>::try_from(slice).ok())
.ok_or(VendorSpecificError::Virtio)?;
let Le((cfg_type, bar, padding, offset, size)) = P5(bytes).into();
let _: [u8;3] = padding;
let result = match cfg_type {
1u8 => Self::CommonCfg { bar, offset, size },
2 => {
let multiplier = slice.get(Virtio::SIZE .. Virtio::SIZE + 4)
.and_then(|slice| <[u8;4]>::try_from(slice).ok())
.map(u32::from_le_bytes);
Self::Notify { bar, offset, size, multiplier }
},
3 => Self::Isr { bar, offset, size },
4 => Self::DeviceCfg { bar, offset, size },
_ => Self::Unknown { bar, offset, size },
};
Ok(result)
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn virtio() {
let data = [
0x09, 0xa4, 0x14, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 ];
let vc: Virtio = data[3..].try_into().unwrap();
let sample = Virtio::Notify {
bar: 4, offset: 0x00003000, size: 0x00001000,
multiplier: Some(0x00000004)
};
assert_eq!(sample, vc);
}
}