pub use crate::proto::device_path::device_path_gen::build::*;
use crate::polyfill::{maybe_uninit_slice_as_mut_ptr, maybe_uninit_slice_assume_init_ref};
use crate::proto::device_path::{DevicePath, DevicePathNode};
use core::fmt::{self, Display, Formatter};
use core::mem::MaybeUninit;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[derive(Debug)]
pub struct DevicePathBuilder<'a> {
storage: BuilderStorage<'a>,
}
impl<'a> DevicePathBuilder<'a> {
pub const fn with_buf(buf: &'a mut [MaybeUninit<u8>]) -> Self {
Self {
storage: BuilderStorage::Buf { buf, offset: 0 },
}
}
#[cfg(feature = "alloc")]
pub fn with_vec(v: &'a mut Vec<u8>) -> Self {
v.clear();
Self {
storage: BuilderStorage::Vec(v),
}
}
pub fn push(mut self, node: &dyn BuildNode) -> Result<Self, BuildError> {
let node_size = usize::from(node.size_in_bytes()?);
match &mut self.storage {
BuilderStorage::Buf { buf, offset } => {
node.write_data(
buf.get_mut(*offset..*offset + node_size)
.ok_or(BuildError::BufferTooSmall)?,
);
*offset += node_size;
}
#[cfg(feature = "alloc")]
BuilderStorage::Vec(vec) => {
let old_size = vec.len();
vec.reserve(node_size);
let buf = &mut vec.spare_capacity_mut()[..node_size];
node.write_data(buf);
unsafe {
vec.set_len(old_size + node_size);
}
}
}
Ok(self)
}
pub fn finalize(self) -> Result<&'a DevicePath, BuildError> {
let this = self.push(&end::Entire)?;
let data: &[u8] = match &this.storage {
BuilderStorage::Buf { buf, offset } => unsafe {
maybe_uninit_slice_assume_init_ref(&buf[..*offset])
},
#[cfg(feature = "alloc")]
BuilderStorage::Vec(vec) => vec,
};
let ptr: *const () = data.as_ptr().cast();
Ok(unsafe { &*ptr_meta::from_raw_parts(ptr, data.len()) })
}
}
#[derive(Debug)]
enum BuilderStorage<'a> {
Buf {
buf: &'a mut [MaybeUninit<u8>],
offset: usize,
},
#[cfg(feature = "alloc")]
Vec(&'a mut Vec<u8>),
}
#[derive(Clone, Copy, Debug)]
pub enum BuildError {
BufferTooSmall,
NodeTooBig,
UnexpectedEndEntire,
}
impl Display for BuildError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::BufferTooSmall => "a node was too big to fit in remaining buffer space",
Self::NodeTooBig => "a node was too big",
Self::UnexpectedEndEntire => "unexpected END_ENTIRE",
}
)
}
}
impl core::error::Error for BuildError {}
pub unsafe trait BuildNode {
fn size_in_bytes(&self) -> Result<u16, BuildError>;
fn write_data(&self, out: &mut [MaybeUninit<u8>]);
}
unsafe impl BuildNode for &DevicePathNode {
fn size_in_bytes(&self) -> Result<u16, BuildError> {
Ok(self.header.length())
}
fn write_data(&self, out: &mut [MaybeUninit<u8>]) {
let src: *const u8 = self.as_ffi_ptr().cast();
let dst: *mut u8 = maybe_uninit_slice_as_mut_ptr(out);
unsafe {
dst.copy_from_nonoverlapping(src, out.len());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::guid;
use crate::proto::device_path::media::{PartitionFormat, PartitionSignature};
use crate::proto::device_path::messaging::{
Ipv4AddressOrigin, IscsiLoginOptions, IscsiProtocol, RestServiceAccessMode, RestServiceType,
};
use core::slice;
const fn path_to_bytes(path: &DevicePath) -> &[u8] {
unsafe { slice::from_raw_parts(path.as_ffi_ptr().cast::<u8>(), size_of_val(path)) }
}
#[test]
fn test_acpi_adr() -> Result<(), BuildError> {
assert!(acpi::AdrSlice::new(&[]).is_none());
let mut v = Vec::new();
let path = DevicePathBuilder::with_vec(&mut v)
.push(&acpi::Adr {
adr: acpi::AdrSlice::new(&[1, 2]).unwrap(),
})?
.finalize()?;
let node: &crate::proto::device_path::acpi::Adr =
path.node_iter().next().unwrap().try_into().unwrap();
assert_eq!(node.adr().iter().collect::<Vec<_>>(), [1, 2]);
let bytes = path_to_bytes(path);
#[rustfmt::skip]
assert_eq!(bytes, [
0x02, 0x03, 0x0c, 0x00,
0x01, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00,
0x7f, 0xff, 0x04, 0x00,
]);
Ok(())
}
#[test]
fn test_acpi_expanded() -> Result<(), BuildError> {
let mut v = Vec::new();
let path = DevicePathBuilder::with_vec(&mut v)
.push(&acpi::Expanded {
hid: 1,
uid: 2,
cid: 3,
hid_str: b"a\0",
uid_str: b"bc\0",
cid_str: b"def\0",
})?
.finalize()?;
let node: &crate::proto::device_path::acpi::Expanded =
path.node_iter().next().unwrap().try_into().unwrap();
assert_eq!(node.hid(), 1);
assert_eq!(node.uid(), 2);
assert_eq!(node.cid(), 3);
assert_eq!(node.hid_str(), b"a\0");
assert_eq!(node.uid_str(), b"bc\0");
assert_eq!(node.cid_str(), b"def\0");
let bytes = path_to_bytes(path);
#[rustfmt::skip]
assert_eq!(bytes, [
0x02, 0x02, 0x19, 0x00,
0x01, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00,
0x61, 0x00,
0x62, 0x63, 0x00,
0x64, 0x65, 0x66, 0x00,
0x7f, 0xff, 0x04, 0x00,
]);
Ok(())
}
#[test]
fn test_messaging_rest_service() -> Result<(), BuildError> {
let mut v = Vec::new();
let vendor_guid = guid!("a1005a90-6591-4596-9bab-1c4249a6d4ff");
let path = DevicePathBuilder::with_vec(&mut v)
.push(&messaging::RestService {
service_type: RestServiceType::REDFISH,
access_mode: RestServiceAccessMode::IN_BAND,
vendor_guid_and_data: None,
})?
.push(&messaging::RestService {
service_type: RestServiceType::VENDOR,
access_mode: RestServiceAccessMode::OUT_OF_BAND,
vendor_guid_and_data: Some(messaging::RestServiceVendorData {
vendor_guid,
vendor_defined_data: &[1, 2, 3, 4, 5],
}),
})?
.finalize()?;
let mut iter = path.node_iter();
let mut node: &crate::proto::device_path::messaging::RestService =
iter.next().unwrap().try_into().unwrap();
assert!(node.vendor_guid_and_data().is_none());
node = iter.next().unwrap().try_into().unwrap();
assert_eq!(node.vendor_guid_and_data().unwrap().0, vendor_guid);
assert_eq!(node.vendor_guid_and_data().unwrap().1, &[1, 2, 3, 4, 5]);
let bytes = path_to_bytes(path);
#[rustfmt::skip]
assert_eq!(bytes, [
0x03, 0x21, 0x06, 0x00,
0x01, 0x01,
0x03, 0x21, 0x1b, 0x00,
0xff, 0x02,
0x90, 0x5a, 0x00, 0xa1,
0x91, 0x65, 0x96, 0x45,
0x9b, 0xab, 0x1c, 0x42,
0x49, 0xa6, 0xd4, 0xff,
0x01, 0x02, 0x03, 0x04, 0x05,
0x7f, 0xff, 0x04, 0x00,
]);
Ok(())
}
#[test]
fn test_build_with_packed_node() -> Result<(), BuildError> {
let mut v = Vec::new();
let path1 = DevicePathBuilder::with_vec(&mut v)
.push(&acpi::Acpi {
hid: 0x41d0_0a03,
uid: 0x0000_0000,
})?
.push(&hardware::Vendor {
vendor_guid: guid!("15e39a00-1dd2-1000-8d7f-00a0c92408fc"),
vendor_defined_data: &[1, 2, 3, 4, 5, 6],
})?
.finalize()?;
let mut v = Vec::new();
let mut builder = DevicePathBuilder::with_vec(&mut v);
for node in path1.node_iter() {
builder = builder.push(&node)?;
}
let path2 = builder.finalize()?;
assert_eq!(path1, path2);
Ok(())
}
#[test]
fn test_fibre_channel_ex_device_path_example() -> Result<(), BuildError> {
let mut buf = [MaybeUninit::uninit(); 256];
let path = DevicePathBuilder::with_buf(&mut buf)
.push(&acpi::Acpi {
hid: 0x41d0_0a03,
uid: 0x0000_0000,
})?
.push(&hardware::Pci {
function: 0x00,
device: 0x1f,
})?
.push(&messaging::FibreChannelEx {
world_wide_name: [0, 1, 2, 3, 4, 5, 6, 7],
logical_unit_number: [0, 1, 2, 3, 4, 5, 6, 7],
})?
.finalize()?;
let bytes = path_to_bytes(path);
#[rustfmt::skip]
assert_eq!(bytes, [
0x02, 0x01, 0x0c, 0x00,
0x03, 0x0a, 0xd0, 0x41,
0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x06, 0x00,
0x00,
0x1f,
0x03, 0x15,
0x18, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x7f, 0xff, 0x04, 0x00,
]);
Ok(())
}
#[test]
fn test_ipv4_configuration_example() -> Result<(), BuildError> {
let mut v = Vec::new();
let path = DevicePathBuilder::with_vec(&mut v)
.push(&acpi::Acpi {
hid: 0x41d0_0a03,
uid: 0x0000_0000,
})?
.push(&hardware::Pci {
function: 0x00,
device: 0x19,
})?
.push(&messaging::MacAddress {
mac_address: [
0x00, 0x13, 0x20, 0xf5, 0xfa, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
interface_type: 0x01,
})?
.push(&messaging::Ipv4 {
local_ip_address: [192, 168, 0, 1],
remote_ip_address: [192, 168, 0, 100],
local_port: 0,
remote_port: 3260,
protocol: 6,
ip_address_origin: Ipv4AddressOrigin::STATIC,
gateway_ip_address: [0, 0, 0, 0],
subnet_mask: [0, 0, 0, 0],
})?
.push(&messaging::Iscsi {
protocol: IscsiProtocol::TCP,
options: IscsiLoginOptions::AUTH_METHOD_NONE,
logical_unit_number: 0u64.to_le_bytes(),
target_portal_group_tag: 1,
iscsi_target_name: b"iqn.1991-05.com.microsoft:iscsitarget-iscsidisk-target\0",
})?
.push(&media::HardDrive {
partition_number: 1,
partition_start: 0x22,
partition_size: 0x2710000,
partition_format: PartitionFormat::GPT,
partition_signature: PartitionSignature::Guid(guid!(
"15e39a00-1dd2-1000-8d7f-00a0c92408fc"
)),
})?
.finalize()?;
let bytes = path_to_bytes(path);
#[rustfmt::skip]
assert_eq!(bytes, [
0x02, 0x01, 0x0c, 0x00,
0x03, 0x0a, 0xd0, 0x41,
0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x06, 0x00,
0x00,
0x19,
0x03, 0x0b, 0x25, 0x00,
0x00, 0x13, 0x20, 0xf5, 0xfa, 0x77, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01,
0x03, 0x0c, 0x1b, 0x00,
0xc0, 0xa8, 0x00, 0x01,
0xc0, 0xa8, 0x00, 0x64,
0x00, 0x00,
0xbc, 0x0c,
0x06, 0x00,
0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x03, 0x13, 0x49, 0x00,
0x00, 0x00,
0x00, 0x08,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00,
0x69, 0x71, 0x6e, 0x2e, 0x31, 0x39, 0x39, 0x31,
0x2d, 0x30, 0x35, 0x2e, 0x63, 0x6f, 0x6d, 0x2e,
0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66,
0x74, 0x3a, 0x69, 0x73, 0x63, 0x73, 0x69, 0x74,
0x61, 0x72, 0x67, 0x65, 0x74, 0x2d, 0x69, 0x73,
0x63, 0x73, 0x69, 0x64, 0x69, 0x73, 0x6b, 0x2d,
0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00,
0x04, 0x01, 0x2a, 0x00,
0x01, 0x00, 0x00, 0x00,
0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x9a, 0xe3, 0x15, 0xd2, 0x1d, 0x00, 0x10,
0x8d, 0x7f, 0x00, 0xa0, 0xc9, 0x24, 0x08, 0xfc,
0x02,
0x02,
0x7f, 0xff, 0x04, 0x00,
]);
Ok(())
}
}