use super::stream::{DEFAULT_FRAME_COMPLETION_TIMEOUT, read_fmp_packet_with_timeout};
use super::*;
use crate::transport::{PacketBuffer, ReceivedPacket};
use tokio::time::timeout;
pub(super) struct AcceptConfig {
pub(super) max_inbound: usize,
pub(super) nodelay: bool,
pub(super) keepalive_secs: u64,
pub(super) recv_buf: usize,
pub(super) send_buf: usize,
pub(super) first_frame_timeout_ms: u64,
}
pub(super) async fn accept_loop(
listener: TcpListener,
transport_id: TransportId,
packet_tx: PacketTx,
pool: ConnectionPool,
cfg: AcceptConfig,
stats: Arc<TcpStats>,
) {
let AcceptConfig {
max_inbound,
nodelay,
keepalive_secs,
recv_buf,
send_buf,
first_frame_timeout_ms,
} = cfg;
debug!(transport_id = %transport_id, "TCP accept loop starting");
loop {
match listener.accept().await {
Ok((stream, peer_addr)) => {
if stats.pool_inbound_count() >= max_inbound as u64 {
stats.record_connection_rejected();
warn!(
transport_id = %transport_id,
peer_addr = %peer_addr,
max = max_inbound,
"Rejecting inbound TCP connection (max_inbound_connections reached)"
);
continue;
}
let std_stream = match stream.into_std() {
Ok(s) => s,
Err(e) => {
warn!(
transport_id = %transport_id,
error = %e,
"Failed to convert accepted stream to std"
);
continue;
}
};
if let Err(e) = configure_accepted_socket(
&std_stream,
nodelay,
keepalive_secs,
recv_buf,
send_buf,
) {
warn!(
transport_id = %transport_id,
peer_addr = %peer_addr,
error = %e,
"Failed to configure accepted socket"
);
continue;
}
let stream = match TcpStream::from_std(std_stream) {
Ok(s) => s,
Err(e) => {
warn!(
transport_id = %transport_id,
error = %e,
"Failed to convert accepted stream back to tokio"
);
continue;
}
};
let remote_addr = TransportAddr::from_string(&peer_addr.to_string());
let mut pool_guard = pool.lock().await;
if let Some(existing) = pool_guard.get(&remote_addr)
&& !existing.io.is_closed()
{
debug!(
transport_id = %transport_id,
remote_addr = %remote_addr,
"Discarding duplicate inbound TCP connection"
);
continue;
}
if let Some(closed) = pool_guard.remove(&remote_addr) {
closed.recv_task.abort();
record_pool_removed(&stats, &closed);
}
let (read_half, write_half) = stream.into_split();
let io = Arc::new(StreamConnectionIo::new(write_half));
let recv_pool = pool.clone();
let recv_packet_tx = packet_tx.clone();
let recv_stats = stats.clone();
let recv_addr = remote_addr.clone();
let recv_io = io.clone();
let recv_task = tokio::spawn(async move {
tcp_receive_loop(
read_half,
TcpReceiveContext {
transport_id,
remote_addr: recv_addr,
packet_tx: recv_packet_tx,
pool: recv_pool,
stats: recv_stats,
first_frame_timeout: first_frame_timeout(first_frame_timeout_ms),
frame_completion_timeout: DEFAULT_FRAME_COMPLETION_TIMEOUT,
direction: Direction::Inbound,
io: recv_io,
},
)
.await;
});
let conn = TcpConnection {
io,
recv_task,
direction: Direction::Inbound,
};
pool_guard.insert(remote_addr.clone(), conn);
stats.record_pool_inbound_added();
drop(pool_guard);
stats.record_connection_accepted();
debug!(
transport_id = %transport_id,
remote_addr = %remote_addr,
"Accepted inbound TCP connection"
);
}
Err(e) => {
warn!(
transport_id = %transport_id,
error = %e,
"TCP accept error"
);
}
}
}
}
pub(super) struct TcpReceiveContext {
pub(super) transport_id: TransportId,
pub(super) remote_addr: TransportAddr,
pub(super) packet_tx: PacketTx,
pub(super) pool: ConnectionPool,
pub(super) stats: Arc<TcpStats>,
pub(super) first_frame_timeout: Option<Duration>,
pub(super) frame_completion_timeout: Duration,
pub(super) direction: Direction,
pub(super) io: Arc<StreamConnectionIo<tokio::net::tcp::OwnedWriteHalf>>,
}
pub(super) async fn tcp_receive_loop(
mut reader: tokio::net::tcp::OwnedReadHalf,
ctx: TcpReceiveContext,
) {
let TcpReceiveContext {
transport_id,
remote_addr,
packet_tx,
pool,
stats,
first_frame_timeout,
frame_completion_timeout,
direction,
io,
} = ctx;
debug!(
transport_id = %transport_id,
remote_addr = %remote_addr,
"TCP receive loop starting"
);
let mut first_frame = true;
loop {
let is_first_frame = first_frame;
let read = async {
if is_first_frame {
match first_frame_timeout {
Some(limit) => match timeout(
limit,
read_fmp_packet_with_timeout(&mut reader, frame_completion_timeout),
)
.await
{
Ok(result) => Some(result),
Err(_) => {
stats.record_first_frame_timeout();
debug!(
transport_id = %transport_id,
remote_addr = %remote_addr,
timeout_ms = limit.as_millis(),
"TCP inbound connection timed out before first complete FMP frame"
);
None
}
},
None => Some(
read_fmp_packet_with_timeout(&mut reader, frame_completion_timeout).await,
),
}
} else {
Some(read_fmp_packet_with_timeout(&mut reader, frame_completion_timeout).await)
}
};
tokio::pin!(read);
let read_result = tokio::select! {
result = &mut read => result,
() = io.closed() => None,
};
let Some(read_result) = read_result else {
break;
};
match read_result {
Ok(data) => {
first_frame = false;
stats.record_recv(data.len());
trace!(
transport_id = %transport_id,
remote_addr = %remote_addr,
bytes = data.len(),
"TCP packet received"
);
let packet = ReceivedPacket::with_timestamp(
transport_id,
remote_addr.clone(),
PacketBuffer::new(data),
crate::time::now_ms(),
);
if packet_tx.send(packet).is_err() {
debug!(
transport_id = %transport_id,
"Packet channel closed, stopping TCP receive loop"
);
break;
}
}
Err(e) => {
stats.record_recv_error();
debug!(
transport_id = %transport_id,
remote_addr = %remote_addr,
error = %e,
"TCP receive error, removing connection"
);
break;
}
}
}
io.mark_closed();
let mut pool_guard = pool.lock().await;
let removed = remove_if_current(&mut pool_guard, &remote_addr, &io);
drop(pool_guard);
if let Some(connection) = removed {
record_pool_removed(&stats, &connection);
}
debug!(
transport_id = %transport_id,
remote_addr = %remote_addr,
direction = ?direction,
"TCP receive loop stopped"
);
}
pub(super) fn first_frame_timeout(timeout_ms: u64) -> Option<Duration> {
(timeout_ms > 0).then(|| Duration::from_millis(timeout_ms))
}