use std::{net::SocketAddr, os::fd::RawFd};
use crate::{
error::{Error, Result},
proto::TlsSession,
};
const MAX_BUFFER_CAPACITY: usize = isize::MAX as usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SocketState {
Idle,
Connecting,
Connected,
Cancelling,
Closing,
Closed,
}
pub struct IoUringSocket {
fd: Option<RawFd>,
state: SocketState,
tls: Option<TlsSession>,
rx_buffer: Option<Box<[u8]>>,
tx_buffer: Option<Box<[u8]>>,
in_flight_ops: usize,
close_requested: bool,
close_submitted: bool,
peer: Option<SocketAddr>,
}
impl IoUringSocket {
pub fn new(fd: RawFd, rx_capacity: usize, tx_capacity: usize) -> Result<Self> {
validate_capacities(rx_capacity, tx_capacity)?;
let flags = fcntl_status(fd, libc::F_GETFL, 0)?;
if flags & libc::O_NONBLOCK == 0 {
let _updated = fcntl_status(fd, libc::F_SETFL, flags | libc::O_NONBLOCK)?;
}
let rx_buffer = zeroed_buffer(rx_capacity);
let tx_buffer = zeroed_buffer(tx_capacity);
Ok(Self {
fd: Some(fd),
state: SocketState::Idle,
tls: None,
rx_buffer: Some(rx_buffer),
tx_buffer: Some(tx_buffer),
in_flight_ops: 0,
close_requested: false,
close_submitted: false,
peer: None,
})
}
pub const fn fd(&self) -> Option<RawFd> {
if self.close_submitted { None } else { self.fd }
}
pub const fn state(&self) -> SocketState {
self.state
}
pub fn tls(&self) -> Option<&TlsSession> {
self.tls.as_ref()
}
pub fn tls_mut(&mut self) -> Option<&mut TlsSession> {
self.tls.as_mut()
}
pub fn set_tls(&mut self, tls: TlsSession) {
self.tls = Some(tls);
}
pub fn take_tls(&mut self) -> Option<TlsSession> {
self.tls.take()
}
pub fn has_tls(&self) -> bool {
self.tls.is_some()
}
pub const fn peer_addr(&self) -> Option<SocketAddr> {
self.peer
}
pub fn set_peer_addr(&mut self, peer: SocketAddr) {
self.peer = Some(peer);
}
pub fn rx_buffer(&self) -> Option<&[u8]> {
self.rx_buffer.as_deref()
}
pub fn rx_buffer_mut(&mut self) -> Option<&mut [u8]> {
self.rx_buffer.as_deref_mut()
}
pub fn tx_buffer(&self) -> Option<&[u8]> {
self.tx_buffer.as_deref()
}
pub fn tx_buffer_mut(&mut self) -> Option<&mut [u8]> {
self.tx_buffer.as_deref_mut()
}
pub const fn in_flight_ops(&self) -> usize {
self.in_flight_ops
}
pub const fn is_closed(&self) -> bool {
matches!(self.state, SocketState::Closed)
}
pub const fn close_submitted(&self) -> bool {
self.close_submitted
}
pub fn renew_buffers(&mut self, rx_capacity: usize, tx_capacity: usize) -> Result<()> {
if matches!(self.state, SocketState::Closing | SocketState::Closed) {
return Err(Error::InvalidState(
"cannot renew buffers while the socket is closing or closed".to_owned(),
));
}
if self.close_requested || self.close_submitted {
return Err(Error::InvalidState(
"cannot renew buffers after a close was requested".to_owned(),
));
}
if self.in_flight_ops != 0 {
return Err(Error::InvalidState(format!(
"cannot renew buffers while {} operations are in flight",
self.in_flight_ops
)));
}
validate_capacities(rx_capacity, tx_capacity)?;
let rx_buffer = zeroed_buffer(rx_capacity);
let tx_buffer = zeroed_buffer(tx_capacity);
self.rx_buffer = Some(rx_buffer);
self.tx_buffer = Some(tx_buffer);
Ok(())
}
pub fn begin_connect(&mut self) -> Result<()> {
if !matches!(self.state, SocketState::Idle) {
return Err(Error::InvalidState(format!(
"connect requires an idle socket, current state is {:?}",
self.state
)));
}
self.start_op()?;
self.state = SocketState::Connecting;
Ok(())
}
pub fn connect_completed(&mut self, success: bool) -> Result<bool> {
match self.state {
SocketState::Connecting => {
self.retire_op("CONNECT")?;
if success {
self.state = SocketState::Connected;
Ok(false)
} else {
self.state = SocketState::Closing;
Ok(true)
}
}
SocketState::Cancelling => self.retire_op("CONNECT"),
_ => Err(Error::InvalidState(format!(
"connect completion requires the connecting or cancelling state, current state is \
{:?}",
self.state
))),
}
}
pub fn begin_op(&mut self) -> Result<()> {
if !matches!(self.state, SocketState::Connected) {
return Err(Error::InvalidState(format!(
"receive or send requires a connected socket, current state is {:?}",
self.state
)));
}
self.start_op()
}
pub fn complete_op(&mut self) -> Result<bool> {
if !matches!(self.state, SocketState::Connected | SocketState::Cancelling) {
return Err(Error::InvalidState(format!(
"receive or send completion requires the connected or cancelling state, current \
state is {:?}",
self.state
)));
}
self.retire_op("receive or send")
}
#[must_use]
pub fn request_cancel(&mut self) -> bool {
match self.state {
SocketState::Idle | SocketState::Connecting | SocketState::Connected => {
if self.in_flight_ops > 0 {
self.state = SocketState::Cancelling;
false
} else {
self.state = SocketState::Closing;
true
}
}
SocketState::Cancelling | SocketState::Closing | SocketState::Closed => false,
}
}
#[must_use]
pub fn request_close(&mut self) -> bool {
if matches!(self.state, SocketState::Closed) || self.close_requested || self.close_submitted
{
return false;
}
self.close_requested = true;
if self.in_flight_ops > 0 {
if !matches!(self.state, SocketState::Cancelling | SocketState::Closing) {
self.state = SocketState::Cancelling;
}
false
} else {
self.state = SocketState::Closing;
true
}
}
pub fn close_started(&mut self) -> Result<()> {
if !matches!(self.state, SocketState::Closing) {
return Err(Error::InvalidState(format!(
"close submission requires the closing state, current state is {:?}",
self.state
)));
}
if self.close_submitted {
return Err(Error::InvalidState(
"close was already submitted for this socket".to_owned(),
));
}
if self.in_flight_ops != 0 {
return Err(Error::InvalidState(format!(
"close submission requires an idle kernel, {} operations are in flight",
self.in_flight_ops
)));
}
self.close_requested = true;
self.close_submitted = true;
Ok(())
}
pub fn close_submission_failed(&mut self) -> Result<()> {
if self.close_submitted {
return Err(Error::InvalidState(
"close SQE was already recorded as submitted; a retry would close twice".to_owned(),
));
}
self.close_requested = false;
Ok(())
}
pub fn close_completed(&mut self) -> Result<()> {
if !matches!(self.state, SocketState::Closing) {
return Err(Error::InvalidState(format!(
"close completion requires the closing state, current state is {:?}",
self.state
)));
}
if !self.close_submitted {
return Err(Error::InvalidState(
"close completion arrived before the close was submitted".to_owned(),
));
}
if self.in_flight_ops != 0 {
return Err(Error::InvalidState(format!(
"close completion arrived while {} operations are still in flight",
self.in_flight_ops
)));
}
self.state = SocketState::Closed;
self.rx_buffer = None;
self.tx_buffer = None;
self.fd = None;
Ok(())
}
fn start_op(&mut self) -> Result<()> {
self.in_flight_ops = self.in_flight_ops.checked_add(1).ok_or_else(|| {
Error::InvalidState("in-flight operation count overflowed".to_owned())
})?;
Ok(())
}
fn retire_op(&mut self, operation: &str) -> Result<bool> {
self.in_flight_ops = self.in_flight_ops.checked_sub(1).ok_or_else(|| {
Error::InvalidState(format!(
"{operation} completion arrived with no operation in flight"
))
})?;
if self.in_flight_ops == 0 && matches!(self.state, SocketState::Cancelling) {
self.state = SocketState::Closing;
Ok(true)
} else {
Ok(false)
}
}
fn forget_buffers(&mut self) {
if let Some(rx) = self.rx_buffer.take() {
std::mem::forget(rx);
}
if let Some(tx) = self.tx_buffer.take() {
std::mem::forget(tx);
}
}
}
impl Drop for IoUringSocket {
fn drop(&mut self) {
if self.close_submitted {
self.forget_buffers();
self.fd = None;
return;
}
if self.in_flight_ops > 0 {
self.forget_buffers();
}
if let Some(fd) = self.fd.take() {
unsafe {
libc::close(fd);
}
}
}
}
fn validate_capacities(rx_capacity: usize, tx_capacity: usize) -> Result<()> {
if rx_capacity == 0 || tx_capacity == 0 {
return Err(Error::InvalidState(
"socket buffers must have nonzero capacity".to_owned(),
));
}
let capacity = rx_capacity.max(tx_capacity);
if capacity > MAX_BUFFER_CAPACITY {
return Err(Error::InvalidState(format!(
"socket buffer capacity {capacity} exceeds the maximum allocation size"
)));
}
Ok(())
}
fn zeroed_buffer(capacity: usize) -> Box<[u8]> {
vec![0u8; capacity].into_boxed_slice()
}
fn fcntl_status(fd: RawFd, command: libc::c_int, argument: libc::c_int) -> Result<libc::c_int> {
loop {
let result = unsafe { libc::fcntl(fd, command, argument) };
if result >= 0 {
return Ok(result);
}
let error = std::io::Error::last_os_error();
if error.kind() != std::io::ErrorKind::Interrupted {
return Err(Error::Io(error));
}
}
}