use std::io::{self, Read};
use std::net::TcpStream;
use flume::TrySendError;
use syncbat::{SessionControl, SessionPoll, SubscriptionSession, SubscriptionSessionFactory};
use super::super::error::NetbatError;
use super::super::limits::Limits;
use super::super::stream_frame::SubscriptionToken;
use super::super::tcp::read_line;
use super::super::tls::{TlsServerConfig, TlsStream};
use super::{
classify_control_line, decode_subscribe_request, map_runtime_error, open_session_for_subscribe,
terminal_delivery, write_delivery, TcpSubscriptionServeStats, TcpSubscriptionServerConfig,
SUBSCRIPTION_POLL_INTERVAL,
};
const DRAIN_READ_CHUNK: usize = 4096;
const MAX_TLS_READS_PER_DRAIN: usize = 64;
pub(super) fn serve_tls_subscription_connection(
stream: TcpStream,
tls: &TlsServerConfig,
runtime: &(impl SubscriptionSessionFactory + ?Sized),
config: &TcpSubscriptionServerConfig,
) -> Result<TcpSubscriptionServeStats, NetbatError> {
let mut stats = TcpSubscriptionServeStats::default();
let limits = &config.limits;
let mut tls_stream = match tls.handshake(stream) {
Ok(tls_stream) => tls_stream,
Err(_error) => {
stats.tls_handshake_failures += 1;
tracing::debug!("tls subscription handshake failed; dropping session");
return Ok(stats);
}
};
let first_line = match read_line(&mut tls_stream, limits.max_line_bytes) {
Ok(line) => line,
Err(NetbatError::EmptyStream) => return Ok(stats),
Err(error) => return Err(error),
};
let subscribe = match decode_subscribe_request(&first_line, limits) {
Ok(frame) => frame,
Err(_) => {
stats.failed_subscriptions += 1;
stats.malformed_pre_subscribe += 1;
return Ok(stats);
}
};
let (control_tx, control_rx) = flume::bounded(16);
let mut session = match open_session_for_subscribe(
runtime,
&subscribe,
&mut tls_stream,
limits,
control_rx,
&mut stats,
)? {
Some(session) => session,
None => return Ok(stats),
};
stats.served_subscriptions += 1;
run_tls_subscription_loop(
&mut tls_stream,
session.as_mut(),
&control_tx,
limits,
&subscribe.subscription_id,
)?;
Ok(stats)
}
fn run_tls_subscription_loop(
tls: &mut TlsStream,
session: &mut dyn SubscriptionSession,
control_tx: &flume::Sender<SessionControl>,
limits: &Limits,
subscription_id: &SubscriptionToken,
) -> Result<(), NetbatError> {
let mut accumulator = ControlAccumulator::new();
let mut control_finished = false;
let mut disconnect_pending = false;
loop {
if disconnect_pending {
if control_tx.try_send(SessionControl::Disconnected).is_ok() {
disconnect_pending = false;
}
} else if !control_finished {
tls.sock.set_nonblocking(true).map_err(NetbatError::from)?;
let drain =
drain_control_frames(tls, &mut accumulator, control_tx, limits, subscription_id);
tls.sock.set_nonblocking(false).map_err(NetbatError::from)?;
match drain {
ControlDrain::Idle | ControlDrain::Backpressure => {}
ControlDrain::Stopped => control_finished = true,
ControlDrain::PeerGone => {
control_finished = true;
disconnect_pending = true;
}
}
}
match session.poll(SUBSCRIPTION_POLL_INTERVAL) {
Ok(SessionPoll::Delivery(delivery)) => {
write_delivery(tls, &delivery, limits)?;
if terminal_delivery(&delivery) {
return Ok(());
}
}
Ok(SessionPoll::Blocked) => {}
Ok(SessionPoll::Ended) => return Ok(()),
Err(error) => return Err(map_runtime_error(&error)),
}
}
}
enum ControlDrain {
Idle,
Backpressure,
PeerGone,
Stopped,
}
fn drain_control_frames(
tls: &mut TlsStream,
accumulator: &mut ControlAccumulator,
control_tx: &flume::Sender<SessionControl>,
limits: &Limits,
subscription_id: &SubscriptionToken,
) -> ControlDrain {
let mut tls_reads = 0_usize;
loop {
match accumulator.forward_complete_lines(control_tx, limits, subscription_id) {
LineFlow::NeedMore => {}
LineFlow::Backpressure => return ControlDrain::Backpressure,
LineFlow::Stopped => return ControlDrain::Stopped,
}
let mut scratch = [0_u8; DRAIN_READ_CHUNK];
match tls.conn.reader().read(&mut scratch) {
Ok(0) => return ControlDrain::PeerGone, Ok(count) => {
accumulator.extend(&scratch[..count]);
continue;
}
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {}
Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
Err(_) => return ControlDrain::PeerGone,
}
tls_reads += 1;
if tls_reads > MAX_TLS_READS_PER_DRAIN {
return ControlDrain::Idle;
}
match tls.conn.read_tls(&mut tls.sock) {
Ok(0) => return ControlDrain::PeerGone, Ok(_) => {
if tls.conn.process_new_packets().is_err() {
return ControlDrain::PeerGone;
}
}
Err(error) if error.kind() == io::ErrorKind::WouldBlock => return ControlDrain::Idle,
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(_) => return ControlDrain::PeerGone,
}
}
}
enum LineFlow {
NeedMore,
Backpressure,
Stopped,
}
struct ControlAccumulator {
buffer: Vec<u8>,
}
impl ControlAccumulator {
fn new() -> Self {
Self { buffer: Vec::new() }
}
fn extend(&mut self, bytes: &[u8]) {
self.buffer.extend_from_slice(bytes);
}
fn forward_complete_lines(
&mut self,
control_tx: &flume::Sender<SessionControl>,
limits: &Limits,
subscription_id: &SubscriptionToken,
) -> LineFlow {
loop {
let Some(newline) = self.buffer.iter().position(|byte| *byte == b'\n') else {
if self.buffer.len() > limits.max_line_bytes {
return match control_tx.try_send(SessionControl::Malformed) {
Ok(()) => {
self.buffer.clear();
LineFlow::Stopped
}
Err(TrySendError::Full(_)) => LineFlow::Backpressure,
Err(TrySendError::Disconnected(_)) => LineFlow::Stopped,
};
}
return LineFlow::NeedMore;
};
let classified =
classify_control_line(&self.buffer[..=newline], limits, subscription_id);
let terminal = classified.terminal;
match control_tx.try_send(classified.control) {
Ok(()) => {
self.buffer.drain(..=newline);
if terminal {
return LineFlow::Stopped;
}
}
Err(TrySendError::Full(_)) => return LineFlow::Backpressure,
Err(TrySendError::Disconnected(_)) => return LineFlow::Stopped,
}
}
}
}
#[cfg(test)]
#[path = "stream_tcp_tls_tests.rs"]
mod tests;