//! PXE Base Code protocol. use core::ffi::c_void; use core::fmt::{self, Debug, Display, Formatter}; use core::iter::from_fn; use core::mem::MaybeUninit; use core::ptr::{null, null_mut}; use crate::polyfill::maybe_uninit_slice_as_mut_ptr; use crate::proto::unsafe_protocol; use crate::util::ptr_write_unaligned_and_add; use bitflags::bitflags; use ptr_meta::Pointee; use crate::{CStr8, Char8, Result, Status, StatusExt}; use super::{IpAddress, MacAddress}; /// PXE Base Code protocol #[derive(Debug)] #[repr(C)] #[unsafe_protocol("03c4e603-ac28-11d3-9a2d-0090273fc14d")] #[allow(clippy::type_complexity)] pub struct BaseCode { revision: u64, start: extern "efiapi" fn(this: &Self, use_ipv6: bool) -> Status, stop: extern "efiapi" fn(this: &Self) -> Status, dhcp: extern "efiapi" fn(this: &Self, sort_offers: bool) -> Status, discover: extern "efiapi" fn( this: &Self, ty: BootstrapType, layer: &mut u16, use_bis: bool, info: *const FfiDiscoverInfo, ) -> Status, mtftp: unsafe extern "efiapi" fn( this: &Self, operation: TftpOpcode, buffer: *mut c_void, overwrite: bool, buffer_size: &mut u64, block_size: Option<&usize>, server_ip: &IpAddress, filename: *const Char8, info: Option<&MtftpInfo>, dont_use_buffer: bool, ) -> Status, udp_write: unsafe extern "efiapi" fn( this: &Self, op_flags: UdpOpFlags, dest_ip: &IpAddress, dest_port: &u16, gateway_ip: Option<&IpAddress>, src_ip: Option<&IpAddress>, src_port: Option<&mut u16>, header_size: Option<&usize>, header_ptr: *const c_void, buffer_size: &usize, buffer_ptr: *const c_void, ) -> Status, udp_read: unsafe extern "efiapi" fn( this: &Self, op_flags: UdpOpFlags, dest_ip: Option<&mut IpAddress>, dest_port: Option<&mut u16>, src_ip: Option<&mut IpAddress>, src_port: Option<&mut u16>, header_size: Option<&usize>, header_ptr: *mut c_void, buffer_size: &mut usize, buffer_ptr: *mut c_void, ) -> Status, set_ip_filter: extern "efiapi" fn(this: &Self, new_filter: &IpFilter) -> Status, arp: extern "efiapi" fn( this: &Self, ip_addr: &IpAddress, mac_addr: Option<&mut MacAddress>, ) -> Status, set_parameters: extern "efiapi" fn( this: &Self, new_auto_arp: Option<&bool>, new_send_guid: Option<&bool>, new_ttl: Option<&u8>, new_tos: Option<&u8>, new_make_callback: Option<&bool>, ) -> Status, set_station_ip: extern "efiapi" fn( this: &Self, new_station_ip: Option<&IpAddress>, new_subnet_mask: Option<&IpAddress>, ) -> Status, set_packets: extern "efiapi" fn( this: &Self, new_dhcp_discover_valid: Option<&bool>, new_dhcp_ack_received: Option<&bool>, new_proxy_offer_received: Option<&bool>, new_pxe_discover_valid: Option<&bool>, new_pxe_reply_received: Option<&bool>, new_pxe_bis_reply_received: Option<&bool>, new_dhcp_discover: Option<&Packet>, new_dhcp_ack: Option<&Packet>, new_proxy_offer: Option<&Packet>, new_pxe_discover: Option<&Packet>, new_pxe_reply: Option<&Packet>, new_pxe_bis_reply: Option<&Packet>, ) -> Status, mode: *const Mode, } impl BaseCode { /// Enables the use of the PXE Base Code Protocol functions. pub fn start(&mut self, use_ipv6: bool) -> Result { (self.start)(self, use_ipv6).to_result() } /// Disables the use of the PXE Base Code Protocol functions. pub fn stop(&mut self) -> Result { (self.stop)(self).to_result() } /// Attempts to complete a DHCPv4 D.O.R.A. (discover / offer / request / /// acknowledge) or DHCPv6 S.A.R.R (solicit / advertise / request / reply) sequence. pub fn dhcp(&mut self, sort_offers: bool) -> Result { (self.dhcp)(self, sort_offers).to_result() } /// Attempts to complete the PXE Boot Server and/or boot image discovery /// sequence. pub fn discover( &mut self, ty: BootstrapType, layer: &mut u16, use_bis: bool, info: Option<&DiscoverInfo>, ) -> Result { let info: *const FfiDiscoverInfo = info .map(|info| { let info_ptr: *const DiscoverInfo = info; info_ptr.cast() }) .unwrap_or(null()); (self.discover)(self, ty, layer, use_bis, info).to_result() } /// Returns the size of a file located on a TFTP server. pub fn tftp_get_file_size(&mut self, server_ip: &IpAddress, filename: &CStr8) -> Result { let mut buffer_size = 0; let status = unsafe { (self.mtftp)( self, TftpOpcode::TftpGetFileSize, null_mut(), false, &mut buffer_size, None, server_ip, filename.as_ptr(), None, false, ) }; status.to_result_with_val(|| buffer_size) } /// Reads a file located on a TFTP server. pub fn tftp_read_file( &mut self, server_ip: &IpAddress, filename: &CStr8, buffer: Option<&mut [u8]>, ) -> Result { let (buffer_ptr, mut buffer_size, dont_use_buffer) = if let Some(buffer) = buffer { let buffer_size = u64::try_from(buffer.len()).unwrap(); ((&mut buffer[0] as *mut u8).cast(), buffer_size, false) } else { (null_mut(), 0, true) }; let status = unsafe { (self.mtftp)( self, TftpOpcode::TftpReadFile, buffer_ptr, false, &mut buffer_size, None, server_ip, filename.as_ptr(), None, dont_use_buffer, ) }; status.to_result_with_val(|| buffer_size) } /// Writes to a file located on a TFTP server. pub fn tftp_write_file( &mut self, server_ip: &IpAddress, filename: &CStr8, overwrite: bool, buffer: &[u8], ) -> Result { let buffer_ptr = (&buffer[0] as *const u8 as *mut u8).cast(); let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); unsafe { (self.mtftp)( self, TftpOpcode::TftpWriteFile, buffer_ptr, overwrite, &mut buffer_size, None, server_ip, filename.as_ptr(), None, false, ) } .to_result() } /// Reads a directory listing of a directory on a TFTP server. pub fn tftp_read_dir<'a>( &self, server_ip: &IpAddress, directory_name: &CStr8, buffer: &'a mut [u8], ) -> Result, ReadDirParseError>> + 'a> { let buffer_ptr = (&buffer[0] as *const u8 as *mut u8).cast(); let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); let status = unsafe { (self.mtftp)( self, TftpOpcode::TftpReadDirectory, buffer_ptr, false, &mut buffer_size, None, server_ip, directory_name.as_ptr(), None, false, ) }; status.to_result()?; let buffer_size = usize::try_from(buffer_size).expect("buffer length should fit in usize"); let buffer = &buffer[..buffer_size]; let mut iterator = buffer.split_inclusive(|b| *b == 0); let mut parse_next = move || { let filename = iterator.next().ok_or(ReadDirParseError)?; if filename == [0] { // This is the final entry. return Ok(None); } let filename = CStr8::from_bytes_with_nul(filename).unwrap(); let information_string = iterator.next().ok_or(ReadDirParseError)?; let (_null_terminator, information_string) = information_string.split_last().unwrap(); let information_string = core::str::from_utf8(information_string).map_err(|_| ReadDirParseError)?; let (size, rest) = information_string .split_once(' ') .ok_or(ReadDirParseError)?; let (year, rest) = rest.split_once('-').ok_or(ReadDirParseError)?; let (month, rest) = rest.split_once('-').ok_or(ReadDirParseError)?; let (day, rest) = rest.split_once(' ').ok_or(ReadDirParseError)?; let (hour, rest) = rest.split_once(':').ok_or(ReadDirParseError)?; let (minute, second) = rest.split_once(':').ok_or(ReadDirParseError)?; let size = size.parse().map_err(|_| ReadDirParseError)?; let year = year.parse().map_err(|_| ReadDirParseError)?; let month = month.parse().map_err(|_| ReadDirParseError)?; let day = day.parse().map_err(|_| ReadDirParseError)?; let hour = hour.parse().map_err(|_| ReadDirParseError)?; let minute = minute.parse().map_err(|_| ReadDirParseError)?; let second = second.parse().map_err(|_| ReadDirParseError)?; Ok(Some(TftpFileInfo { filename, size, year, month, day, hour, minute, second, })) }; Ok(from_fn(move || parse_next().transpose()).fuse()) } /// Returns the size of a file located on a MTFTP server. pub fn mtftp_get_file_size( &mut self, server_ip: &IpAddress, filename: &CStr8, info: &MtftpInfo, ) -> Result { let mut buffer_size = 0; let status = unsafe { (self.mtftp)( self, TftpOpcode::MtftpGetFileSize, null_mut(), false, &mut buffer_size, None, server_ip, filename.as_ptr(), Some(info), false, ) }; status.to_result_with_val(|| buffer_size) } /// Reads a file located on a MTFTP server. pub fn mtftp_read_file( &mut self, server_ip: &IpAddress, filename: &CStr8, buffer: Option<&mut [u8]>, info: &MtftpInfo, ) -> Result { let (buffer_ptr, mut buffer_size, dont_use_buffer) = if let Some(buffer) = buffer { let buffer_size = u64::try_from(buffer.len()).unwrap(); ((&mut buffer[0] as *mut u8).cast(), buffer_size, false) } else { (null_mut(), 0, true) }; let status = unsafe { (self.mtftp)( self, TftpOpcode::MtftpReadFile, buffer_ptr, false, &mut buffer_size, None, server_ip, filename.as_ptr(), Some(info), dont_use_buffer, ) }; status.to_result_with_val(|| buffer_size) } /// Reads a directory listing of a directory on a MTFTP server. pub fn mtftp_read_dir<'a>( &self, server_ip: &IpAddress, buffer: &'a mut [u8], info: &MtftpInfo, ) -> Result, ReadDirParseError>> + 'a> { let buffer_ptr = (&buffer[0] as *const u8 as *mut u8).cast(); let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); let status = unsafe { (self.mtftp)( self, TftpOpcode::MtftpReadDirectory, buffer_ptr, false, &mut buffer_size, None, server_ip, null_mut(), Some(info), false, ) }; status.to_result()?; let buffer_size = usize::try_from(buffer_size).expect("buffer length should fit in usize"); let buffer = &buffer[..buffer_size]; let mut iterator = buffer.split_inclusive(|b| *b == 0); let mut parse_next = move || { let filename = iterator.next().ok_or(ReadDirParseError)?; if filename == [0] { // This is the final entry. return Ok(None); } let filename = CStr8::from_bytes_with_nul(filename).unwrap(); let multicast_ip = iterator.next().ok_or(ReadDirParseError)?; let (_null_terminator, multicast_ip) = multicast_ip.split_last().unwrap(); let multicast_ip = core::str::from_utf8(multicast_ip).map_err(|_| ReadDirParseError)?; let mut octets = multicast_ip.split('.'); let mut buffer = [0; 4]; for b in buffer.iter_mut() { let octet = octets.next().ok_or(ReadDirParseError)?; let octet = octet.parse().map_err(|_| ReadDirParseError)?; *b = octet; } if octets.next().is_some() { // The IP should have exact 4 octets, not more. return Err(ReadDirParseError); } let ip_address = IpAddress::new_v4(buffer); let information_string = iterator.next().ok_or(ReadDirParseError)?; let (_null_terminator, information_string) = information_string.split_last().unwrap(); let information_string = core::str::from_utf8(information_string).map_err(|_| ReadDirParseError)?; let (size, rest) = information_string .split_once(' ') .ok_or(ReadDirParseError)?; let (year, rest) = rest.split_once('-').ok_or(ReadDirParseError)?; let (month, rest) = rest.split_once('-').ok_or(ReadDirParseError)?; let (day, rest) = rest.split_once(' ').ok_or(ReadDirParseError)?; let (hour, rest) = rest.split_once(':').ok_or(ReadDirParseError)?; let (minute, second) = rest.split_once(':').ok_or(ReadDirParseError)?; let size = size.parse().map_err(|_| ReadDirParseError)?; let year = year.parse().map_err(|_| ReadDirParseError)?; let month = month.parse().map_err(|_| ReadDirParseError)?; let day = day.parse().map_err(|_| ReadDirParseError)?; let hour = hour.parse().map_err(|_| ReadDirParseError)?; let minute = minute.parse().map_err(|_| ReadDirParseError)?; let second = second.parse().map_err(|_| ReadDirParseError)?; Ok(Some(MtftpFileInfo { filename, ip_address, size, year, month, day, hour, minute, second, })) }; Ok(from_fn(move || parse_next().transpose()).fuse()) } /// Writes a UDP packet to the network interface. #[allow(clippy::too_many_arguments)] pub fn udp_write( &mut self, op_flags: UdpOpFlags, dest_ip: &IpAddress, dest_port: u16, gateway_ip: Option<&IpAddress>, src_ip: Option<&IpAddress>, src_port: Option<&mut u16>, header: Option<&[u8]>, buffer: &[u8], ) -> Result { let header_size_tmp; let (header_size, header_ptr) = if let Some(header) = header { header_size_tmp = header.len(); (Some(&header_size_tmp), (&header[0] as *const u8).cast()) } else { (None, null()) }; unsafe { (self.udp_write)( self, op_flags, dest_ip, &dest_port, gateway_ip, src_ip, src_port, header_size, header_ptr, &buffer.len(), (&buffer[0] as *const u8).cast(), ) } .to_result() } /// Reads a UDP packet from the network interface. #[allow(clippy::too_many_arguments)] pub fn udp_read( &mut self, op_flags: UdpOpFlags, dest_ip: Option<&mut IpAddress>, dest_port: Option<&mut u16>, src_ip: Option<&mut IpAddress>, src_port: Option<&mut u16>, header: Option<&mut [u8]>, buffer: &mut [u8], ) -> Result { let header_size_tmp; let (header_size, header_ptr) = if let Some(header) = header { header_size_tmp = header.len(); (Some(&header_size_tmp), (&mut header[0] as *mut u8).cast()) } else { (None, null_mut()) }; let mut buffer_size = buffer.len(); let status = unsafe { (self.udp_read)( self, op_flags, dest_ip, dest_port, src_ip, src_port, header_size, header_ptr, &mut buffer_size, (&mut buffer[0] as *mut u8).cast(), ) }; status.to_result_with_val(|| buffer_size) } /// Updates the IP receive filters of a network device and enables software /// filtering. pub fn set_ip_filter(&mut self, new_filter: &IpFilter) -> Result { (self.set_ip_filter)(self, new_filter).to_result() } /// Uses the ARP protocol to resolve a MAC address. pub fn arp(&mut self, ip_addr: &IpAddress, mac_addr: Option<&mut MacAddress>) -> Result { (self.arp)(self, ip_addr, mac_addr).to_result() } /// Updates the parameters that affect the operation of the PXE Base Code /// Protocol. pub fn set_parameters( &mut self, new_auto_arp: Option, new_send_guid: Option, new_ttl: Option, new_tos: Option, new_make_callback: Option, ) -> Result { (self.set_parameters)( self, new_auto_arp.as_ref(), new_send_guid.as_ref(), new_ttl.as_ref(), new_tos.as_ref(), new_make_callback.as_ref(), ) .to_result() } /// Updates the station IP address and/or subnet mask values of a network /// device. pub fn set_station_ip( &mut self, new_station_ip: Option<&IpAddress>, new_subnet_mask: Option<&IpAddress>, ) -> Result { (self.set_station_ip)(self, new_station_ip, new_subnet_mask).to_result() } /// Updates the contents of the cached DHCP and Discover packets. #[allow(clippy::too_many_arguments)] pub fn set_packets( &mut self, new_dhcp_discover_valid: Option, new_dhcp_ack_received: Option, new_proxy_offer_received: Option, new_pxe_discover_valid: Option, new_pxe_reply_received: Option, new_pxe_bis_reply_received: Option, new_dhcp_discover: Option<&Packet>, new_dhcp_ack: Option<&Packet>, new_proxy_offer: Option<&Packet>, new_pxe_discover: Option<&Packet>, new_pxe_reply: Option<&Packet>, new_pxe_bis_reply: Option<&Packet>, ) -> Result { (self.set_packets)( self, new_dhcp_discover_valid.as_ref(), new_dhcp_ack_received.as_ref(), new_proxy_offer_received.as_ref(), new_pxe_discover_valid.as_ref(), new_pxe_reply_received.as_ref(), new_pxe_bis_reply_received.as_ref(), new_dhcp_discover, new_dhcp_ack, new_proxy_offer, new_pxe_discover, new_pxe_reply, new_pxe_bis_reply, ) .to_result() } /// Returns a reference to the `Mode` struct. #[must_use] pub const fn mode(&self) -> &Mode { unsafe { &*self.mode } } } /// A type of bootstrap to perform in [`BaseCode::discover`]. /// /// Corresponds to the `EFI_PXE_BASE_CODE_BOOT_` constants in the C API. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(u16)] #[allow(missing_docs)] pub enum BootstrapType { Bootstrap = 0, MsWinntRis = 1, IntelLcm = 2, DosUndi = 3, NecEsmpro = 4, IbmWsoD = 5, IbmLccm = 6, CaUnicenterTng = 7, HpOpenview = 8, Altiris9 = 9, Altiris10 = 10, Altiris11 = 11, // NOT_USED_12 = 12, RedhatInstall = 13, RedhatBoot = 14, Rembo = 15, Beoboot = 16, // // Values 17 through 32767 are reserved. // Values 32768 through 65279 are for vendor use. // Values 65280 through 65534 are reserved. // PxeTest = 65535, } opaque_type! { /// Opaque type that should be used to represent a pointer to a [`DiscoverInfo`] in /// foreign function interfaces. This type produces a thin pointer, unlike /// [`DiscoverInfo`]. pub struct FfiDiscoverInfo; } /// This struct contains optional parameters for [`BaseCode::discover`]. /// /// Corresponds to the `EFI_PXE_BASE_CODE_DISCOVER_INFO` type in the C API. #[repr(C)] #[derive(Debug, Pointee)] pub struct DiscoverInfo { use_m_cast: bool, use_b_cast: bool, use_u_cast: bool, must_use_list: bool, server_m_cast_ip: IpAddress, ip_cnt: u16, srv_list: [Server], } impl DiscoverInfo { /// Create a `DiscoverInfo`. pub fn new_in_buffer<'buf>( buffer: &'buf mut [MaybeUninit], use_m_cast: bool, use_b_cast: bool, use_u_cast: bool, must_use_list: bool, server_m_cast_ip: IpAddress, srv_list: &[Server], ) -> Result<&'buf mut Self> { let server_count = srv_list.len(); assert!(server_count <= u16::MAX as usize, "too many servers"); let required_size = core::mem::size_of::() * 4 + core::mem::size_of::() + core::mem::size_of::() + core::mem::size_of_val(srv_list); if buffer.len() < required_size { return Err(Status::BUFFER_TOO_SMALL.into()); } let mut ptr: *mut u8 = maybe_uninit_slice_as_mut_ptr(buffer); unsafe { ptr_write_unaligned_and_add(&mut ptr, use_m_cast); ptr_write_unaligned_and_add(&mut ptr, use_b_cast); ptr_write_unaligned_and_add(&mut ptr, use_u_cast); ptr_write_unaligned_and_add(&mut ptr, must_use_list); ptr_write_unaligned_and_add(&mut ptr, server_m_cast_ip); ptr_write_unaligned_and_add(&mut ptr, server_count as u16); ptr = ptr.add(2); // Align server list (4-byte alignment). core::ptr::copy(srv_list.as_ptr(), ptr.cast(), server_count); let ptr: *mut Self = ptr_meta::from_raw_parts_mut(buffer.as_mut_ptr().cast(), server_count); Ok(&mut *ptr) } } } impl DiscoverInfo { /// Returns whether discovery should use multicast. #[must_use] pub const fn use_m_cast(&self) -> bool { self.use_m_cast } /// Returns whether discovery should use broadcast. #[must_use] pub const fn use_b_cast(&self) -> bool { self.use_b_cast } /// Returns whether discovery should use unicast. #[must_use] pub const fn use_u_cast(&self) -> bool { self.use_u_cast } /// Returns whether discovery should only accept boot servers in the server /// list (boot server verification). #[must_use] pub const fn must_use_list(&self) -> bool { self.must_use_list } /// Returns the address used in multicast discovery. #[must_use] pub const fn server_m_cast_ip(&self) -> &IpAddress { &self.server_m_cast_ip } /// Returns the amount of Boot Server. #[must_use] pub const fn ip_cnt(&self) -> u16 { self.ip_cnt } /// Returns the Boot Server list used for unicast discovery or boot server /// verification. #[must_use] pub const fn srv_list(&self) -> &[Server] { &self.srv_list } } /// An entry in the Boot Server list /// /// Corresponds to the `EFI_PXE_BASE_CODE_SRVLIST` type in the C API. #[repr(C)] #[derive(Debug)] pub struct Server { /// The type of Boot Server reply pub ty: u16, accept_any_response: bool, _reserved: u8, /// The IP address of the server ip_addr: IpAddress, } impl Server { /// Construct a `Server` for a Boot Server reply type. If `ip_addr` is not /// `None` only Boot Server replies with matching the IP address will be /// accepted. #[must_use] pub fn new(ty: u16, ip_addr: Option) -> Self { Self { ty, accept_any_response: ip_addr.is_none(), _reserved: 0, ip_addr: ip_addr.unwrap_or(IpAddress([0; 16])), } } /// Returns a `None` if the any response should be accepted or the IP /// address of a Boot Server whose responses should be accepted. #[must_use] pub const fn ip_addr(&self) -> Option<&IpAddress> { if self.accept_any_response { None } else { Some(&self.ip_addr) } } } /// Corresponds to the `EFI_PXE_BASE_CODE_TFTP_OPCODE` type in the C API. #[repr(C)] enum TftpOpcode { TftpGetFileSize = 1, TftpReadFile, TftpWriteFile, TftpReadDirectory, MtftpGetFileSize, MtftpReadFile, MtftpReadDirectory, } /// MTFTP connection parameters /// /// Corresponds to the `EFI_PXE_BASE_CODE_MTFTP_INFO` type in the C API. #[derive(Clone, Copy, Debug)] #[repr(C)] pub struct MtftpInfo { /// File multicast IP address. This is the IP address to which the server /// will send the requested file. pub m_cast_ip: IpAddress, /// Client multicast listening port. This is the UDP port to which the /// server will send the requested file. pub c_port: u16, /// Server multicast listening port. This is the UDP port on which the /// server listens for multicast open requests and data acks. pub s_port: u16, /// The number of seconds a client should listen for an active multicast /// session before requesting a new multicast session. pub listen_timeout: u16, /// The number of seconds a client should wait for a packet from the server /// before retransmitting the previous open request or data ack packet. pub transmit_timeout: u16, } // No corresponding type in the UEFI spec, it just uses UINT16. bitflags! { /// Flags for UDP read and write operations. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct UdpOpFlags: u16 { /// Receive a packet sent from any IP address in UDP read operations. const ANY_SRC_IP = 0x0001; /// Receive a packet sent from any UDP port in UDP read operations. If /// the source port is no specified in UDP write operations, the /// source port will be automatically selected. const ANY_SRC_PORT = 0x0002; /// Receive a packet sent to any IP address in UDP read operations. const ANY_DEST_IP = 0x0004; /// Receive a packet sent to any UDP port in UDP read operations. const ANY_DEST_PORT = 0x0008; /// The software filter is used in UDP read operations. const USE_FILTER = 0x0010; /// If required, a UDP write operation may be broken up across multiple packets. const MAY_FRAGMENT = 0x0020; } } /// IP receive filter settings /// /// Corresponds to the `EFI_PXE_BASE_CODE_IP_FILTER` type in the C API. #[repr(C)] #[derive(Debug)] pub struct IpFilter { /// A set of filters. pub filters: IpFilters, ip_cnt: u8, _reserved: u16, ip_list: [IpAddress; 8], } impl IpFilter { /// Construct a new `IpFilter`. /// /// # Panics /// /// Panics if `ip_list` contains more than 8 entries. #[must_use] pub fn new(filters: IpFilters, ip_list: &[IpAddress]) -> Self { assert!(ip_list.len() <= 8); let ip_cnt = ip_list.len() as u8; let mut buffer = [IpAddress([0; 16]); 8]; buffer[..ip_list.len()].copy_from_slice(ip_list); Self { filters, ip_cnt, _reserved: 0, ip_list: buffer, } } /// A list of IP addresses other than the Station Ip that should be /// enabled. Maybe be multicast or unicast. #[must_use] pub fn ip_list(&self) -> &[IpAddress] { &self.ip_list[..usize::from(self.ip_cnt)] } } bitflags! { /// IP receive filters. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct IpFilters: u8 { /// Enable the Station IP address. const STATION_IP = 0x01; /// Enable IPv4 broadcast addresses. const BROADCAST = 0x02; /// Enable all addresses. const PROMISCUOUS = 0x04; /// Enable all multicast addresses. const PROMISCUOUS_MULTICAST = 0x08; } } /// A network packet. /// /// Corresponds to the `EFI_PXE_BASE_CODE_PACKET` type in the C API. #[repr(C)] pub union Packet { raw: [u8; 1472], dhcpv4: DhcpV4Packet, dhcpv6: DhcpV6Packet, } impl Debug for Packet { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "") } } impl AsRef<[u8; 1472]> for Packet { fn as_ref(&self) -> &[u8; 1472] { unsafe { &self.raw } } } impl AsRef for Packet { fn as_ref(&self) -> &DhcpV4Packet { unsafe { &self.dhcpv4 } } } impl AsRef for Packet { fn as_ref(&self) -> &DhcpV6Packet { unsafe { &self.dhcpv6 } } } /// A Dhcpv4 Packet. /// /// Corresponds to the `EFI_PXE_BASE_CODE_DHCPV4_PACKET` type in the C API. #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct DhcpV4Packet { /// Packet op code / message type. pub bootp_opcode: u8, /// Hardware address type. pub bootp_hw_type: u8, /// Hardware address length. pub bootp_hw_addr_len: u8, /// Client sets to zero, optionally used by gateways in cross-gateway booting. pub bootp_gate_hops: u8, bootp_ident: u32, bootp_seconds: u16, bootp_flags: u16, /// Client IP address, filled in by client in bootrequest if known. pub bootp_ci_addr: [u8; 4], /// 'your' (client) IP address; filled by server if client doesn't know its own address (`bootp_ci_addr` was 0). pub bootp_yi_addr: [u8; 4], /// Server IP address, returned in bootreply by server. pub bootp_si_addr: [u8; 4], /// Gateway IP address, used in optional cross-gateway booting. pub bootp_gi_addr: [u8; 4], /// Client hardware address, filled in by client. pub bootp_hw_addr: [u8; 16], /// Optional server host name, null terminated string. pub bootp_srv_name: [u8; 64], /// Boot file name, null terminated string, 'generic' name or null in /// bootrequest, fully qualified directory-path name in bootreply. pub bootp_boot_file: [u8; 128], dhcp_magik: u32, /// Optional vendor-specific area, e.g. could be hardware type/serial on request, or 'capability' / remote file system handle on reply. This info may be set aside for use by a third phase bootstrap or kernel. pub dhcp_options: [u8; 56], } impl DhcpV4Packet { /// The expected value for [`Self::dhcp_magik`]. pub const DHCP_MAGIK: u32 = 0x63825363; /// Transaction ID, a random number, used to match this boot request with the responses it generates. #[must_use] pub const fn bootp_ident(&self) -> u32 { u32::from_be(self.bootp_ident) } /// Filled in by client, seconds elapsed since client started trying to boot. #[must_use] pub const fn bootp_seconds(&self) -> u16 { u16::from_be(self.bootp_seconds) } /// The flags. #[must_use] pub const fn bootp_flags(&self) -> DhcpV4Flags { DhcpV4Flags::from_bits_truncate(u16::from_be(self.bootp_flags)) } /// A magic cookie, should be [`Self::DHCP_MAGIK`]. #[must_use] pub const fn dhcp_magik(&self) -> u32 { u32::from_be(self.dhcp_magik) } } bitflags! { /// Represents the 'flags' field for a [`DhcpV4Packet`]. #[repr(transparent)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct DhcpV4Flags: u16 { /// Should be set when the client cannot receive unicast IP datagrams /// until its protocol software has been configured with an IP address. const BROADCAST = 1; } } /// A Dhcpv6 Packet. /// /// Corresponds to the `EFI_PXE_BASE_CODE_DHCPV6_PACKET` type in the C API. #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct DhcpV6Packet { /// The message type. pub message_type: u8, transaction_id: [u8; 3], /// A byte array containing dhcp options. pub dhcp_options: [u8; 1024], } impl DhcpV6Packet { /// The transaction id. #[must_use] pub fn transaction_id(&self) -> u32 { u32::from(self.transaction_id[0]) << 16 | u32::from(self.transaction_id[1]) << 8 | u32::from(self.transaction_id[2]) } } /// The data values in this structure are read-only and are updated by the /// [`BaseCode`]. /// /// Corresponds to the `EFI_PXE_BASE_CODE_MODE` type in the C API. #[repr(C)] #[derive(Debug)] pub struct Mode { /// `true` if this device has been started by calling [`BaseCode::start`]. /// This field is set to `true` by [`BaseCode::start`] and to `false` by /// the [`BaseCode::stop`] function. pub started: bool, /// `true` if the UNDI protocol supports IPv6 pub ipv6_available: bool, /// `true` if this PXE Base Code Protocol implementation supports IPv6. pub ipv6_supported: bool, /// `true` if this device is currently using IPv6. This field is set by /// [`BaseCode::start`]. pub using_ipv6: bool, /// `true` if this PXE Base Code implementation supports Boot Integrity /// Services (BIS). This field is set by [`BaseCode::start`]. pub bis_supported: bool, /// `true` if this device and the platform support Boot Integrity Services /// (BIS). This field is set by [`BaseCode::start`]. pub bis_detected: bool, /// `true` for automatic ARP packet generation, `false` otherwise. This /// field is initialized to `true` by [`BaseCode::start`] and can be /// modified with [`BaseCode::set_parameters`]. pub auto_arp: bool, /// This field is used to change the Client Hardware Address (chaddr) field /// in the DHCP and Discovery packets. Set to `true` to send the SystemGuid /// (if one is available). Set to `false` to send the client NIC MAC /// address. This field is initialized to `false` by [`BaseCode::start`] /// and can be modified with [`BaseCode::set_parameters`]. pub send_guid: bool, /// This field is initialized to `false` by [`BaseCode::start`] and set to /// `true` when [`BaseCode::dhcp`] completes successfully. When `true`, /// [`Self::dhcp_discover`] is valid. This field can also be changed by /// [`BaseCode::set_packets`]. pub dhcp_discover_valid: bool, /// This field is initialized to `false` by [`BaseCode::start`] and set to /// `true` when [`BaseCode::dhcp`] completes successfully. When `true`, /// [`Self::dhcp_ack`] is valid. This field can also be changed by /// [`BaseCode::set_packets`]. pub dhcp_ack_received: bool, /// This field is initialized to `false` by [`BaseCode::start`] and set to /// `true` when [`BaseCode::dhcp`] completes successfully and a proxy DHCP /// offer packet was received. When `true`, [`Self::proxy_offer`] is valid. /// This field can also be changed by [`BaseCode::set_packets`]. pub proxy_offer_received: bool, /// When `true`, [`Self::pxe_discover`] is valid. This field is set to /// `false` by [`BaseCode::start`] and [`BaseCode::dhcp`], and can be set /// to `true` or `false` by [`BaseCode::discover`] and /// [`BaseCode::set_packets`]. pub pxe_discover_valid: bool, /// When `true`, [`Self::pxe_reply`] is valid. This field is set to `false` /// by [`BaseCode::start`] and [`BaseCode::dhcp`], and can be set to `true` /// or `false` by [`BaseCode::discover`] and [`BaseCode::set_packets`]. pub pxe_reply_received: bool, /// When `true`, [`Self::pxe_bis_reply`] is valid. This field is set to /// `false` by [`BaseCode::start`] and [`BaseCode::dhcp`], and can be set /// to `true` or `false` by the [`BaseCode::discover`] and /// [`BaseCode::set_packets`]. pub pxe_bis_reply_received: bool, /// Indicates whether [`Self::icmp_error`] has been updated. This field is /// reset to `false` by [`BaseCode::start`], [`BaseCode::dhcp`], /// [`BaseCode::discover`],[`BaseCode::udp_read`], [`BaseCode::udp_write`], /// [`BaseCode::arp`] and any of the TFTP/MTFTP operations. If an ICMP /// error is received, this field will be set to `true` after /// [`Self::icmp_error`] is updated. pub icmp_error_received: bool, /// Indicates whether [`Self::tftp_error`] has been updated. This field is /// reset to `false` by [`BaseCode::start`] and any of the TFTP/MTFTP /// operations. If a TFTP error is received, this field will be set to /// `true` after [`Self::tftp_error`] is updated. pub tftp_error_received: bool, /// When `false`, callbacks will not be made. When `true`, make callbacks /// to the PXE Base Code Callback Protocol. This field is reset to `false` /// by [`BaseCode::start`] if the PXE Base Code Callback Protocol is not /// available. It is reset to `true` by [`BaseCode::start`] if the PXE Base /// Code Callback Protocol is available. pub make_callbacks: bool, /// The "time to live" field of the IP header. This field is initialized to /// `16` by [`BaseCode::start`] and can be modified by /// [`BaseCode::set_parameters`]. pub ttl: u8, /// The type of service field of the IP header. This field is initialized /// to `0` by [`BaseCode::start`], and can be modified with /// [`BaseCode::set_parameters`]. pub tos: u8, /// The device’s current IP address. This field is initialized to a zero /// address by Start(). This field is set when [`BaseCode::dhcp`] completes /// successfully. This field can also be set by /// [`BaseCode::set_station_ip`]. This field must be set to a valid IP /// address by either [`BaseCode::dhcp`] or [`BaseCode::set_station_ip`] /// before [`BaseCode::discover`], [`BaseCode::udp_read`], /// [`BaseCode::udp_write`], [`BaseCode::arp`] and any of the TFTP/MTFTP /// operations are called. pub station_ip: IpAddress, /// The device's current subnet mask. This field is initialized to a zero /// address by [`BaseCode::start`]. This field is set when /// [`BaseCode::dhcp`] completes successfully. This field can also be set /// by [`BaseCode::set_station_ip`]. This field must be set to a valid /// subnet mask by either [`BaseCode::dhcp`] or /// [`BaseCode::set_station_ip`] before [`BaseCode::discover`], /// [`BaseCode::udp_read`], [`BaseCode::udp_write`], /// [`BaseCode::arp`] or any of the TFTP/MTFTP operations are called. pub subnet_mask: IpAddress, /// Cached DHCP Discover packet. This field is zero-filled by the /// [`BaseCode::start`] function, and is set when [`BaseCode::dhcp`] /// completes successfully. The contents of this field can replaced by /// [`BaseCode::set_packets`]. pub dhcp_discover: Packet, /// Cached DHCP Ack packet. This field is zero-filled by /// [`BaseCode::start`], and is set when [`BaseCode::dhcp`] completes /// successfully. The contents of this field can be replaced by /// [`BaseCode::set_packets`]. pub dhcp_ack: Packet, /// Cached Proxy Offer packet. This field is zero-filled by /// [`BaseCode::start`], and is set when [`BaseCode::dhcp`] completes /// successfully. The contents of this field can be replaced by /// [`BaseCode::set_packets`]. pub proxy_offer: Packet, /// Cached PXE Discover packet. This field is zero-filled by /// [`BaseCode::start`], and is set when [`BaseCode::discover`] completes /// successfully. The contents of this field can be replaced by /// [`BaseCode::set_packets`]. pub pxe_discover: Packet, /// Cached PXE Reply packet. This field is zero-filled by /// [`BaseCode::start`], and is set when [`BaseCode::discover`] completes /// successfully. The contents of this field can be replaced by the /// [`BaseCode::set_packets`] function. pub pxe_reply: Packet, /// Cached PXE BIS Reply packet. This field is zero-filled by /// [`BaseCode::start`], and is set when [`BaseCode::discover`] completes /// successfully. This field can be replaced by [`BaseCode::set_packets`]. pub pxe_bis_reply: Packet, /// The current IP receive filter settings. The receive filter is disabled /// and the number of IP receive filters is set to zero by /// [`BaseCode::start`], and is set by [`BaseCode::set_ip_filter`]. pub ip_filter: IpFilter, /// The number of valid entries in the ARP cache. This field is reset to /// zero by [`BaseCode::start`]. pub arp_cache_entries: u32, /// Array of cached ARP entries. pub arp_cache: [ArpEntry; 8], /// The number of valid entries in the current route table. This field is /// reset to zero by [`BaseCode::start`]. pub route_table_entries: u32, /// Array of route table entries. pub route_table: [RouteEntry; 8], /// ICMP error packet. This field is updated when an ICMP error is received /// and is undefined until the first ICMP error is received. This field is /// zero-filled by [`BaseCode::start`]. pub icmp_error: IcmpError, /// TFTP error packet. This field is updated when a TFTP error is received /// and is undefined until the first TFTP error is received. This field is /// zero-filled by the [`BaseCode::start`] function. pub tftp_error: TftpError, } /// An entry for the ARP cache found in [`Mode::arp_cache`] /// /// Corresponds to the `EFI_PXE_BASE_CODE_ARP_ENTRY` type in the C API. #[repr(C)] #[derive(Debug)] pub struct ArpEntry { /// The IP address. pub ip_addr: IpAddress, /// The mac address of the device that is addressed by [`Self::ip_addr`]. pub mac_addr: MacAddress, } /// An entry for the route table found in [`Mode::route_table`] /// /// Corresponds to the `EFI_PXE_BASE_CODE_ROUTE_ENTRY` type in the C API. #[repr(C)] #[allow(missing_docs)] #[derive(Debug)] pub struct RouteEntry { pub ip_addr: IpAddress, pub subnet_mask: IpAddress, pub gw_addr: IpAddress, } /// An ICMP error packet. /// /// Corresponds to the `EFI_PXE_BASE_CODE_ICMP_ERROR` type in the C API. #[repr(C)] #[allow(missing_docs)] #[derive(Debug)] pub struct IcmpError { pub ty: u8, pub code: u8, pub checksum: u16, pub u: IcmpErrorUnion, pub data: [u8; 494], } impl Display for IcmpError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } #[cfg(feature = "unstable")] impl core::error::Error for IcmpError {} /// Corresponds to the anonymous union inside /// `EFI_PXE_BASE_CODE_ICMP_ERROR` in the C API. #[repr(C)] #[allow(missing_docs)] pub union IcmpErrorUnion { pub reserved: u32, pub mtu: u32, pub pointer: u32, pub echo: IcmpErrorEcho, } impl Debug for IcmpErrorUnion { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "") } } /// Corresponds to the `Echo` field in the anonymous union inside /// `EFI_PXE_BASE_CODE_ICMP_ERROR` in the C API. #[repr(C)] #[derive(Clone, Copy, Debug)] #[allow(missing_docs)] pub struct IcmpErrorEcho { pub identifier: u16, pub sequence: u16, } /// A TFTP error packet. /// /// Corresponds to the `EFI_PXE_BASE_CODE_TFTP_ERROR` type in the C API. #[repr(C)] #[allow(missing_docs)] #[derive(Debug)] pub struct TftpError { pub error_code: u8, pub error_string: [u8; 127], } impl Display for TftpError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } #[cfg(feature = "unstable")] impl core::error::Error for TftpError {} /// Returned by [`BaseCode::tftp_read_dir`]. #[allow(missing_docs)] #[derive(Debug)] pub struct TftpFileInfo<'a> { pub filename: &'a CStr8, pub size: u64, pub year: u16, pub month: u8, pub day: u8, pub hour: u8, pub minute: u8, pub second: f32, } /// Returned by [`BaseCode::mtftp_read_dir`]. #[allow(missing_docs)] #[derive(Debug)] pub struct MtftpFileInfo<'a> { pub filename: &'a CStr8, pub ip_address: IpAddress, pub size: u64, pub year: u16, pub month: u8, pub day: u8, pub hour: u8, pub minute: u8, pub second: f32, } /// Returned if a server sends a malformed response in /// [`BaseCode::tftp_read_dir`] or [`BaseCode::mtftp_read_dir`]. #[derive(Clone, Copy, Debug)] pub struct ReadDirParseError; impl Display for ReadDirParseError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } #[cfg(feature = "unstable")] impl core::error::Error for ReadDirParseError {}