use core::fmt;
use std::io::{self, Read, Write};
use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
use std::time::Duration;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConnectionState {
Open,
ReadClosed,
WriteClosed,
Closed,
}
impl ConnectionState {
fn read_closed(self) -> bool {
matches!(self, Self::ReadClosed | Self::Closed)
}
fn write_closed(self) -> bool {
matches!(self, Self::WriteClosed | Self::Closed)
}
fn with_read_closed(self) -> Self {
match self {
Self::Open | Self::ReadClosed => Self::ReadClosed,
Self::WriteClosed | Self::Closed => Self::Closed,
}
}
fn with_write_closed(self) -> Self {
match self {
Self::Open | Self::WriteClosed => Self::WriteClosed,
Self::ReadClosed | Self::Closed => Self::Closed,
}
}
}
#[derive(Debug)]
pub enum ConnectionError {
Io(io::Error),
}
impl fmt::Display for ConnectionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(error) => write!(f, "network connection I/O failed: {error}"),
}
}
}
impl std::error::Error for ConnectionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(error) => Some(error),
}
}
}
impl From<io::Error> for ConnectionError {
fn from(error: io::Error) -> Self {
Self::Io(error)
}
}
#[derive(Debug)]
pub struct Endpoint {
listener: TcpListener,
}
impl Endpoint {
pub fn bind(address: SocketAddr) -> Result<Self, ConnectionError> {
Ok(Self {
listener: TcpListener::bind(address)?,
})
}
pub fn local_addr(&self) -> Result<SocketAddr, ConnectionError> {
Ok(self.listener.local_addr()?)
}
pub fn set_nonblocking(&self, nonblocking: bool) -> Result<(), ConnectionError> {
self.listener.set_nonblocking(nonblocking)?;
Ok(())
}
pub fn accept(&self) -> Result<(Connection, SocketAddr), ConnectionError> {
let (stream, peer_address) = self.listener.accept()?;
Ok((Connection::from_tcp_stream(stream), peer_address))
}
}
#[derive(Debug)]
pub struct Connection {
stream: TcpStream,
state: ConnectionState,
}
impl Connection {
pub fn connect(address: SocketAddr) -> Result<Self, ConnectionError> {
Ok(Self {
stream: TcpStream::connect(address)?,
state: ConnectionState::Open,
})
}
pub fn connect_timeout(
address: SocketAddr,
timeout: Duration,
) -> Result<Self, ConnectionError> {
if timeout.is_zero() {
return Err(ConnectionError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
"connection timeout must be non-zero",
)));
}
Ok(Self {
stream: TcpStream::connect_timeout(&address, timeout)?,
state: ConnectionState::Open,
})
}
pub fn from_tcp_stream(stream: TcpStream) -> Self {
Self {
stream,
state: ConnectionState::Open,
}
}
pub fn state(&self) -> ConnectionState {
self.state
}
pub fn local_addr(&self) -> Result<SocketAddr, ConnectionError> {
Ok(self.stream.local_addr()?)
}
pub fn peer_addr(&self) -> Result<SocketAddr, ConnectionError> {
Ok(self.stream.peer_addr()?)
}
pub fn set_read_timeout(&self, timeout: Option<Duration>) -> Result<(), ConnectionError> {
self.stream.set_read_timeout(timeout)?;
Ok(())
}
pub fn set_write_timeout(&self, timeout: Option<Duration>) -> Result<(), ConnectionError> {
self.stream.set_write_timeout(timeout)?;
Ok(())
}
pub fn read(&mut self, buffer: &mut [u8]) -> Result<usize, ConnectionError> {
if self.state.read_closed() || buffer.is_empty() {
return Ok(0);
}
let count = self.stream.read(buffer)?;
if count == 0 {
self.state = self.state.with_read_closed();
}
Ok(count)
}
pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), ConnectionError> {
if self.state.write_closed() {
return Err(ConnectionError::Io(io::Error::new(
io::ErrorKind::BrokenPipe,
"connection send direction is closed",
)));
}
self.stream.write_all(bytes)?;
Ok(())
}
pub fn shutdown(&mut self, how: Shutdown) -> Result<(), ConnectionError> {
self.stream.shutdown(how)?;
self.state = match how {
Shutdown::Read => self.state.with_read_closed(),
Shutdown::Write => self.state.with_write_closed(),
Shutdown::Both => ConnectionState::Closed,
};
Ok(())
}
}
pub mod prediction;
pub mod disconnect;
pub mod latency;
pub mod reliability;
pub mod runtime;
pub mod simulator;
#[cfg(test)]
mod tests {
use super::ConnectionState;
#[test]
fn connection_state_transitions_preserve_half_close_semantics() {
assert_eq!(
ConnectionState::Open.with_read_closed(),
ConnectionState::ReadClosed
);
assert_eq!(
ConnectionState::Open.with_write_closed(),
ConnectionState::WriteClosed
);
assert_eq!(
ConnectionState::ReadClosed.with_write_closed(),
ConnectionState::Closed
);
assert_eq!(
ConnectionState::WriteClosed.with_read_closed(),
ConnectionState::Closed
);
assert_eq!(
ConnectionState::Closed.with_read_closed(),
ConnectionState::Closed
);
assert_eq!(
ConnectionState::Closed.with_write_closed(),
ConnectionState::Closed
);
}
#[test]
fn closed_directions_are_reported_consistently() {
assert!(!ConnectionState::Open.read_closed());
assert!(!ConnectionState::Open.write_closed());
assert!(ConnectionState::ReadClosed.read_closed());
assert!(!ConnectionState::ReadClosed.write_closed());
assert!(!ConnectionState::WriteClosed.read_closed());
assert!(ConnectionState::WriteClosed.write_closed());
assert!(ConnectionState::Closed.read_closed());
assert!(ConnectionState::Closed.write_closed());
}
}