From d4c029544b39690cca4095a554c15379cb5c98b6 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sat, 14 Jun 2025 21:24:08 +0200 Subject: [PATCH 1/4] uefi-raw: move types to net module We have enough network-related types to justify a dedicated module: - IpAddress - Ipv4Address - Ipv6Address - MacAddress --- uefi-raw/src/lib.rs | 163 +--------------------------------------- uefi-raw/src/net.rs | 176 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 161 deletions(-) create mode 100644 uefi-raw/src/net.rs diff --git a/uefi-raw/src/lib.rs b/uefi-raw/src/lib.rs index a67ac564d..1eab855a2 100644 --- a/uefi-raw/src/lib.rs +++ b/uefi-raw/src/lib.rs @@ -31,13 +31,14 @@ pub mod protocol; pub mod table; pub mod time; +mod net; mod status; +pub use net::*; pub use status::Status; pub use uguid::{Guid, guid}; use core::ffi::c_void; -use core::fmt::{self, Debug, Formatter}; /// Handle to an event structure. pub type Event = *mut c_void; @@ -106,140 +107,10 @@ impl From for bool { } } -/// An IPv4 internet protocol address. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[repr(transparent)] -pub struct Ipv4Address(pub [u8; 4]); - -impl From for Ipv4Address { - fn from(ip: core::net::Ipv4Addr) -> Self { - Self(ip.octets()) - } -} - -impl From for core::net::Ipv4Addr { - fn from(ip: Ipv4Address) -> Self { - Self::from(ip.0) - } -} - -/// An IPv6 internet protocol address. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[repr(transparent)] -pub struct Ipv6Address(pub [u8; 16]); - -impl From for Ipv6Address { - fn from(ip: core::net::Ipv6Addr) -> Self { - Self(ip.octets()) - } -} - -impl From for core::net::Ipv6Addr { - fn from(ip: Ipv6Address) -> Self { - Self::from(ip.0) - } -} - -/// An IPv4 or IPv6 internet protocol address. -/// -/// Corresponds to the `EFI_IP_ADDRESS` type in the UEFI specification. This -/// type is defined in the same way as edk2 for compatibility with C code. Note -/// that this is an untagged union, so there's no way to tell which type of -/// address an `IpAddress` value contains without additional context. -#[derive(Clone, Copy)] -#[repr(C)] -pub union IpAddress { - /// This member serves to align the whole type to a 4 bytes as required by - /// the spec. Note that this is slightly different from `repr(align(4))`, - /// which would prevent placing this type in a packed structure. - pub addr: [u32; 4], - - /// An IPv4 internet protocol address. - pub v4: Ipv4Address, - - /// An IPv6 internet protocol address. - pub v6: Ipv6Address, -} - -impl IpAddress { - /// Construct a new IPv4 address. - #[must_use] - pub const fn new_v4(ip_addr: [u8; 4]) -> Self { - Self { - v4: Ipv4Address(ip_addr), - } - } - - /// Construct a new IPv6 address. - #[must_use] - pub const fn new_v6(ip_addr: [u8; 16]) -> Self { - Self { - v6: Ipv6Address(ip_addr), - } - } -} - -impl Debug for IpAddress { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // The type is an untagged union, so we don't know whether it contains - // an IPv4 or IPv6 address. It's also not safe to just print the whole - // 16 bytes, since they might not all be initialized. - f.debug_struct("IpAddress").finish() - } -} - -impl Default for IpAddress { - fn default() -> Self { - Self { addr: [0u32; 4] } - } -} - -impl From for IpAddress { - fn from(t: core::net::IpAddr) -> Self { - match t { - core::net::IpAddr::V4(ip) => Self { - v4: Ipv4Address::from(ip), - }, - core::net::IpAddr::V6(ip) => Self { - v6: Ipv6Address::from(ip), - }, - } - } -} - -/// A Media Access Control (MAC) address. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[repr(transparent)] -pub struct MacAddress(pub [u8; 32]); - -impl From<[u8; 6]> for MacAddress { - fn from(octets: [u8; 6]) -> Self { - let mut buffer = [0; 32]; - buffer[0] = octets[0]; - buffer[1] = octets[1]; - buffer[2] = octets[2]; - buffer[3] = octets[3]; - buffer[4] = octets[4]; - buffer[5] = octets[5]; - Self(buffer) - } -} - -impl From for [u8; 6] { - fn from(MacAddress(o): MacAddress) -> Self { - [o[0], o[1], o[2], o[3], o[4], o[5]] - } -} - #[cfg(test)] mod tests { use super::*; - const TEST_IPV4: [u8; 4] = [91, 92, 93, 94]; - const TEST_IPV6: [u8; 16] = [ - 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, - ]; - #[test] /// Test the properties promised in [0]. This also applies for the other /// architectures. @@ -257,34 +128,4 @@ mod tests { assert!(bool::from(Boolean(0b11111110))); assert!(bool::from(Boolean(0b11111111))); } - - /// Test round-trip conversion between `Ipv4Address` and `core::net::Ipv4Addr`. - #[test] - fn test_ip_addr4_conversion() { - let uefi_addr = Ipv4Address(TEST_IPV4); - let core_addr = core::net::Ipv4Addr::from(uefi_addr); - assert_eq!(uefi_addr, Ipv4Address::from(core_addr)); - } - - /// Test round-trip conversion between `Ipv6Address` and `core::net::Ipv6Addr`. - #[test] - fn test_ip_addr6_conversion() { - let uefi_addr = Ipv6Address(TEST_IPV6); - let core_addr = core::net::Ipv6Addr::from(uefi_addr); - assert_eq!(uefi_addr, Ipv6Address::from(core_addr)); - } - - /// Test conversion from `core::net::IpAddr` to `IpvAddress`. - /// - /// Note that conversion in the other direction is not possible. - #[test] - fn test_ip_addr_conversion() { - let core_addr = core::net::IpAddr::V4(core::net::Ipv4Addr::from(TEST_IPV4)); - let uefi_addr = IpAddress::from(core_addr); - assert_eq!(unsafe { uefi_addr.v4.0 }, TEST_IPV4); - - let core_addr = core::net::IpAddr::V6(core::net::Ipv6Addr::from(TEST_IPV6)); - let uefi_addr = IpAddress::from(core_addr); - assert_eq!(unsafe { uefi_addr.v6.0 }, TEST_IPV6); - } } diff --git a/uefi-raw/src/net.rs b/uefi-raw/src/net.rs new file mode 100644 index 000000000..cb063d4c3 --- /dev/null +++ b/uefi-raw/src/net.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! UEFI network types. + +use core::fmt; +use core::fmt::{Debug, Formatter}; + +/// An IPv4 internet protocol address. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct Ipv4Address(pub [u8; 4]); + +impl From for Ipv4Address { + fn from(ip: core::net::Ipv4Addr) -> Self { + Self(ip.octets()) + } +} + +impl From for core::net::Ipv4Addr { + fn from(ip: Ipv4Address) -> Self { + Self::from(ip.0) + } +} + +/// An IPv6 internet protocol address. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct Ipv6Address(pub [u8; 16]); + +impl From for Ipv6Address { + fn from(ip: core::net::Ipv6Addr) -> Self { + Self(ip.octets()) + } +} + +impl From for core::net::Ipv6Addr { + fn from(ip: Ipv6Address) -> Self { + Self::from(ip.0) + } +} + +/// An IPv4 or IPv6 internet protocol address. +/// +/// Corresponds to the `EFI_IP_ADDRESS` type in the UEFI specification. This +/// type is defined in the same way as edk2 for compatibility with C code. Note +/// that this is an **untagged union**, so there's no way to tell which type of +/// address an `IpAddress` value contains without additional context. +#[derive(Clone, Copy)] +#[repr(C)] +pub union IpAddress { + /// This member serves to align the whole type to a 4 bytes as required by + /// the spec. Note that this is slightly different from `repr(align(4))`, + /// which would prevent placing this type in a packed structure. + pub addr: [u32; 4], + + /// An IPv4 internet protocol address. + pub v4: Ipv4Address, + + /// An IPv6 internet protocol address. + pub v6: Ipv6Address, +} + +impl IpAddress { + /// Construct a new IPv4 address. + #[must_use] + pub const fn new_v4(ip_addr: [u8; 4]) -> Self { + Self { + v4: Ipv4Address(ip_addr), + } + } + + /// Construct a new IPv6 address. + #[must_use] + pub const fn new_v6(ip_addr: [u8; 16]) -> Self { + Self { + v6: Ipv6Address(ip_addr), + } + } +} + +impl Debug for IpAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // The type is an untagged union, so we don't know whether it contains + // an IPv4 or IPv6 address. It's also not safe to just print the whole + // 16 bytes, since they might not all be initialized. + f.debug_struct("IpAddress").finish() + } +} + +impl Default for IpAddress { + fn default() -> Self { + Self { + _align_helper: [0u32; 4], + } + } +} + +impl From for IpAddress { + fn from(t: core::net::IpAddr) -> Self { + match t { + core::net::IpAddr::V4(ip) => Self { + v4: Ipv4Address::from(ip), + }, + core::net::IpAddr::V6(ip) => Self { + v6: Ipv6Address::from(ip), + }, + } + } +} + +/// UEFI Media Access Control (MAC) address. +/// +/// UEFI supports multiple network protocols and hardware types, not just +/// Ethernet. Some of them may use MAC addresses longer than 6 bytes. To be +/// protocol-agnostic and future-proof, the UEFI spec chooses a maximum size +/// that can hold any supported media access control address. +/// +/// In most cases, this is just a typical `[u8; 6]` Ethernet style MAC +/// address with the rest of the bytes being zero. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct MacAddress(pub [u8; 32]); + +impl From<[u8; 6]> for MacAddress { + fn from(octets: [u8; 6]) -> Self { + let mut buffer = [0; 32]; + buffer.copy_from_slice(&octets); + Self(buffer) + } +} + +impl From for [u8; 6] { + fn from(MacAddress(o): MacAddress) -> Self { + [o[0], o[1], o[2], o[3], o[4], o[5]] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_IPV4: [u8; 4] = [91, 92, 93, 94]; + const TEST_IPV6: [u8; 16] = [ + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + ]; + + /// Test round-trip conversion between `Ipv4Address` and `core::net::Ipv4Addr`. + #[test] + fn test_ip_addr4_conversion() { + let uefi_addr = Ipv4Address(TEST_IPV4); + let core_addr = core::net::Ipv4Addr::from(uefi_addr); + assert_eq!(uefi_addr, Ipv4Address::from(core_addr)); + } + + /// Test round-trip conversion between `Ipv6Address` and `core::net::Ipv6Addr`. + #[test] + fn test_ip_addr6_conversion() { + let uefi_addr = Ipv6Address(TEST_IPV6); + let core_addr = core::net::Ipv6Addr::from(uefi_addr); + assert_eq!(uefi_addr, Ipv6Address::from(core_addr)); + } + + /// Test conversion from `core::net::IpAddr` to `IpvAddress`. + /// + /// Note that conversion in the other direction is not possible. + #[test] + fn test_ip_addr_conversion() { + let core_addr = core::net::IpAddr::V4(core::net::Ipv4Addr::from(TEST_IPV4)); + let uefi_addr = IpAddress::from(core_addr); + assert_eq!(unsafe { uefi_addr.v4.0 }, TEST_IPV4); + + let core_addr = core::net::IpAddr::V6(core::net::Ipv6Addr::from(TEST_IPV6)); + let uefi_addr = IpAddress::from(core_addr); + assert_eq!(unsafe { uefi_addr.v6.0 }, TEST_IPV6); + } +} From 870aced787bf3c30cc5614a19aafd7751625a325 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sun, 15 Jun 2025 20:29:48 +0200 Subject: [PATCH 2/4] uefi-raw: net: improve convenience of net types + doc This streamlines the integration with core::net types but also the general usage of uefi_raw::net types. --- uefi-raw/CHANGELOG.md | 5 + uefi-raw/src/net.rs | 294 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 262 insertions(+), 37 deletions(-) diff --git a/uefi-raw/CHANGELOG.md b/uefi-raw/CHANGELOG.md index fc2a6ad61..85a821279 100644 --- a/uefi-raw/CHANGELOG.md +++ b/uefi-raw/CHANGELOG.md @@ -5,6 +5,11 @@ - Added `PciRootBridgeIoProtocol`. - Added `ConfigKeywordHandlerProtocol`. - Added `HiiConfigAccessProtocol`. +- Added lots of convenient methods and `From` implementations to better + integrate the net types (`MacAddress`, `IpAddress`, `Ipv4Address`, + `Ipv6Address`) with the types from `core::net::*` plus some other convenient + `From` transformations. Look into the documentation of the types for further + guidance. ## Changed - The documentation for UEFI protocols has been streamlined and improved. diff --git a/uefi-raw/src/net.rs b/uefi-raw/src/net.rs index cb063d4c3..a7698b226 100644 --- a/uefi-raw/src/net.rs +++ b/uefi-raw/src/net.rs @@ -1,50 +1,108 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 //! UEFI network types. +//! +//! The main exports of this module are: +//! - [`MacAddress`] +//! - [`IpAddress`] +//! - [`Ipv4Address`] +//! - [`Ipv6Address`] -use core::fmt; use core::fmt::{Debug, Formatter}; +use core::net::{IpAddr as StdIpAddr, Ipv4Addr as StdIpv4Addr, Ipv6Addr as StdIpv6Addr}; +use core::{fmt, mem}; /// An IPv4 internet protocol address. +/// +/// # Conversions and Relation to [`core::net`] +/// +/// The following [`From`] implementations exist: +/// - `[u8; 4]` -> [`Ipv4Address`] +/// - [`core::net::Ipv4Addr`] -> [`Ipv4Address`] +/// - [`core::net::IpAddr`] -> [`Ipv4Address`] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] pub struct Ipv4Address(pub [u8; 4]); -impl From for Ipv4Address { - fn from(ip: core::net::Ipv4Addr) -> Self { +impl Ipv4Address { + /// Returns the octets of the IP address. + #[must_use] + pub const fn octets(self) -> [u8; 4] { + self.0 + } +} + +impl From for Ipv4Address { + fn from(ip: StdIpv4Addr) -> Self { Self(ip.octets()) } } -impl From for core::net::Ipv4Addr { +impl From for StdIpv4Addr { fn from(ip: Ipv4Address) -> Self { Self::from(ip.0) } } +impl From<[u8; 4]> for Ipv4Address { + fn from(octets: [u8; 4]) -> Self { + Self(octets) + } +} + /// An IPv6 internet protocol address. +/// +/// # Conversions and Relation to [`core::net`] +/// +/// The following [`From`] implementations exist: +/// - `[u8; 16]` -> [`Ipv6Address`] +/// - [`core::net::Ipv6Addr`] -> [`Ipv6Address`] +/// - [`core::net::IpAddr`] -> [`Ipv6Address`] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] pub struct Ipv6Address(pub [u8; 16]); -impl From for Ipv6Address { - fn from(ip: core::net::Ipv6Addr) -> Self { +impl Ipv6Address { + /// Returns the octets of the IP address. + #[must_use] + pub const fn octets(self) -> [u8; 16] { + self.0 + } +} + +impl From for Ipv6Address { + fn from(ip: StdIpv6Addr) -> Self { Self(ip.octets()) } } -impl From for core::net::Ipv6Addr { +impl From for StdIpv6Addr { fn from(ip: Ipv6Address) -> Self { Self::from(ip.0) } } -/// An IPv4 or IPv6 internet protocol address. +impl From<[u8; 16]> for Ipv6Address { + fn from(octets: [u8; 16]) -> Self { + Self(octets) + } +} + +/// EFI ABI-compatible union of an IPv4 or IPv6 internet protocol address. /// /// Corresponds to the `EFI_IP_ADDRESS` type in the UEFI specification. This /// type is defined in the same way as edk2 for compatibility with C code. Note /// that this is an **untagged union**, so there's no way to tell which type of /// address an `IpAddress` value contains without additional context. +/// +/// # Conversions and Relation to [`core::net`] +/// +/// The following [`From`] implementations exist: +/// - `[u8; 4]` -> [`IpAddress`] +/// - `[u8; 16]` -> [`IpAddress`] +/// - [`core::net::Ipv4Addr`] -> [`IpAddress`] +/// - [`core::net::Ipv6Addr`] -> [`IpAddress`] +/// - [`core::net::IpAddr`] -> [`IpAddress`] #[derive(Clone, Copy)] #[repr(C)] pub union IpAddress { @@ -61,19 +119,75 @@ pub union IpAddress { } impl IpAddress { + /// Construct a new zeroed address. + #[must_use] + pub const fn new_zeroed() -> Self { + // SAFETY: All bit patterns are valid. + unsafe { mem::zeroed() } + } + /// Construct a new IPv4 address. + /// + /// The type won't know that it is an IPv6 address and additional context + /// is needed. #[must_use] - pub const fn new_v4(ip_addr: [u8; 4]) -> Self { - Self { - v4: Ipv4Address(ip_addr), - } + pub const fn new_v4(octets: [u8; 4]) -> Self { + // Initialize all bytes to zero first. + let mut obj = Self::new_zeroed(); + obj.v4 = Ipv4Address(octets); + obj } /// Construct a new IPv6 address. + /// + /// The type won't know that it is an IPv6 address and additional context + /// is needed. + #[must_use] + pub const fn new_v6(octets: [u8; 16]) -> Self { + // Initialize all bytes to zero first. + let mut obj = Self::new_zeroed(); + obj.v6 = Ipv6Address(octets); + obj + } + + /// Returns the octets of the union. Without additional context, it is not + /// clear whether the octets represent an IPv4 or IPv6 address. + /// + /// # Safety + /// Callers must be sure that all underlying bytes were initialized. + #[must_use] + pub const unsafe fn octets(&self) -> [u8; 16] { + unsafe { self.v6.octets() } + } + + /// Returns a raw pointer to the IP address. #[must_use] - pub const fn new_v6(ip_addr: [u8; 16]) -> Self { - Self { - v6: Ipv6Address(ip_addr), + pub const fn as_ptr(&self) -> *const Self { + core::ptr::addr_of!(*self) + } + + /// Returns a raw mutable pointer to the IP address. + #[must_use] + pub const fn as_ptr_mut(&mut self) -> *mut Self { + core::ptr::addr_of_mut!(*self) + } + + /// Transforms this EFI type to the Rust standard library's type. + /// + /// # Arguments + /// - `is_ipv6`: Whether the internal data should be interpreted as IPv6 or + /// IPv4 address. + /// + /// # Safety + /// Callers must be sure that all underlying bytes were initialized. + #[must_use] + pub unsafe fn into_std_ip_addr(self, is_ipv6: bool) -> StdIpAddr { + if is_ipv6 { + // SAFETY: Caller assumes that the underlying data is initialized. + StdIpAddr::V6(StdIpv6Addr::from(unsafe { self.v6.octets() })) + } else { + // SAFETY: Caller assumes that the underlying data is initialized. + StdIpAddr::V4(StdIpv4Addr::from(unsafe { self.v4.octets() })) } } } @@ -89,25 +203,43 @@ impl Debug for IpAddress { impl Default for IpAddress { fn default() -> Self { - Self { - _align_helper: [0u32; 4], - } + Self::new_zeroed() } } -impl From for IpAddress { - fn from(t: core::net::IpAddr) -> Self { +impl From for IpAddress { + fn from(t: StdIpAddr) -> Self { match t { - core::net::IpAddr::V4(ip) => Self { - v4: Ipv4Address::from(ip), - }, - core::net::IpAddr::V6(ip) => Self { - v6: Ipv6Address::from(ip), - }, + StdIpAddr::V4(ip) => Self::new_v4(ip.octets()), + StdIpAddr::V6(ip) => Self::new_v6(ip.octets()), } } } +impl From for IpAddress { + fn from(value: StdIpv4Addr) -> Self { + Self::new_v4(value.octets()) + } +} + +impl From for IpAddress { + fn from(value: StdIpv6Addr) -> Self { + Self::new_v6(value.octets()) + } +} + +impl From<[u8; 4]> for IpAddress { + fn from(octets: [u8; 4]) -> Self { + Self::new_v4(octets) + } +} + +impl From<[u8; 16]> for IpAddress { + fn from(octets: [u8; 16]) -> Self { + Self::new_v6(octets) + } +} + /// UEFI Media Access Control (MAC) address. /// /// UEFI supports multiple network protocols and hardware types, not just @@ -117,21 +249,49 @@ impl From for IpAddress { /// /// In most cases, this is just a typical `[u8; 6]` Ethernet style MAC /// address with the rest of the bytes being zero. +/// +/// # Conversions and Relation to [`core::net`] +/// +/// There is no matching type in [`core::net`] but the following [`From`] +/// implementations exist: +/// - `[u8; 6]` -> [`MacAddress`] +/// - `[u8; 32]` -> [`MacAddress`] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] pub struct MacAddress(pub [u8; 32]); +impl MacAddress { + /// Returns the octets of the MAC address. + #[must_use] + pub const fn octets(self) -> [u8; 32] { + self.0 + } + + /// Tries to interpret the MAC address as normal 6-byte MAC address, as used + /// in ethernet. + pub fn try_into_ethernet_mac_addr(self) -> Result<[u8; 6], [u8; 32]> { + let extra = self.octets()[4..].iter().any(|&x| x != 0); + if extra { + Err(self.0) + } else { + Ok(self.octets()[..4].try_into().unwrap()) + } + } +} + +// Normal/typical MAC addresses, such as in Ethernet. impl From<[u8; 6]> for MacAddress { fn from(octets: [u8; 6]) -> Self { let mut buffer = [0; 32]; - buffer.copy_from_slice(&octets); + buffer[..6].copy_from_slice(&octets); Self(buffer) } } -impl From for [u8; 6] { - fn from(MacAddress(o): MacAddress) -> Self { - [o[0], o[1], o[2], o[3], o[4], o[5]] +// UEFI MAC addresses. +impl From<[u8; 32]> for MacAddress { + fn from(octets: [u8; 32]) -> Self { + Self(octets) } } @@ -144,33 +304,93 @@ mod tests { 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, ]; - /// Test round-trip conversion between `Ipv4Address` and `core::net::Ipv4Addr`. + /// Test round-trip conversion between `Ipv4Address` and `StdIpv4Addr`. #[test] fn test_ip_addr4_conversion() { let uefi_addr = Ipv4Address(TEST_IPV4); - let core_addr = core::net::Ipv4Addr::from(uefi_addr); + let core_addr = StdIpv4Addr::from(uefi_addr); assert_eq!(uefi_addr, Ipv4Address::from(core_addr)); } - /// Test round-trip conversion between `Ipv6Address` and `core::net::Ipv6Addr`. + /// Test round-trip conversion between [`Ipv6Address`] and [`StdIpv6Addr`]. #[test] fn test_ip_addr6_conversion() { let uefi_addr = Ipv6Address(TEST_IPV6); - let core_addr = core::net::Ipv6Addr::from(uefi_addr); + let core_addr = StdIpv6Addr::from(uefi_addr); assert_eq!(uefi_addr, Ipv6Address::from(core_addr)); } - /// Test conversion from `core::net::IpAddr` to `IpvAddress`. + /// Test conversion from [`StdIpAddr`] to [`IpvAddress`]. /// /// Note that conversion in the other direction is not possible. #[test] fn test_ip_addr_conversion() { - let core_addr = core::net::IpAddr::V4(core::net::Ipv4Addr::from(TEST_IPV4)); + let core_addr = StdIpAddr::V4(StdIpv4Addr::from(TEST_IPV4)); let uefi_addr = IpAddress::from(core_addr); assert_eq!(unsafe { uefi_addr.v4.0 }, TEST_IPV4); - let core_addr = core::net::IpAddr::V6(core::net::Ipv6Addr::from(TEST_IPV6)); + let core_addr = StdIpAddr::V6(StdIpv6Addr::from(TEST_IPV6)); let uefi_addr = IpAddress::from(core_addr); assert_eq!(unsafe { uefi_addr.v6.0 }, TEST_IPV6); } + + /// Tests the From-impls from the documentation. + #[test] + fn test_promised_from_impls() { + // octets -> Ipv4Address + { + let octets = [0_u8, 1, 2, 3]; + assert_eq!(Ipv4Address::from(octets), Ipv4Address(octets)); + let uefi_addr = IpAddress::from(octets); + assert_eq!(&octets, &unsafe { uefi_addr.octets() }[0..4]); + } + // octets -> Ipv6Address + { + let octets = [0_u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + assert_eq!(Ipv6Address::from(octets), Ipv6Address(octets)); + let uefi_addr = IpAddress::from(octets); + assert_eq!(&octets, &unsafe { uefi_addr.octets() }); + } + // StdIpv4Addr -> Ipv4Address + { + let octets = [7, 5, 3, 1]; + let core_ipv4_addr = StdIpv4Addr::from(octets); + assert_eq!(Ipv4Address::from(core_ipv4_addr).octets(), octets); + assert_eq!( + unsafe { IpAddress::from(core_ipv4_addr).octets() }[0..4], + octets + ); + } + // StdIpv6Addr -> Ipv6Address + { + let octets = [7, 5, 3, 1, 6, 3, 8, 5, 2, 5, 2, 7, 3, 5, 2, 6]; + let core_ipv6_addr = StdIpv6Addr::from(octets); + assert_eq!(Ipv6Address::from(core_ipv6_addr).octets(), octets); + assert_eq!(unsafe { IpAddress::from(core_ipv6_addr).octets() }, octets); + } + // StdIpAddr -> IpAddress + { + let octets = [8, 8, 2, 6]; + let core_ip_addr = StdIpAddr::from(octets); + assert_eq!( + unsafe { IpAddress::from(core_ip_addr).octets() }[0..4], + octets + ); + } + // octets -> MacAddress + { + let octets = [8, 8, 2, 6, 6, 7]; + let uefi_mac_addr = MacAddress::from(octets); + assert_eq!(uefi_mac_addr.octets()[0..6], octets); + } + // octets -> MacAddress + { + let octets = [ + 8_u8, 8, 2, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 7, 0, 0, 0, + 0, 0, 0, 0, 42, + ]; + let uefi_mac_addr = MacAddress::from(octets); + assert_eq!(uefi_mac_addr.octets(), octets); + } + } } From b386e92d0a459138a4c1e18c84212e37a9d0e850 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sat, 16 Aug 2025 13:37:50 +0200 Subject: [PATCH 3/4] uefi-raw: add ABI test for IpAddress --- uefi-raw/src/net.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/uefi-raw/src/net.rs b/uefi-raw/src/net.rs index a7698b226..ba5b85e86 100644 --- a/uefi-raw/src/net.rs +++ b/uefi-raw/src/net.rs @@ -393,4 +393,16 @@ mod tests { assert_eq!(uefi_mac_addr.octets(), octets); } } + + #[test] + fn test_efi_ip_address_abi() { + #[repr(C, packed)] + struct PackedHelper(T); + + assert_eq!(align_of::(), 4); + assert_eq!(size_of::(), 16); + + assert_eq!(align_of::>(), 1); + assert_eq!(size_of::>(), 16); + } } From f2b51981bc386ef8d5a213365b94b4cac9ab9414 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 19 Aug 2025 10:50:21 +0200 Subject: [PATCH 4/4] uefi-raw: add unit test for typical high-level net API usage --- uefi-raw/src/net.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/uefi-raw/src/net.rs b/uefi-raw/src/net.rs index ba5b85e86..4fc271c0d 100644 --- a/uefi-raw/src/net.rs +++ b/uefi-raw/src/net.rs @@ -394,6 +394,47 @@ mod tests { } } + /// Tests the expected flow of types in a higher-level UEFI API. + #[test] + fn test_uefi_flow() { + fn efi_retrieve_efi_ip_addr(addr: &mut IpAddress, is_ipv6: bool) { + // SAFETY: Alignment is guaranteed and memory is initialized. + unsafe { + addr.v4.0[0] = 42; + addr.v4.0[1] = 42; + addr.v4.0[2] = 42; + addr.v4.0[3] = 42; + } + if is_ipv6 { + unsafe { + addr.v6.0[14] = 42; + addr.v6.0[15] = 42; + } + } + } + + fn high_level_retrieve_ip(is_ipv6: bool) -> StdIpAddr { + let mut efi_ip_addr = IpAddress::new_zeroed(); + efi_retrieve_efi_ip_addr(&mut efi_ip_addr, is_ipv6); + unsafe { efi_ip_addr.into_std_ip_addr(is_ipv6) } + } + + let ipv4_addr = high_level_retrieve_ip(false); + let ipv4_addr: StdIpv4Addr = match ipv4_addr { + StdIpAddr::V4(ipv4_addr) => ipv4_addr, + StdIpAddr::V6(_) => panic!("should not happen"), + }; + assert_eq!(ipv4_addr.octets(), [42, 42, 42, 42]); + + let ipv6_addr = high_level_retrieve_ip(true); + let ipv6_addr: StdIpv6Addr = match ipv6_addr { + StdIpAddr::V6(ipv6_addr) => ipv6_addr, + StdIpAddr::V4(_) => panic!("should not happen"), + }; + let expected = [42, 42, 42, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 42]; + assert_eq!(ipv6_addr.octets(), expected); + } + #[test] fn test_efi_ip_address_abi() { #[repr(C, packed)]