use super::frame;
use super::material::Material;
use crate::registry::Peer;
use crate::registry::presence::Presence;
use rustls::{ServerConfig, ServerConnection, StreamOwned};
use serde_json::Value;
use std::net::{TcpListener, TcpStream};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
use std::time::Duration;
const ACCEPT_POLL: Duration = Duration::from_millis(20);
const IDLE_TIMEOUT: Duration = Duration::from_mins(2);
pub(crate) const PING: Duration = Duration::from_secs(25);
pub(crate) mod peer;
pub(crate) use peer::{Quiet, peer_client};
pub trait Answerer: Send + Sync {
fn answer(&self, peer: &Peer, request: Value) -> Box<dyn Iterator<Item = Value>>;
}
pub struct Listener {
address: String,
stop: Arc<AtomicBool>,
handle: Option<JoinHandle<()>>,
}
impl Listener {
pub fn bind(
m: &Material,
answerer: Arc<dyn Answerer>,
presence: Presence,
) -> Result<Self, String> {
let config = super::tls::server_config(m)?;
let tcp = TcpListener::bind(&m.address).map_err(|e| format!("bind {}: {e}", m.address))?;
let address = tcp
.local_addr()
.map_err(|e| format!("bind {}: {e}", m.address))?
.to_string();
tcp.set_nonblocking(true)
.map_err(|e| format!("bind {address}: {e}"))?;
let stop = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&stop);
let handle =
std::thread::spawn(move || accept_loop(&tcp, &config, &answerer, &presence, &flag));
Ok(Self {
address,
stop,
handle: Some(handle),
})
}
pub fn address(&self) -> String {
self.address.clone()
}
}
impl Drop for Listener {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
fn accept_loop(
tcp: &TcpListener,
config: &Arc<ServerConfig>,
answerer: &Arc<dyn Answerer>,
presence: &Presence,
stop: &Arc<AtomicBool>,
) {
while !stop.load(Ordering::Relaxed) {
match tcp.accept() {
Ok((stream, _)) => {
let config = Arc::clone(config);
let answerer = Arc::clone(answerer);
let presence = presence.clone();
std::thread::spawn(move || {
let _ = stream.set_nonblocking(false);
serve(
stream,
&config,
answerer.as_ref(),
&presence,
Quiet::dialled(),
);
});
}
Err(_) => std::thread::sleep(ACCEPT_POLL),
}
}
}
impl Quiet {
pub(crate) fn dialled() -> Quiet {
Quiet {
gone: IDLE_TIMEOUT,
ping: None,
}
}
pub(crate) fn held() -> Quiet {
Quiet {
gone: IDLE_TIMEOUT,
ping: Some(PING),
}
}
}
pub(crate) fn serve(
tcp: TcpStream,
config: &Arc<ServerConfig>,
answerer: &dyn Answerer,
presence: &Presence,
quiet: Quiet,
) -> bool {
let _ = tcp.set_read_timeout(Some(quiet.read_timeout()));
let Ok(conn) = ServerConnection::new(Arc::clone(config)) else {
return false;
};
let mut tls = StreamOwned::new(conn, tcp);
let Some(edition) = super::hello::admit(&mut tls) else {
peer::hang_up(&mut tls);
return false;
};
let _ = converse(&mut tls, answerer, presence, quiet, edition);
true
}
fn converse(
tls: &mut StreamOwned<ServerConnection, TcpStream>,
answerer: &dyn Answerer,
presence: &Presence,
quiet: Quiet,
edition: u32,
) -> Option<std::convert::Infallible> {
let mut live = None;
let mut quiet_for = Duration::ZERO;
loop {
let request = match frame::read_value(tls) {
Ok(Some(request)) => request,
Err(e) if quiet.pings_at(quiet_for, &e) && peer::ping(tls) => {
quiet_for += quiet.read_timeout();
continue;
}
_ => return None,
};
quiet_for = Duration::ZERO;
let peer = peer_client(tls.conn.peer_certificates())?;
let _ = live.get_or_insert_with(|| presence.enter(&peer.client, edition));
for chunk in answerer.answer(&peer, request) {
frame::write_value(tls, &chunk).ok()?;
}
frame::write_end(tls).ok()?;
}
}
#[cfg(test)]
mod tests;