use r_efi::efi::{self, Status}; use r_efi::protocols::tcp4; use crate::io; use crate::net::SocketAddrV4; use crate::ptr::NonNull; use crate::sync::atomic::{AtomicBool, Ordering}; use crate::sys::pal::helpers; use crate::time::{Duration, Instant}; const TYPE_OF_SERVICE: u8 = 8; const TIME_TO_LIVE: u8 = 255; pub(crate) struct Tcp4 { protocol: NonNull, flag: AtomicBool, #[expect(dead_code)] service_binding: helpers::ServiceProtocol, } const DEFAULT_ADDR: efi::Ipv4Address = efi::Ipv4Address { addr: [0u8; 4] }; impl Tcp4 { pub(crate) fn new() -> io::Result { let service_binding = helpers::ServiceProtocol::open(tcp4::SERVICE_BINDING_PROTOCOL_GUID)?; let protocol = helpers::open_protocol(service_binding.child_handle(), tcp4::PROTOCOL_GUID)?; Ok(Self { service_binding, protocol, flag: AtomicBool::new(false) }) } pub(crate) fn configure( &self, active: bool, remote_address: Option<&SocketAddrV4>, station_address: Option<&SocketAddrV4>, ) -> io::Result<()> { let protocol = self.protocol.as_ptr(); let (remote_address, remote_port) = if let Some(x) = remote_address { (helpers::ipv4_to_r_efi(*x.ip()), x.port()) } else { (DEFAULT_ADDR, 0) }; // FIXME: Remove when passive connections with proper subnet handling are added assert!(station_address.is_none()); let use_default_address = efi::Boolean::TRUE; let (station_address, station_port) = (DEFAULT_ADDR, 0); let subnet_mask = helpers::ipv4_to_r_efi(crate::net::Ipv4Addr::new(0, 0, 0, 0)); let mut config_data = tcp4::ConfigData { type_of_service: TYPE_OF_SERVICE, time_to_live: TIME_TO_LIVE, access_point: tcp4::AccessPoint { use_default_address, remote_address, remote_port, active_flag: active.into(), station_address, station_port, subnet_mask, }, control_option: crate::ptr::null_mut(), }; let r = unsafe { ((*protocol).configure)(protocol, &mut config_data) }; if r.is_error() { Err(crate::io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) } } pub(crate) fn get_mode_data(&self) -> io::Result { let mut config_data = tcp4::ConfigData::default(); let protocol = self.protocol.as_ptr(); let r = unsafe { ((*protocol).get_mode_data)( protocol, crate::ptr::null_mut(), &mut config_data, crate::ptr::null_mut(), crate::ptr::null_mut(), crate::ptr::null_mut(), ) }; if r.is_error() { Err(io::Error::from_raw_os_error(r.as_usize())) } else { Ok(config_data) } } pub(crate) fn connect(&self, timeout: Option) -> io::Result<()> { let evt = unsafe { self.create_evt() }?; let completion_token = tcp4::CompletionToken { event: evt.as_ptr(), status: Status::SUCCESS }; let protocol = self.protocol.as_ptr(); let mut conn_token = tcp4::ConnectionToken { completion_token }; let r = unsafe { ((*protocol).connect)(protocol, &mut conn_token) }; if r.is_error() { return Err(io::Error::from_raw_os_error(r.as_usize())); } unsafe { self.wait_or_cancel(timeout, &mut conn_token.completion_token) }?; if completion_token.status.is_error() { Err(io::Error::from_raw_os_error(completion_token.status.as_usize())) } else { Ok(()) } } pub(crate) fn write(&self, buf: &[u8], timeout: Option) -> io::Result { let evt = unsafe { self.create_evt() }?; let completion_token = tcp4::CompletionToken { event: evt.as_ptr(), status: Status::SUCCESS }; let data_len = u32::try_from(buf.len()).unwrap_or(u32::MAX); let fragment = tcp4::FragmentData { fragment_length: data_len, fragment_buffer: buf.as_ptr().cast::().cast_mut(), }; let mut tx_data = tcp4::TransmitData { push: r_efi::efi::Boolean::FALSE, urgent: r_efi::efi::Boolean::FALSE, data_length: data_len, fragment_count: 1, fragment_table: [fragment], }; let protocol = self.protocol.as_ptr(); let mut token = tcp4::IoToken { completion_token, packet: tcp4::IoTokenPacket { tx_data: (&raw mut tx_data).cast::>(), }, }; let r = unsafe { ((*protocol).transmit)(protocol, &mut token) }; if r.is_error() { return Err(io::Error::from_raw_os_error(r.as_usize())); } unsafe { self.wait_or_cancel(timeout, &mut token.completion_token) }?; if completion_token.status.is_error() { Err(io::Error::from_raw_os_error(completion_token.status.as_usize())) } else { Ok(data_len as usize) } } pub(crate) fn read(&self, buf: &mut [u8], timeout: Option) -> io::Result { let evt = unsafe { self.create_evt() }?; let completion_token = tcp4::CompletionToken { event: evt.as_ptr(), status: Status::SUCCESS }; let data_len = u32::try_from(buf.len()).unwrap_or(u32::MAX); let fragment = tcp4::FragmentData { fragment_length: data_len, fragment_buffer: buf.as_mut_ptr().cast::(), }; let mut tx_data = tcp4::ReceiveData { urgent_flag: r_efi::efi::Boolean::FALSE, data_length: data_len, fragment_count: 1, fragment_table: [fragment], }; let protocol = self.protocol.as_ptr(); let mut token = tcp4::IoToken { completion_token, packet: tcp4::IoTokenPacket { rx_data: (&raw mut tx_data).cast::>(), }, }; let r = unsafe { ((*protocol).receive)(protocol, &mut token) }; if r.is_error() { return Err(io::Error::from_raw_os_error(r.as_usize())); } unsafe { self.wait_or_cancel(timeout, &mut token.completion_token) }?; if completion_token.status.is_error() { Err(io::Error::from_raw_os_error(completion_token.status.as_usize())) } else { Ok(data_len as usize) } } /// Wait for an event to finish. This is checked by an atomic boolean that is supposed to be set /// to true in the event callback. /// /// Optionally, allow specifying a timeout. /// /// If a timeout is provided, the operation (specified by its `EFI_TCP4_COMPLETION_TOKEN`) is /// canceled and Error of kind TimedOut is returned. /// /// # SAFETY /// /// Pointer to a valid `EFI_TCP4_COMPLETION_TOKEN` unsafe fn wait_or_cancel( &self, timeout: Option, token: *mut tcp4::CompletionToken, ) -> io::Result<()> { if !self.wait_for_flag(timeout) { let _ = unsafe { self.cancel(token) }; return Err(io::Error::new(io::ErrorKind::TimedOut, "Operation Timed out")); } Ok(()) } /// Abort an asynchronous connection, listen, transmission or receive request. /// /// If token is NULL, then all pending tokens issued by EFI_TCP4_PROTOCOL.Connect(), /// EFI_TCP4_PROTOCOL.Accept(), EFI_TCP4_PROTOCOL.Transmit() or EFI_TCP4_PROTOCOL.Receive() are /// aborted. /// /// # SAFETY /// /// Pointer to a valid `EFI_TCP4_COMPLETION_TOKEN` or NULL unsafe fn cancel(&self, token: *mut tcp4::CompletionToken) -> io::Result<()> { let protocol = self.protocol.as_ptr(); let r = unsafe { ((*protocol).cancel)(protocol, token) }; if r.is_error() { return Err(io::Error::from_raw_os_error(r.as_usize())); } else { Ok(()) } } unsafe fn create_evt(&self) -> io::Result { self.flag.store(false, Ordering::Relaxed); helpers::OwnedEvent::new( efi::EVT_NOTIFY_SIGNAL, efi::TPL_CALLBACK, Some(toggle_atomic_flag), Some(unsafe { NonNull::new_unchecked(self.flag.as_ptr().cast()) }), ) } fn wait_for_flag(&self, timeout: Option) -> bool { let start = Instant::now(); while !self.flag.load(Ordering::Relaxed) { let _ = self.poll(); if let Some(t) = timeout { if Instant::now().duration_since(start) >= t { return false; } } } true } fn poll(&self) -> io::Result<()> { let protocol = self.protocol.as_ptr(); let r = unsafe { ((*protocol).poll)(protocol) }; if r.is_error() { Err(io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) } } } extern "efiapi" fn toggle_atomic_flag(_: r_efi::efi::Event, ctx: *mut crate::ffi::c_void) { let flag = unsafe { AtomicBool::from_ptr(ctx.cast()) }; flag.store(true, Ordering::Relaxed); }