Skip to content

uefi-raw and uefi: use core::net types where possible, remove duplicated types #1645

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions uefi-raw/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@
- Added `UsbIoProtocol`.
- Added `Usb2HostControllerProtocol`.
- Added `DevicePathProtocol::length()` properly constructing the `u16` value
- Type `IpAddress` is now tightly integrated with `core::net::IpAddr`, e.g.,
various `From` implementations are available.

## Changed
- **Breaking:** Types `Ipv4Address` and `Ipv6Address` have been removed. They
were replaced by `core::net::Ipv4Addr` and `core::net::Ipv6Addr`, as they are
ABI compatible. This mainly affects the `IpAddress` wrapper type.
- `DevicePathProtocol` now derives
`Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash`

Expand Down
276 changes: 189 additions & 87 deletions uefi-raw/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub use uguid::{guid, Guid};

use core::ffi::c_void;
use core::fmt::{self, Debug, Formatter};
use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};

/// Handle to an event structure.
pub type Event = *mut c_void;
Expand Down Expand Up @@ -106,121 +107,190 @@ impl From<Boolean> 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<core::net::Ipv4Addr> for Ipv4Address {
fn from(ip: core::net::Ipv4Addr) -> Self {
Self(ip.octets())
}
}

impl From<Ipv4Address> 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<core::net::Ipv6Addr> for Ipv6Address {
fn from(ip: core::net::Ipv6Addr) -> Self {
Self(ip.octets())
}
}

impl From<Ipv6Address> 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
/// that this is an **untagged union**, so there's no way to tell which type of
/// address an `IpAddress` value contains without additional context.
///
/// For convenience, this type is tightly integrated with the Rust standard
/// library types [`IpAddr`], [`Ipv4Addr`], and [`Ipv6Addr`].
///
/// The constructors ensure that all unused bytes of these type are always
/// initialized to zero.
#[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,
pub v4: Ipv4Addr,

/// An IPv6 internet protocol address.
pub v6: Ipv6Address,
pub v6: Ipv6Addr,

/// This member serves to align the whole type to 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 _align_helper: [u32; 4],
}

impl IpAddress {
/// Construct a new IPv4 address.
#[must_use]
pub const fn new_v4(ip_addr: [u8; 4]) -> Self {
Self {
v4: Ipv4Address(ip_addr),
}
pub fn new_v4(ip_addr: [u8; 4]) -> Self {
// Initialize all bytes to zero first.
let mut obj = Self::default();
obj.v4 = Ipv4Addr::from(ip_addr);
obj
}

/// Construct a new IPv6 address.
#[must_use]
pub const fn new_v6(ip_addr: [u8; 16]) -> Self {
pub fn new_v6(ip_addr: [u8; 16]) -> Self {
Self {
v6: Ipv6Address(ip_addr),
v6: Ipv6Addr::from(ip_addr),
}
}

/// Returns the octets of the union. Without additional context, it is not
/// clear whether the octets represent an IPv4 or IPv6 address.
#[must_use]
pub const fn octets(&self) -> [u8; 16] {
unsafe { self.v6.octets() }
}

/// Returns a raw pointer to the IP address.
#[must_use]
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 fn as_ptr_mut(&mut self) -> *mut Self {
core::ptr::addr_of_mut!(*self)
}

/// Transforms this EFI type to the Rust standard libraries type.
///
/// # Arguments
/// - `is_ipv6`: Whether the internal data should be interpreted as IPv6 or
/// IPv4 address.
#[must_use]
pub fn to_ip_addr(self, is_ipv6: bool) -> IpAddr {
if is_ipv6 {
IpAddr::V6(Ipv6Addr::from(unsafe { self.v6.octets() }))
} else {
IpAddr::V4(Ipv4Addr::from(unsafe { self.v4.octets() }))
}
}

/// Returns the underlying data as [`Ipv4Addr`], if only the first four
/// octets are used.
///
/// # Safety
/// This function is not unsafe memory-wise but callers need to ensure with
/// additional context that the IP is indeed an IPv4 address.
pub unsafe fn as_ipv4(&self) -> Result<Ipv4Addr, Ipv6Addr> {
let extra = self.octets()[4..].iter().any(|&x| x != 0);
if !extra {
Ok(Ipv4Addr::from(unsafe { self.v4.octets() }))
} else {
Err(Ipv6Addr::from(unsafe { self.v6.octets() }))
}
}

/// Returns the underlying data as [`Ipv6Addr`].
///
/// # Safety
/// This function is not unsafe memory-wise but callers need to ensure with
/// additional context that the IP is indeed an IPv6 address.
#[must_use]
pub unsafe fn as_ipv6(&self) -> Ipv6Addr {
Ipv6Addr::from(unsafe { self.v6.octets() })
}
}

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()
f.debug_tuple("IpAddress").field(&self.octets()).finish()
}
}

impl Default for IpAddress {
fn default() -> Self {
Self { addr: [0u32; 4] }
Self {
// Initialize all fields to zero
_align_helper: [0u32; 4],
}
}
}

impl From<core::net::IpAddr> for IpAddress {
fn from(t: core::net::IpAddr) -> Self {
impl From<IpAddr> for IpAddress {
fn from(t: IpAddr) -> Self {
match t {
core::net::IpAddr::V4(ip) => Self {
v4: Ipv4Address::from(ip),
},
core::net::IpAddr::V6(ip) => Self {
v6: Ipv6Address::from(ip),
},
IpAddr::V4(ip) => Self::new_v4(ip.octets()),
IpAddr::V6(ip) => Self::new_v6(ip.octets()),
}
}
}

/// A Media Access Control (MAC) address.
impl From<&IpAddr> for IpAddress {
fn from(t: &IpAddr) -> Self {
match t {
IpAddr::V4(ip) => Self::new_v4(ip.octets()),
IpAddr::V6(ip) => Self::new_v6(ip.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)
}
}

impl From<IpAddress> for [u8; 16] {
fn from(value: IpAddress) -> Self {
value.octets()
}
}

impl From<Ipv4Addr> for IpAddress {
fn from(value: Ipv4Addr) -> Self {
Self::new_v4(value.octets())
}
}

impl From<Ipv6Addr> for IpAddress {
fn from(value: Ipv6Addr) -> Self {
Self::new_v6(value.octets())
}
}

/// 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[0] = octets[0];
buffer[1] = octets[1];
buffer[2] = octets[2];
buffer[3] = octets[3];
buffer[4] = octets[4];
buffer[5] = octets[5];
buffer.copy_from_slice(&octets);
Self(buffer)
}
}
Expand Down Expand Up @@ -258,33 +328,65 @@ mod tests {
assert!(bool::from(Boolean(0b11111111)));
}

/// Test round-trip conversion between `Ipv4Address` and `core::net::Ipv4Addr`.
/// We test that the core::net-types are ABI compatible with the EFI types.
/// As long as this is the case, we can reuse core functionality and
/// prevent type duplication.
#[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));
fn net_abi() {
assert_eq!(size_of::<Ipv4Addr>(), 4);
assert_eq!(align_of::<Ipv4Addr>(), 1);
assert_eq!(size_of::<Ipv6Addr>(), 16);
assert_eq!(align_of::<Ipv6Addr>(), 1);
}

/// 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));
fn ip_ptr() {
let mut ip = IpAddress::new_v4([0; 4]);
let ptr = ip.as_ptr_mut().cast::<u8>();
unsafe {
core::ptr::write(ptr, 192);
core::ptr::write(ptr.add(1), 168);
core::ptr::write(ptr.add(2), 42);
core::ptr::write(ptr.add(3), 73);
}
unsafe { assert_eq!(ip.v4.octets(), [192, 168, 42, 73]) }
}

/// Test conversion from `core::net::IpAddr` to `IpvAddress`.
///
/// Note that conversion in the other direction is not possible.
/// Test conversion from [`IpAddr`] to [`IpAddress`].
#[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);
// Reference: std types
let core_ipv4_v4 = Ipv4Addr::from(TEST_IPV4);
let core_ipv4 = IpAddr::from(core_ipv4_v4);
let core_ipv6_v6 = Ipv6Addr::from(TEST_IPV6);
let core_ipv6 = IpAddr::from(core_ipv6_v6);

// Test From [u8; N] constructors
assert_eq!(IpAddress::from(TEST_IPV4).octets()[0..4], TEST_IPV4);
assert_eq!(IpAddress::from(TEST_IPV6).octets(), TEST_IPV6);
{
let bytes: [u8; 16] = IpAddress::from(TEST_IPV6).into();
assert_eq!(bytes, TEST_IPV6);
}

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);
// Test From::from std type constructors
let efi_ipv4 = IpAddress::from(core_ipv4);
assert_eq!(efi_ipv4.octets()[0..4], TEST_IPV4);
assert_eq!(unsafe { efi_ipv4.as_ipv4().unwrap() }, core_ipv4);

let efi_ipv6 = IpAddress::from(core_ipv6);
assert_eq!(efi_ipv6.octets(), TEST_IPV6);
assert_eq!(unsafe { efi_ipv6.as_ipv4().unwrap_err() }, core_ipv6);
assert_eq!(unsafe { efi_ipv6.as_ipv6() }, core_ipv6);

// Test From::from std type constructors
let efi_ipv4 = IpAddress::from(core_ipv4_v4);
assert_eq!(efi_ipv4.octets()[0..4], TEST_IPV4);
assert_eq!(unsafe { efi_ipv4.as_ipv4().unwrap() }, core_ipv4);

let efi_ipv6 = IpAddress::from(core_ipv6_v6);
assert_eq!(efi_ipv6.octets(), TEST_IPV6);
assert_eq!(unsafe { efi_ipv6.as_ipv4().unwrap_err() }, core_ipv6);
assert_eq!(unsafe { efi_ipv6.as_ipv6() }, core_ipv6);
}
}
Loading
Loading