Skip to content
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
11 changes: 11 additions & 0 deletions uefi-raw/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
- Added `HiiConfigAccessProtocol`.
- Added `::octets()` for `Ipv4Address`, `Ipv6Address`, and
`MacAddress` to streamline the API with `core::net`.
- Added `::ZERO` constant for `IpAddress`
- Added comprehensive integration with `core::net::{IpAddr, Ipv4Addr, Ipv6Addr}`
via `From` impls to better integrate uefi-raw types `IpAddress`,
`Ipv4Address`, and `Ipv6Address` with the Rust ecosystem.
- Added convenient `From` impls:
- `[u8; 6]` --> `MacAddress`
- `[u8; 32]` --> `MacAddress`
- `[u8; 4]` --> `Ipv4Address`, `IpAddress`
- `[u8; 16]` --> `Ipv6Address`, `IpAddress`
- Added `::into_core_ip_addr()` for `IpAddress`
- Added `::try_into_ethernet_mac_addr()` for `MacAddress`

## Changed
- **Breaking:** The MSRV is now 1.85.1 and the crate uses the Rust 2024 edition.
Expand Down
254 changes: 241 additions & 13 deletions uefi-raw/src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@
//! - [`Ipv4Address`]
//! - [`Ipv6Address`]

use core::fmt;
use core::fmt::{Debug, Formatter};
use core::fmt::{self, Debug, Display, Formatter};

/// 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]);
Expand All @@ -36,7 +42,27 @@ impl From<Ipv4Address> for core::net::Ipv4Addr {
}
}

impl From<[u8; 4]> for Ipv4Address {
fn from(octets: [u8; 4]) -> Self {
Self(octets)
}
}

impl Display for Ipv4Address {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let ip = core::net::Ipv4Addr::from(*self);
write!(f, "{}", ip)
}
}

/// 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]);
Expand All @@ -61,12 +87,34 @@ impl From<Ipv6Address> for core::net::Ipv6Addr {
}
}

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

impl Display for Ipv6Address {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let ip = core::net::Ipv6Addr::from(*self);
write!(f, "{}", ip)
}
}

/// An IPv4 or IPv6 internet protocol address that is ABI compatible with EFI.
///
/// 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 {
Expand All @@ -83,19 +131,52 @@ pub union IpAddress {
}

impl IpAddress {
/// Zeroed variant where all bytes are guaranteed to be initialized to zero.
pub const ZERO: Self = Self { addr: [0; 4] };

/// Construct a new IPv4 address.
///
/// The type won't know that it is an IPv6 address and additional context
/// is needed.
///
/// # Safety
/// The constructor only initializes the bytes needed for IPv4 addresses.
#[must_use]
pub const fn new_v4(ip_addr: [u8; 4]) -> Self {
pub const fn new_v4(octets: [u8; 4]) -> Self {
Self {
v4: Ipv4Address(ip_addr),
v4: Ipv4Address(octets),
}
}

/// 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(ip_addr: [u8; 16]) -> Self {
pub const fn new_v6(octets: [u8; 16]) -> Self {
Self {
v6: Ipv6Address(ip_addr),
v6: Ipv6Address(octets),
}
}

/// Transforms this EFI type to the Rust standard library's type
/// [`core::net::IpAddr`].
///
/// # Arguments
/// - `is_ipv6`: Whether the internal data should be interpreted as IPv6 or
/// IPv4 address.
///
/// # Safety
/// Callers must ensure that the `v4` field is valid if `is_ipv6` is false,
/// and that the `v6` field is valid if `is_ipv6` is true
#[must_use]
pub unsafe fn into_core_ip_addr(self, is_ipv6: bool) -> core::net::IpAddr {
if is_ipv6 {
// SAFETY: Caller assumes that the underlying data is initialized.
core::net::IpAddr::V6(core::net::Ipv6Addr::from(unsafe { self.v6.octets() }))
} else {
// SAFETY: Caller assumes that the underlying data is initialized.
core::net::IpAddr::V4(core::net::Ipv4Addr::from(unsafe { self.v4.octets() }))
}
}
}
Expand All @@ -111,23 +192,43 @@ impl Debug for IpAddress {

impl Default for IpAddress {
fn default() -> Self {
Self { addr: [0u32; 4] }
Self::ZERO
}
}

impl From<core::net::IpAddr> 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),
},
core::net::IpAddr::V4(ip) => Self::new_v4(ip.octets()),
core::net::IpAddr::V6(ip) => Self::new_v6(ip.octets()),
}
}
}

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

impl From<core::net::Ipv6Addr> for IpAddress {
fn from(value: core::net::Ipv6Addr) -> 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
Expand All @@ -137,6 +238,13 @@ impl From<core::net::IpAddr> 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]);
Expand All @@ -147,6 +255,17 @@ impl MacAddress {
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]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to make an assumption about zero being invalid, but is that true? If so, let's point to relevant documentation. If not, let's drop this method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This originates from my naive assumption that triviality applies here. Why would one have a fully non-null initialized byte array that takes more bytes than just a normal MAC address?

IMHO, this is a common use-case that will simplify things. In most cases, people want to work with Ethernet Mac addresses and they need a convenient conversion for that.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about an infallible conversion? Something like fn truncate_to_ethernet_mac_addr(self) -> [u8; 6]. That way, we avoid making any assumptions about the meaning (or lack thereof) of the last 26 bytes, and the name makes it clear that this is truncating the input.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simple but elegant, I like it, thanks!

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.
Expand All @@ -164,6 +283,13 @@ impl From<MacAddress> for [u8; 6] {
}
}

// UEFI MAC addresses.
impl From<[u8; 32]> for MacAddress {
fn from(octets: [u8; 32]) -> Self {
Self(octets)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -216,4 +342,106 @@ mod tests {
assert_eq!(align_of::<PackedHelper<IpAddress>>(), 1);
assert_eq!(size_of::<PackedHelper<IpAddress>>(), 16);
}

/// 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.v4.octets() });
}
// 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.v6.octets() });
}
// StdIpv4Addr -> Ipv4Address
{
let octets = [7, 5, 3, 1];
let core_ipv4_addr = core::net::Ipv4Addr::from(octets);
assert_eq!(Ipv4Address::from(core_ipv4_addr).octets(), octets);
assert_eq!(
unsafe { IpAddress::from(core_ipv4_addr).v4.octets() },
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 = core::net::Ipv6Addr::from(octets);
assert_eq!(Ipv6Address::from(core_ipv6_addr).octets(), octets);
assert_eq!(
unsafe { IpAddress::from(core_ipv6_addr).v6.octets() },
octets
);
}
// StdIpAddr -> IpAddress
{
let octets = [8, 8, 2, 6];
let core_ip_addr = core::net::IpAddr::from(octets);
assert_eq!(unsafe { IpAddress::from(core_ip_addr).v4.octets() }, 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);
}
}

/// 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) {
let addr = unsafe { addr.as_mut().unwrap() };
// 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) -> core::net::IpAddr {
let mut efi_ip_addr = IpAddress::ZERO;
efi_retrieve_efi_ip_addr(&mut efi_ip_addr, is_ipv6);
unsafe { efi_ip_addr.into_core_ip_addr(is_ipv6) }
}

let ipv4_addr = high_level_retrieve_ip(false);
let ipv4_addr: core::net::Ipv4Addr = match ipv4_addr {
core::net::IpAddr::V4(ipv4_addr) => ipv4_addr,
core::net::IpAddr::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: core::net::Ipv6Addr = match ipv6_addr {
core::net::IpAddr::V6(ipv6_addr) => ipv6_addr,
core::net::IpAddr::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);
}
}
Loading