From c0ec9b4bcd6f4218a6285762e387b2e86be157da Mon Sep 17 00:00:00 2001 From: Lorenzo Rizzotti Date: Thu, 27 Nov 2025 10:06:08 -0800 Subject: [PATCH] Add vectored IO to avoid userspace vec copy (#38) --- idevice/src/lib.rs | 69 +++++++++++++++++++++++ idevice/src/services/core_device_proxy.rs | 16 +++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index 2fab32e..cacd0de 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -304,6 +304,75 @@ impl Idevice { self.send_raw_with_progress(message, |_| async {}, ()).await } + /// Sends raw binary data via vectored I/O + /// + /// # Arguments + /// * `bufs` - The buffers to send + /// + /// # Errors + /// Returns `IdeviceError` if transmission fails + pub async fn send_raw_vectored( + &mut self, + bufs: &[std::io::IoSlice<'_>], + ) -> Result<(), IdeviceError> { + if let Some(socket) = &mut self.socket { + let mut curr_idx = 0; + let mut curr_offset = 0; + + while curr_idx < bufs.len() { + let mut iovec = Vec::new(); + let mut accumulated_len = 0; + let max_chunk = 1024 * 64; + + // Add partial first slice + let first_avail = bufs[curr_idx].len() - curr_offset; + let to_take_first = std::cmp::min(first_avail, max_chunk); + iovec.push(std::io::IoSlice::new( + &bufs[curr_idx][curr_offset..curr_offset + to_take_first], + )); + accumulated_len += to_take_first; + + // Add others up to max_chunk + let mut temp_idx = curr_idx + 1; + while temp_idx < bufs.len() && accumulated_len < max_chunk { + let needed = max_chunk - accumulated_len; + let avail = bufs[temp_idx].len(); + let take = std::cmp::min(avail, needed); + iovec.push(std::io::IoSlice::new(&bufs[temp_idx][..take])); + accumulated_len += take; + temp_idx += 1; + } + + let n = socket.write_vectored(&iovec).await?; + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write whole buffer", + ) + .into()); + } + + // Advance cursor by n + let mut advanced = n; + while advanced > 0 && curr_idx < bufs.len() { + let available = bufs[curr_idx].len() - curr_offset; + if advanced < available { + curr_offset += advanced; + advanced = 0; + } else { + advanced -= available; + curr_idx += 1; + curr_offset = 0; + } + } + } + socket.flush().await?; + Ok(()) + } else { + Err(IdeviceError::NoEstablishedConnection) + } + } + /// Sends raw binary data with progress callbacks /// /// # Arguments diff --git a/idevice/src/services/core_device_proxy.rs b/idevice/src/services/core_device_proxy.rs index a08b0ff..6783d50 100644 --- a/idevice/src/services/core_device_proxy.rs +++ b/idevice/src/services/core_device_proxy.rs @@ -17,7 +17,7 @@ use crate::{Idevice, IdeviceError, IdeviceService, obf}; use byteorder::{BigEndian, WriteBytesExt}; use serde::{Deserialize, Serialize}; -use std::io::{self, Write}; +use std::io::{self, IoSlice, Write}; /// A representation of a CDTunnel packet used in the CoreDeviceProxy protocol. #[derive(Debug, PartialEq)] @@ -195,6 +195,20 @@ impl CoreDeviceProxy { Ok(()) } + /// Sends a raw data packet through the tunnel using vectored I/O. + /// + /// # Arguments + /// + /// * `bufs` - The buffers to send. + /// + /// # Returns + /// + /// * `Ok(())` if the data is successfully sent. + /// * `Err(IdeviceError)` if sending fails. + pub async fn send_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<(), IdeviceError> { + self.idevice.send_raw_vectored(bufs).await + } + /// Receives up to `mtu` bytes from the tunnel. /// /// # Returns