From 2af4d225a8726d738985c1202d509833a3e4beb3 Mon Sep 17 00:00:00 2001 From: Jay Kickliter Date: Tue, 14 Oct 2025 17:56:21 -0600 Subject: [PATCH 1/2] uefi-raw: add tcpv4 protocol definitions --- .typos.toml | 4 +- uefi-raw/CHANGELOG.md | 1 + uefi-raw/src/protocol/network/mod.rs | 1 + uefi-raw/src/protocol/network/tcpv4.rs | 513 +++++++++++++++++++++++++ 4 files changed, 518 insertions(+), 1 deletion(-) create mode 100644 uefi-raw/src/protocol/network/tcpv4.rs diff --git a/.typos.toml b/.typos.toml index 5dc809c9e..bacfe1b94 100644 --- a/.typos.toml +++ b/.typos.toml @@ -9,7 +9,9 @@ extend-exclude = [ [default] extend-ignore-identifiers-re = [ # uefi-raw/src/protocol/device_path.rs - "PnP" + "PnP", + # uefi-raw/src/protocol/network/tcpv4.rs + "ANDed" ] [default.extend-words] diff --git a/uefi-raw/CHANGELOG.md b/uefi-raw/CHANGELOG.md index 1ff1e6625..913fc3dc6 100644 --- a/uefi-raw/CHANGELOG.md +++ b/uefi-raw/CHANGELOG.md @@ -1,6 +1,7 @@ # uefi-raw - [Unreleased] ## Added +- Added `Tcpv4Protocol`. ## Changed diff --git a/uefi-raw/src/protocol/network/mod.rs b/uefi-raw/src/protocol/network/mod.rs index f2a016cfc..84bc11091 100644 --- a/uefi-raw/src/protocol/network/mod.rs +++ b/uefi-raw/src/protocol/network/mod.rs @@ -6,4 +6,5 @@ pub mod ip4; pub mod ip4_config2; pub mod pxe; pub mod snp; +pub mod tcpv4; pub mod tls; diff --git a/uefi-raw/src/protocol/network/tcpv4.rs b/uefi-raw/src/protocol/network/tcpv4.rs new file mode 100644 index 000000000..35f9a215f --- /dev/null +++ b/uefi-raw/src/protocol/network/tcpv4.rs @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! TCPv4 Protocol +//! +//! This module provides the TCPv4 Protocol interface definitions. The +//! TCPv4 Protocol provides services to send and receive data streams +//! over IPv4 networks. +//! +//! The protocol is defined in the [UEFI Specification, Section 28.1](https://uefi.org/specs/UEFI/2.11/28_Network_Protocols_TCP_IP_and_Configuration.html#efi-tcpv4-protocol). + +use crate::protocol::network::snp::NetworkMode; +use crate::{Boolean, Event, Guid, Handle, Ipv4Address, Status, guid, newtype_enum}; +use core::ffi::c_void; +use core::fmt::{Debug, Formatter}; + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4Protocol { + /// Get the current operational status. + /// + /// `get_mode_data` copies the current operational settings of + /// this instance into user-supplied structs. This function can + /// also be used to retrieve the operational setting of underlying + /// drivers such as IPv4, MNP, or SNP. + #[allow(clippy::type_complexity)] + pub get_mode_data: unsafe extern "efiapi" fn( + this: *mut Self, + connection_state: *mut Tcpv4ConnectionState, + config_data: *mut Tcpv4ConfigData, + ip4_mode_data: *mut Ipv4ModeData, + managed_network_config_data: *mut c_void, + simple_network_mode: *mut NetworkMode, + ) -> Status, + + /// Initialize or brutally reset the operational parameters for + /// this instance. + /// + /// No other TCPv4 Protocol operation can be executed by this + /// instance until it is configured properly. For an active TCPv4 + /// instance, after a proper configuration it may call + /// [`Tcpv4Protocol::connect`] to initiates the three-way + /// handshake. For a passive TCPv4 instance, its state will + /// transition to [`Tcpv4ConnectionState::LISTEN`] after + /// configuration, and [`accept`][Self::accept] may be called to + /// listen for incoming TCP connection requests. If `config_data` + /// is set to `NULL`, the instance is reset. Resetting process + /// will be done brutally; the state machine will be set to + /// [`Tcpv4ConnectionState::CLOSED`] directly, the receive queue + /// and transmit queue will be flushed, and no traffic will be + /// allowed through this instance. + pub configure: + unsafe extern "efiapi" fn(this: *mut Self, config_data: *const Tcpv4ConfigData) -> Status, + + /// Add or delete routing entries. + /// + /// The most specific route is selected by comparing the + /// `subnet_address` with the destination IP address + /// arithmetically ANDed with the `subnet_mask`. + /// + /// The default route is added with both `subnet_address` and + /// `subnet_mask` set to `0.0.0.0`. The default route matches all + /// destination IP addresses if there is no more specific route. + /// + /// A direct route is added with `gateway_address` set to + /// `0.0.0.0`. Packets are sent to the destination host if its + /// address can be found in the Address Resolution Protocol (ARP) + /// cache or it is on the local subnet. If the instance is + /// configured to use default address, a direct route to the local + /// network will be added automatically. + /// + /// Each TCP instance has its own independent routing table. An + /// instance that uses the default IP address will have a copy of + /// the + /// [`Ipv4Config2Protocol`][super::ip4_config2::Ip4Config2Protocol]'s + /// routing table. The copy will be updated automatically whenever + /// the IP driver reconfigures its instance. As a result, the + /// previous modification to the instance's local copy will be + /// lost. + /// + /// The priority of checking the route table is specific to the IP + /// implementation, and every IP implementation must comply with + /// RFC 1122. + /// + /// Note: There is no way to set up routes to other network + /// interface cards (NICs) because each NIC has its own + /// independent network stack that shares information only through + /// EFI TCPv4 variable. + pub routes: unsafe extern "efiapi" fn( + this: *mut Self, + delete_route: Boolean, + subnet_address: *const Ipv4Address, + subnet_mask: *const Ipv4Address, + gateway_address: *const Ipv4Address, + ) -> Status, + + /// Initiate a nonblocking TCP connection request for an active + /// TCP instance. + /// + /// `connect` initiates an active open to the remote peer + /// configured in the current TCP instance if it is configured as + /// active. If the connection succeeds or fails due to any error, + /// the [`connection_token.event`][Tcpv4CompletionToken::event] + /// will be signaled and `connection_token.status` will be updated + /// accordingly. This function can only be called for the TCP + /// instance in the [`Tcpv4ConnectionState::CLOSED`] state. The + /// instance will transition to [`Tcpv4ConnectionState::SYN_SENT`] + /// if the function returns [`Status::SUCCESS`]. If the TCP + /// three-way handshake succeeds, its state will become + /// [`Tcpv4ConnectionState::ESTABLISHED`], otherwise, the state + /// will return to [`Tcpv4ConnectionState::CLOSED`]. + pub connect: unsafe extern "efiapi" fn( + this: *mut Self, + connection_token: *mut Tcpv4CompletionToken, + ) -> Status, + + /// Listen on the passive instance to accept an incoming + /// connection request. This is a nonblocking operation. + /// + /// The `accept` function initiates an asynchronous accept request + /// to wait for an incoming connection on the passive TCP + /// instance. If a remote peer successfully establishes a + /// connection with this instance, a new TCP instance will be + /// created and its handle will be returned in + /// [`new_child_handle`][Tcpv4ListenToken::new_child_handle]. The + /// newly created instance is configured by inheriting the passive + /// instance's configuration and is ready for use upon return. The + /// instance is in the [`Tcpv4ConnectionState::ESTABLISHED`] + /// state. + /// + /// The [`new_child_handle`][Tcpv4ListenToken::new_child_handle] + /// will be signaled when a new connection is accepted, the user + /// aborts the listen, or the connection is reset. + /// + /// This function can only be called when the current TCP instance + /// is in [`Tcpv4ConnectionState::LISTEN`] state. + pub accept: + unsafe extern "efiapi" fn(this: *mut Self, listen_token: *mut Tcpv4ListenToken) -> Status, + + /// Queues outgoing data into the transmit queue. + /// + /// The `transmit` function queues a sending request to this + /// instance along with the user data. The status of the token is + /// updated and the event in the token will be signaled once the + /// data is sent out or some error occurs. + pub transmit: unsafe extern "efiapi" fn(this: *mut Self, token: *mut Tcpv4IoToken) -> Status, + + /// Places an asynchronous receive request into the receiving + /// queue. + /// + /// `receive` places a completion token into the receive packet + /// queue. This function is always asynchronous. The caller must + /// allocate the + /// [`token.completion_token.event`][Tcpv4CompletionToken::event] + /// and the `fragment_buffer` used to receive data. They also must + /// fill [`data_length`][Tcpv4ReceiveData::data_length] which + /// represents the whole length of all `fragment_buffer`. When the + /// receive operation completes, the driver updates the + /// [`token.completion_token.status`][Tcpv4CompletionToken::status] + /// and [`token.packet.rx_data`][Tcpv4Packet::rx_data] fields and + /// and the `token.completion_token.event` is signaled. If data + /// was received, the data and its length will be copied into the + /// `fragment_table`. At the same time, the full length of + /// received data will be recorded in the `data_length` + /// fields. Providing a proper notification function and context + /// for the event will enable the user to receive the notification + /// and receiving status. That notification function is guaranteed + /// to not be re-entered. + pub receive: unsafe extern "efiapi" fn(this: *mut Self, token: *mut Tcpv4IoToken) -> Status, + + /// Disconnect a TCP connection gracefully or reset a TCP + /// connection. This function is a nonblocking operation. + /// + /// Initiate an asynchronous close token to TCP driver. After + /// `close` is called, any buffered transmission data will be sent + /// by TCP driver and the current instance will have a graceful + /// close working flow described as RFC 793 if `abort_on_close` is + /// set to `false`, otherwise, a reset packet will be sent by the + /// TCP driver to quickly disconnect this connection. When the + /// close operation completes successfully the TCP instance is in + /// [`Tcpv4ConnectionState::CLOSED`] state, all pending + /// asynchronous operations are signaled, and any buffers used for + /// TCP network traffic are flushed. + pub close: + unsafe extern "efiapi" fn(this: *mut Self, close_token: *mut Tcpv4CloseToken) -> Status, + + /// Abort an asynchronous connection, listen, transmission or + /// receive request. + /// + /// The `cancel` function aborts a pending connection, listen, + /// transmit or receive request. If `completion_token` is not + /// `NULL` and the token is in the connection, listen, + /// transmission or receive queue when it is being cancelled, its + /// `status` will be set to [`Status::ABORTED`] and then `event` + /// will be signaled. If the token is not in one of the queues, + /// which usually means that the asynchronous operation has + /// completed, [`Status::NOT_FOUND`] is returned. If + /// `completion_token` is `NULL`, all asynchronous tokens issued + /// by [`Tcpv4Protocol::connect`], [`Tcpv4Protocol::accept`], + /// [`Tcpv4Protocol::transmit`] and [`Tcpv4Protocol::receive`] + /// will be aborted. + pub cancel: unsafe extern "efiapi" fn( + this: *mut Self, + completion_token: *mut Tcpv4CompletionToken, + ) -> Status, + + /// Poll to receive incoming data and transmit outgoing segments. + /// + /// The `poll` function increases the rate that data is moved + /// between the network and application and can be called when the + /// TCP instance is created successfully. Its use is optional. + /// + /// In some implementations, the periodical timer in the MNP + /// driver may not poll the underlying communications device fast + /// enough to avoid dropping packets. Drivers and applications + /// that are experiencing packet loss should try calling the + /// `poll` function at a high frequency. + pub poll: unsafe extern "efiapi" fn(this: *mut Self) -> Status, +} + +impl Tcpv4Protocol { + /// The GUID for the TCPv4 protocol. + /// + /// Defined in the [UEFI Specification, Section 28.1.4](https://uefi.org/specs/UEFI/2.11/28_Network_Protocols_TCP_IP_and_Configuration.html#efi-tcp4-protocol). + pub const GUID: Guid = guid!("65530BC7-A359-410F-B010-5AADC7EC2B62"); + + /// The GUID for the TCPv4 service binding protocol. + /// + /// Defined in the [UEFI Specification, Section 28.1.2](https://uefi.org/specs/UEFI/2.11/28_Network_Protocols_TCP_IP_and_Configuration.html#efi-tcp4-service-binding-protocol). + pub const SERVICE_BINDING_GUID: Guid = guid!("00720665-67EB-4a99-BAF7-D3C33A1C7CC9"); +} + +newtype_enum! { + pub enum Tcpv4ConnectionState: i32 => { + CLOSED = 0, + LISTEN = 1, + SYN_SENT = 2, + SYN_RECEIVED = 3, + ESTABLISHED = 4, + FIN_WAIT1 = 5, + FIN_WAIT2 = 6, + CLOSING = 7, + TIME_WAIT = 8, + CLOSE_WAIT = 9, + LAST_ACK = 10, + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct Ipv4ModeData { + /// Set to `TRUE` after this [`Tcpv4Protocol`] instance has been + /// successfully configured. + pub is_started: Boolean, + /// The maximum packet size, in bytes, of the packet which the + /// upper layer driver could feed. + pub max_packet_size: u32, + /// Current configuration settings. + pub config_data: Ipv4ConfigData, + /// Set to `TRUE` when the [`Tcpv4Protocol`] instance has a + /// station address and subnet mask. + pub is_configured: Boolean, + /// Number of joined multicast groups. + pub group_count: u32, + /// List of joined multicast group addresses. + pub group_table: *const Ipv4Address, + /// Number of entries in the routing table. + pub route_count: u32, + /// Routing table entries. + pub ip4_route_table: *const Ipv4RouteTable, + /// Number of entries in the supported ICMP types list. + pub icmp_type_count: u32, + /// Array of ICMP types and codes that are supported. + pub icmp_type_list: *const Ipv4IcmpType, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Ipv4ConfigData { + /// Default protocol to be used. + /// + /// See . + pub default_protocol: u8, + /// Set to `TRUE` to receive all IPv4 packets. + pub accept_any_protocol: Boolean, + /// Set to `TRUE` to receive ICMP error packets. + pub accept_icmp_errors: Boolean, + /// Set to `TRUE` to receive broadcast IPv4 packets. + pub accept_broadcast: Boolean, + /// Set to `TRUE` to receive all IPv4 packets in promiscuous mode. + pub accept_promiscuous: Boolean, + /// Set to `TRUE` to use the default IPv4 address and routing + /// table. + pub use_default_address: Boolean, + /// Station IPv4 address. + pub station_address: Ipv4Address, + /// Subnet mask for the station address. + pub subnet_mask: Ipv4Address, + /// Type of service field in transmitted IPv4 packets. + pub type_of_service: u8, + /// Time to live field in transmitted IPv4 packets. + pub time_to_live: u8, + /// Set to `TRUE` to disable fragmentation. + pub do_not_fragment: Boolean, + /// Set to `TRUE` to enable raw data mode. + pub raw_data: Boolean, + /// Receive timeout in milliseconds. + pub receive_timeout: u32, + /// Transmit timeout in milliseconds. + pub transmit_timeout: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Ipv4RouteTable { + /// The subnet address. + pub subnet_address: Ipv4Address, + /// The subnet mask. + pub subnet_mask: Ipv4Address, + /// The gateway address. + pub gateway_address: Ipv4Address, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Ipv4IcmpType { + /// ICMP message type. + pub type_: u8, + /// ICMP message code. + pub code: u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4ConfigData { + /// Type of service field in transmitted IPv4 packets. + pub type_of_service: u8, + /// Time to live field in transmitted IPv4 packets. + pub time_to_live: u8, + /// Access point configuration. + pub access_point: Tcpv4AccessPoint, + /// Optional TCP configuration parameters. + pub control_option: *mut Tcpv4Option, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct Tcpv4AccessPoint { + /// Set to `TRUE` to use the default IP address. + pub use_default_address: Boolean, + /// The local IP address assigned to this TCP instance. + pub station_address: Ipv4Address, + /// The subnet mask associated with the station address. + pub subnet_mask: Ipv4Address, + /// The local port number. + pub station_port: u16, + /// The remote IP address. + pub remote_address: Ipv4Address, + /// The remote port number. + pub remote_port: u16, + /// Set to `TRUE` for active open, `FALSE` for passive open. + pub active_flag: Boolean, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4Option { + /// Size of the TCP receive buffer. + pub receive_buffer_size: u32, + /// Size of the TCP send buffer. + pub send_buffer_size: u32, + /// Maximum number of pending connections for passive instances. + pub max_syn_back_log: u32, + /// Connection timeout in seconds. + pub connection_timeout: u32, + /// Number of data retransmission attempts. + pub data_retries: u32, + /// FIN timeout in seconds. + pub fin_timeout: u32, + /// TIME_WAIT timeout in seconds. + pub time_wait_timeout: u32, + /// Number of keep-alive probes. + pub keep_alive_probes: u32, + /// Time before sending keep-alive probes in seconds. + pub keep_alive_time: u32, + /// Interval between keep-alive probes in seconds. + pub keep_alive_interval: u32, + /// Set to `TRUE` to enable Nagle algorithm. + pub enable_nagle: Boolean, + /// Set to `TRUE` to enable TCP timestamps. + pub enable_time_stamp: Boolean, + /// Set to `TRUE` to enable window scaling. + pub enable_window_scaling: Boolean, + /// Set to `TRUE` to enable selective acknowledgment. + pub enable_selective_ack: Boolean, + /// Set to `TRUE` to enable path MTU discovery. + pub enable_path_mtu_discovery: Boolean, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4CompletionToken { + /// Event to signal when the operation completes. + pub event: Event, + /// Status of the completed operation. + pub status: Status, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4ListenToken { + /// Completion token for the listen operation. + pub completion_token: Tcpv4CompletionToken, + /// The new TCP instance handle created for the established + /// connection. + pub new_child_handle: Handle, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4IoToken { + /// Completion token for the I/O operation. + pub completion_token: Tcpv4CompletionToken, + /// Packet data for the I/O operation. + pub packet: Tcpv4Packet, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4CloseToken { + /// Completion token for the close operation. + pub completion_token: Tcpv4CompletionToken, + /// Abort the TCP connection on close instead of the standard TCP + /// close process when it is set to TRUE. This option can be used + /// to satisfy a fast disconnect. + pub abort_on_close: Boolean, +} + +#[repr(C)] +pub union Tcpv4Packet { + /// Pointer to receive data structure. + pub rx_data: *mut Tcpv4ReceiveData, + /// Pointer to transmit data structure. + pub tx_data: *mut Tcpv4TransmitData, +} + +impl Debug for Tcpv4Packet { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Tcpv4Packet").finish() + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4FragmentData { + /// Length of the fragment in bytes. + pub fragment_length: u32, + /// Pointer to an array of contiguous bytes, at least + /// `fragment_length` in length. + pub fragment_buf: *mut u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4ReceiveData { + /// When `TRUE`, the instance is in urgent mode. The + /// implementations of this specification should follow RFC793 to + /// process urgent data, and should NOT mix the data across the + /// urgent point in one token. + pub urgent: Boolean, + /// When calling [`receive`][Tcpv4Protocol::receive], the caller + /// is responsible for setting it to the byte counts of all + /// fragment buffers. When the token is signaled by TCPv4 driver + /// it is the length of received data in the fragments. + pub data_length: u32, + /// Number of fragments in the following fragment table. + pub fragment_count: u32, + /// Variable-length array of fragment descriptors. + /// + /// NOTE: this is a flexible array member. + pub fragment_table: [Tcpv4FragmentData; 0], +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4TransmitData { + /// If `TRUE`, data must be transmitted promptly, and the PUSH bit + /// in the last TCP segment created will be set. If `FALSE`, data + /// transmission may be delayed to combine with data from + /// subsequent [`transmit`][Tcpv4Protocol::transmit] for + /// efficiency. + pub push: Boolean, + /// The data in the fragment table are urgent and urgent point is + /// in effect if `TRUE`. Otherwise those data are NOT considered + /// urgent. + pub urgent: Boolean, + /// Total length of data to transmit. + pub data_length: u32, + /// Number of fragments in the following fragment table. + pub fragment_count: u32, + /// Variable-length array of fragment descriptors. + /// + /// NOTE: this is a flexible array member. + pub fragment_table: [Tcpv4FragmentData; 0], +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4ClientConnectionModeParams { + /// Remote IP address for the connection. + pub remote_ip: Ipv4Address, + /// Remote port for the connection. + pub remote_port: u16, +} From dee28f731ef082d7249fbafdb0b738b73376e697 Mon Sep 17 00:00:00 2001 From: Jay Kickliter Date: Wed, 22 Oct 2025 11:48:58 -0600 Subject: [PATCH 2/2] uefi: add high-level wrapper around raw tcpv4 protoco --- uefi/CHANGELOG.md | 1 + uefi/src/proto/network/mod.rs | 1 + uefi/src/proto/network/tcpv4.rs | 639 ++++++++++++++++++++++++++++++++ 3 files changed, 641 insertions(+) create mode 100755 uefi/src/proto/network/tcpv4.rs diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 1ce17987e..661e9991f 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -1,6 +1,7 @@ # uefi - [Unreleased] ## Added +- Added `Tcpv4` protocol and types. ## Changed diff --git a/uefi/src/proto/network/mod.rs b/uefi/src/proto/network/mod.rs index 58f7eefcd..125607e71 100644 --- a/uefi/src/proto/network/mod.rs +++ b/uefi/src/proto/network/mod.rs @@ -21,5 +21,6 @@ pub mod http; pub mod ip4config2; pub mod pxe; pub mod snp; +pub mod tcpv4; pub use uefi_raw::MacAddress as EfiMacAddr; diff --git a/uefi/src/proto/network/tcpv4.rs b/uefi/src/proto/network/tcpv4.rs new file mode 100755 index 000000000..1b96d5332 --- /dev/null +++ b/uefi/src/proto/network/tcpv4.rs @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +// SPDX-License-Identifier: MIT OR Apache-2.0 +#![allow(unused_imports)] +#![cfg(feature = "alloc")] + +//! TCPv4 protocol. +//! +//! See [Tcpv4]. + +use crate::boot::{self, EventType, Tpl}; +use crate::proto::unsafe_protocol; +use crate::{Error, Event, Handle, Result, ResultExt, Status, StatusExt}; +use core::ffi::c_void; +use core::fmt::Debug; +use core::ptr::{self, NonNull}; +use core::time::Duration; +use core::{array, hint}; +use uefi_raw::Boolean; +use uefi_raw::protocol::driver::ServiceBindingProtocol; +use uefi_raw::protocol::network::tcpv4::{ + Ipv4ModeData, Tcpv4AccessPoint, Tcpv4CompletionToken, Tcpv4ConfigData, Tcpv4ConnectionState, + Tcpv4FragmentData, Tcpv4IoToken, Tcpv4Option, Tcpv4Packet, Tcpv4Protocol, Tcpv4ReceiveData, + Tcpv4TransmitData, +}; +pub use wrappers::{AccessPoint, ConfigData, ConfigOptions}; + +/// A TCPv4 connection. +/// +/// # Examples +/// +/// ```no_run +/// # fn hello_world() -> uefi::Result { +/// # extern crate alloc; +/// use alloc::string::String; +/// use core::net::Ipv4Addr; +/// use uefi::{ +/// boot, print, println, +/// proto::network::tcpv4::{AccessPoint, ConfigData, Tcpv4, +/// Tcpv4ServiceBinding, +/// }, +/// }; +/// use uefi_raw::{ +/// Boolean, protocol::network::tcpv4::Tcpv4AccessPoint, +/// protocol::network::tcpv4::Tcpv4ClientConnectionModeParams, +/// }; +/// +/// let remote_address = Ipv4Addr::new(192, 0, 2, 2); +/// let remote_port = 5050; +/// +/// println!("Connecting to {remote_address:?}:{remote_port}..."); +/// let mut tcp = { +/// let tcp_svc_handle = boot::get_handle_for_protocol::()?; +/// let mut tcp_svc_proto = +/// boot::open_protocol_exclusive::(tcp_svc_handle)?; +/// let tcp_proto_handle = tcp_svc_proto.create_child()?; +/// let mut tcp_proto = boot::open_protocol_exclusive::(tcp_proto_handle)?; +/// let config_data = ConfigData { +/// type_of_service: 0, +/// time_to_live: 255, +/// access_point: AccessPoint { +/// use_default_address: true, +/// // The following two fields are meaningless when +/// // `use_default_address == true` +/// station_address: Ipv4Addr::UNSPECIFIED, +/// subnet_mask: Ipv4Addr::UNSPECIFIED, +/// station_port: 0, +/// remote_address, +/// remote_port, +/// // true => client mode +/// active_flag: true, +/// }, +/// }; +/// tcp_proto +/// .configure(&config_data, None) +/// .expect("configure failed"); +/// tcp_proto.connect()?; +/// tcp_proto +/// }; +/// +/// let tx_msg = "Hello"; +/// println!("Sending {tx_msg:?} over TCP..."); +/// tcp.transmit(tx_msg.as_bytes())?; +/// +/// print!("Received "); +/// let mut buf = [0_u8; 64]; +/// let n = tcp.receive(&mut buf)?; +/// let rx_string = String::from_utf8_lossy(&buf[..n]); +/// println!("{rx_string:?}"); +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(Tcpv4Protocol::GUID)] +pub struct Tcpv4(pub Tcpv4Protocol); + +impl Tcpv4 { + /// See [Tcpv4Protocol::configure]. + pub fn configure(&mut self, config: &ConfigData, options: Option<&ConfigOptions>) -> Result { + let control_option = options.map(Tcpv4Option::from); + let tcpv4_config_data = Tcpv4ConfigData { + type_of_service: config.type_of_service, + time_to_live: config.time_to_live, + access_point: Tcpv4AccessPoint::from(&config.access_point), + control_option: control_option + .as_ref() + .map(|r| ptr::from_ref(r) as *mut _) + .unwrap_or_else(ptr::null_mut), + }; + let mut res = + unsafe { (self.0.configure)(self.this(), ptr::from_ref(&tcpv4_config_data) as *mut _) } + .to_result(); + // Maximum timeout of 10 seconds. + for _ in 0..9 { + match res { + Ok(()) => break, + Err(e) if e.status() == Status::NO_MAPPING => { + log::debug!("DHCP still running, waiting..."); + } + Err(e) => { + log::debug!("Err {e:?}; will spin and try again..."); + } + } + boot::stall(Duration::from_secs(1)); + res = unsafe { + (self.0.configure)(self.this(), ptr::from_ref(&tcpv4_config_data) as *mut _) + } + .to_result(); + } + res + } + + /// See [`Tcpv4Protocol::connect`]. + pub fn connect(&mut self) -> Result { + unsafe { + let event = boot::create_event( + EventType::NOTIFY_WAIT, + Tpl::CALLBACK, + Some(helpers::noop), + None, + )?; + let mut completion_token = helpers::make_completion_token(&event); + (self.0.connect)(self.this(), &mut completion_token).to_result()?; + boot::wait_for_event(&mut [event.unsafe_clone()]).expect("can't fail waiting for event") + }; + Ok(()) + } + + /// See [Tcpv4Protocol::transmit]. + pub fn transmit_vectored(&mut self, data: &[&[u8]; N]) -> Result { + // SAFETY: safe because there is no callback nor callback-data. + let event = unsafe { + boot::create_event( + EventType::NOTIFY_WAIT, + Tpl::CALLBACK, + Some(helpers::noop), + None, + ) + }?; + let tx_data = helpers::TransmitData::new(data); + let mut token = helpers::make_tx_token(&event, &tx_data); + unsafe { (self.0.transmit)(self.this(), &mut token) }.to_result()?; + // See docs on `poll` for why this is crucial for performance. + self.poll()?; + boot::wait_for_event(&mut [event]).discard_errdata()?; + Ok(()) + } + + /// See [`Tcpv4Protocol::transmit`]. + pub fn transmit(&mut self, data: &[u8]) -> Result { + self.transmit_vectored(&[data]) + } + + /// Receives data from the remote connection. On success, returns + /// the number of bytes read. + /// + /// See [Tcpv4Protocol::receive]. + pub fn receive_vectored(&mut self, bufs: &[&mut [u8]]) -> Result { + // SAFETY: safe because there is no callback nor callback-data. + let event = unsafe { + boot::create_event( + EventType::NOTIFY_WAIT, + Tpl::CALLBACK, + Some(helpers::noop), + None, + ) + }?; + let rx_data = helpers::ReceiveData::new(bufs); + let mut token = helpers::make_rx_token(&event, &rx_data); + unsafe { (self.0.receive)(self.this(), &mut token) }.to_result()?; + // See docs on `poll` for why this is crucial for performance. + self.poll()?; + boot::wait_for_event(&mut [event]).discard_errdata()?; + let rx_data_len = rx_data.len(); + Ok(rx_data_len) + } + + /// Receives data from the remote connection. On success, returns + /// the number of bytes read. + /// + /// See [Tcpv4Protocol::receive]. + pub fn receive(&mut self, buf: &mut [u8]) -> Result { + self.receive_vectored(&[buf]) + } + + /// Receives the exact number of bytes required to fill `buf`. + /// + /// This function receives as many bytes as necessary to + /// completely fill the specified buffer `buf`. + pub fn receive_exact(&mut self, mut buf: &mut [u8]) -> Result { + while !buf.is_empty() { + let n = self.receive(buf)?; + buf = &mut buf[n..]; + } + Ok(()) + } +} + +// Private API +impl Tcpv4 { + /// Convenience method to return a non-null pointer to our inner + /// Tcpv4Protocol. + const fn this(&mut self) -> *mut Tcpv4Protocol { + ptr::from_mut(&mut self.0) + } + + /// **28.1.13. EFI_TCP4_PROTOCOL.Poll()**: + /// + /// > The Poll() function polls for incoming data packets and + /// > processes outgoing data packets. Network drivers and + /// > applications can call the EFI_IP4_PROTOCOL .Poll() + /// > function to increase the rate that data packets are + /// > moved between the communications device and the transmit + /// > and receive queues. + /// > + /// > In some systems the periodic timer event may not poll the + /// > underlying communications device fast enough to transmit + /// > and/or receive all data packets without missing incoming + /// > packets or dropping outgoing packets. Drivers and + /// > applications that are experiencing packet loss should + /// > try calling the EFI_IP4_PROTOCOL .Poll() function more + /// > often. + fn poll(&mut self) -> Result { + unsafe { (self.0.poll)(self.this()) }.to_result()?; + Ok(()) + } +} + +/// TCPv4 Service Binding Protocol. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(Tcpv4Protocol::SERVICE_BINDING_GUID)] +pub struct Tcpv4ServiceBinding(ServiceBindingProtocol); + +impl Tcpv4ServiceBinding { + /// Create TCPv4 Protocol Handle. + pub fn create_child(&mut self) -> uefi::Result { + let mut c_handle = ptr::null_mut(); + let status; + let handle; + unsafe { + status = (self.0.create_child)(&mut self.0, &mut c_handle); + handle = Handle::from_ptr(c_handle); + }; + match status { + Status::SUCCESS => Ok(handle.unwrap()), + _ => Err(status.into()), + } + } + + /// Destroy TCPv4 Protocol Handle. + pub fn destroy_child(&mut self, handle: Handle) -> uefi::Result<()> { + let status = unsafe { (self.0.destroy_child)(&mut self.0, handle.as_ptr()) }; + match status { + Status::SUCCESS => Ok(()), + _ => Err(status.into()), + } + } +} + +mod helpers { + use alloc::alloc::{alloc, dealloc}; + use alloc::boxed::Box; + use alloc::vec::Vec; + use core::alloc::Layout; + use core::ffi::c_void; + use core::marker::{PhantomData, PhantomPinned}; + use core::ptr::{self, NonNull, read_volatile}; + use core::{array, mem}; + use uefi::Event; + use uefi_raw::protocol::network::tcpv4::{ + Tcpv4CompletionToken, Tcpv4FragmentData, Tcpv4IoToken, Tcpv4Packet, Tcpv4ReceiveData, + Tcpv4TransmitData, + }; + use uefi_raw::{Boolean, Status}; + + #[derive(Debug)] + #[repr(C)] + pub struct ReceiveData<'a> { + ptr: NonNull, + layout: Layout, + _pd: PhantomData>, + } + + impl<'a> ReceiveData<'a> { + pub fn new(data: &'a [&mut [u8]]) -> Self { + let urgent = Boolean::FALSE; + let data_length = data.iter().map(|d| d.len() as u32).sum(); + let fragment_count = data.len() as u32; + let header_layout = Layout::new::(); + let payload_layout = + Layout::array::(data.len()).expect("overflow not expected"); + let fragment_table: Vec = + data.iter().map(|d| FragmentData::new(d)).collect(); + let (layout, _) = header_layout + .extend(payload_layout) + .expect("overflow not expected"); + let ptr = unsafe { + let ptr = alloc(layout).cast::(); + let mut ptr = NonNull::new(ptr).expect("Allocation failed"); + ptr.as_mut().urgent = urgent; + ptr.as_mut().data_length = data_length; + ptr.as_mut().fragment_count = fragment_count; + ptr::copy_nonoverlapping( + fragment_table.as_ptr() as *mut Tcpv4FragmentData, + ptr.as_mut().fragment_table.as_mut_ptr(), + fragment_table.len(), + ); + ptr + }; + Self { + ptr, + layout, + _pd: PhantomData, + } + } + + pub const fn as_ptr(&self) -> *mut Tcpv4ReceiveData { + self.ptr.as_ptr() + } + + pub const fn as_ref(&self) -> &Tcpv4ReceiveData { + unsafe { self.ptr.as_ref() } + } + + pub fn len(&self) -> usize { + let len = unsafe { ptr::read_volatile(&self.as_ref().data_length) }; + len as usize + } + } + + impl<'a> Drop for ReceiveData<'a> { + fn drop(&mut self) { + unsafe { dealloc(self.ptr.cast::().as_ptr(), self.layout) } + } + } + + /// This is the same as [`Tcpv4TransmitData`], but with generically + /// sized fragment table to allow for vectored writes. + #[derive(Debug)] + #[repr(C)] + pub struct TransmitData<'a> { + ptr: NonNull, + layout: Layout, + _pd: PhantomData>, + } + + impl<'a> TransmitData<'a> { + pub fn new(data: &'a [&[u8]]) -> Self { + let data_length = data.iter().map(|d| d.len() as u32).sum(); + let fragment_table: Vec = + data.iter().map(|d| FragmentData::new(d)).collect(); + let layout = { + let header_layout = Layout::new::(); + let payload_layout = + Layout::array::(data.len()).expect("overflow not expected"); + let (layout, _) = header_layout + .extend(payload_layout) + .expect("overflow not expected"); + layout + }; + let ptr = unsafe { + let ptr = alloc(layout).cast::(); + let mut ptr = NonNull::new(ptr).expect("Allocation failed"); + ptr.as_mut().push = Boolean::FALSE; + ptr.as_mut().urgent = Boolean::FALSE; + ptr.as_mut().data_length = data_length; + ptr.as_mut().fragment_count = data.len() as u32; + ptr::copy_nonoverlapping( + fragment_table.as_ptr() as *mut Tcpv4FragmentData, + ptr.as_mut().fragment_table.as_mut_ptr(), + fragment_table.len(), + ); + ptr + }; + Self { + ptr, + layout, + _pd: PhantomData, + } + } + + pub const fn as_ptr(&self) -> *mut Tcpv4TransmitData { + self.ptr.as_ptr() + } + } + + impl<'a> Drop for TransmitData<'a> { + fn drop(&mut self) { + unsafe { dealloc(self.ptr.cast::().as_ptr(), self.layout) } + } + } + + #[derive(Debug)] + #[repr(C)] + pub struct FragmentData<'a> { + fragment_length: u32, + fragment_buf: NonNull, + _pd: PhantomData<&'a [u8]>, + } + + impl<'a> FragmentData<'a> { + pub const fn new(buf: &'a [u8]) -> Self { + let fragment_length = buf.len() as u32; + let fragment_buf = NonNull::new(ptr::from_ref(buf) as *mut c_void) + .expect("buf is always a valid reference"); + let _pd = PhantomData; + FragmentData { + fragment_length, + fragment_buf, + _pd, + } + } + } + + pub const fn make_completion_token(event: &Event) -> Tcpv4CompletionToken { + Tcpv4CompletionToken { + event: event.as_ptr(), + status: Status::SUCCESS, + } + } + + pub const fn make_rx_token(event: &Event, rx_data: &ReceiveData) -> Tcpv4IoToken { + let rx_data = rx_data.as_ptr(); + let packet = Tcpv4Packet { rx_data }; + let completion_token = make_completion_token(event); + Tcpv4IoToken { + completion_token, + packet, + } + } + + pub const fn make_tx_token(event: &Event, tx_data: &TransmitData) -> Tcpv4IoToken { + let tx_data = tx_data.as_ptr(); + let packet = Tcpv4Packet { tx_data }; + let completion_token = make_completion_token(event); + Tcpv4IoToken { + completion_token, + packet, + } + } + + /// Dummy callback used for TCP events. + #[allow(clippy::missing_const_for_fn)] + pub extern "efiapi" fn noop(_event: Event, _context: Option>) {} +} + +mod wrappers { + use core::net::Ipv4Addr; + use uefi_raw::protocol::network::tcpv4::{Tcpv4AccessPoint, Tcpv4ConfigData, Tcpv4Option}; + + /// See [Tcpv4AccessPoint] + #[derive(Debug, Clone)] + pub struct AccessPoint { + /// Set to `true` to use the default IP address and default + /// routing table. + pub use_default_address: bool, + /// The local IP address assigned to this TCP instance. + /// + /// Not used when `use_default_address` is `true`. + pub station_address: Ipv4Addr, + /// The subnet mask associated with the station address. + /// + /// Not used when `use_default_address` is `true`. + pub subnet_mask: Ipv4Addr, + /// The local port number. + /// + /// Set to 0 to get an ephemeral port. + pub station_port: u16, + /// The remote IP address to which this EFI TCPv4 Protocol + /// instance is connected. + /// + /// If `active_flag` is `true` ('client mode'), the instance + /// will connect to `remote_address`. + /// + /// If `active_flag` is `false` ('serve mode'), the instance + /// only accepts connections from this address. If + /// `active_flag` is `false` and `remote_address` is + /// `0.0.0.0`, the instance will accept connections from any + /// address. + pub remote_address: Ipv4Addr, + /// The remote port number. + /// + /// If `active_flag` is `true` ('client mode'), the instance + /// will connect to the remote on this port. When + /// `active_flag` is `true`, `remote_port` cannot be set to 0. + /// + /// If `active_flag` is `false` ('server mode'), `remote_port` + /// port can be set to 0 to allow connections from any client + /// port. Otherwise, the instance will only accept connections + /// from clients with this port. + pub remote_port: u16, + /// Set to `true` to operate as a client and connect to remote + /// host. Set to `false` to accept incoming connections from + /// remote clients. + pub active_flag: bool, + } + + impl From for Tcpv4AccessPoint { + fn from(other: AccessPoint) -> Self { + let AccessPoint { + use_default_address, + station_address, + subnet_mask, + station_port, + remote_address, + remote_port, + active_flag, + } = other; + Self { + use_default_address: use_default_address.into(), + station_address: station_address.into(), + subnet_mask: subnet_mask.into(), + station_port, + remote_address: remote_address.into(), + remote_port, + active_flag: active_flag.into(), + } + } + } + + impl From<&AccessPoint> for Tcpv4AccessPoint { + fn from(other: &AccessPoint) -> Self { + Self::from(other.clone()) + } + } + + /// See [Tcpv4ConfigData]. + #[derive(Debug, Clone)] + pub struct ConfigData { + /// Type of service field in transmitted IPv4 packets. + pub type_of_service: u8, + /// Time to live field in transmitted IPv4 packets. + pub time_to_live: u8, + /// Access point configuration. + pub access_point: AccessPoint, + } + + /// See [Tcpv4Option]. + #[derive(Debug, Clone)] + pub struct ConfigOptions { + /// Size of the TCP receive buffer. + pub receive_buffer_size: u32, + /// Size of the TCP send buffer. + pub send_buffer_size: u32, + /// Maximum number of pending connections for passive instances. + pub max_syn_back_log: u32, + /// Connection timeout in seconds. + pub connection_timeout: u32, + /// Number of data retransmission attempts. + pub data_retries: u32, + /// `FIN` timeout in seconds. + pub fin_timeout: u32, + /// `TIME_WAIT` timeout in seconds. + pub time_wait_timeout: u32, + /// Number of keep-alive probes. + pub keep_alive_probes: u32, + /// Time before sending keep-alive probes in seconds. + pub keep_alive_time: u32, + /// Interval between keep-alive probes in seconds. + pub keep_alive_interval: u32, + /// Set to `true` to enable Nagle algorithm. + pub enable_nagle: bool, + /// Set to `true` to enable TCP timestamps. + pub enable_time_stamp: bool, + /// Set to `true` to enable window scaling. + pub enable_window_scaling: bool, + /// Set to `true` to enable selective acknowledgment. + pub enable_selective_ack: bool, + /// Set to `true` to enable path MTU discovery. + pub enable_path_mtu_discovery: bool, + } + + impl From for Tcpv4Option { + fn from(other: ConfigOptions) -> Self { + let ConfigOptions { + receive_buffer_size, + send_buffer_size, + max_syn_back_log, + connection_timeout, + data_retries, + fin_timeout, + time_wait_timeout, + keep_alive_probes, + keep_alive_time, + keep_alive_interval, + enable_nagle, + enable_time_stamp, + enable_window_scaling, + enable_selective_ack, + enable_path_mtu_discovery, + } = other; + Self { + receive_buffer_size, + send_buffer_size, + max_syn_back_log, + connection_timeout, + data_retries, + fin_timeout, + time_wait_timeout, + keep_alive_probes, + keep_alive_time, + keep_alive_interval, + enable_nagle: enable_nagle.into(), + enable_time_stamp: enable_time_stamp.into(), + enable_window_scaling: enable_window_scaling.into(), + enable_selective_ack: enable_selective_ack.into(), + enable_path_mtu_discovery: enable_path_mtu_discovery.into(), + } + } + } + + impl From<&ConfigOptions> for Tcpv4Option { + fn from(other: &ConfigOptions) -> Self { + Self::from(other.clone()) + } + } +}