use core::fmt;
use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::config::ProfileId;
use crate::engine::{Engine, EngineError, OutBuf, RefusalKind};
use crate::envelope::Envelope;
pub const DEFAULT_MAX_FRAME: u64 = 64 * 1024;
pub const DEFAULT_MAX_MESSAGE: u64 = 1024 * 1024;
pub const DEFAULT_PING_INTERVAL: Duration = Duration::from_secs(30);
pub trait WebSocketService: Send + Sync + 'static {
fn opened(&self, env: &Envelope, conn: &mut WebSocketConn<'_>) {
let _ = (env, conn);
}
fn message(&self, env: &Envelope, message: WsMessage<'_>, conn: &mut WebSocketConn<'_>);
fn closed(&self, env: &Envelope, code: Option<u16>) {
let _ = (env, code);
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WsMessage<'a> {
Text(&'a str),
Binary(&'a [u8]),
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WsSendError {
Full,
Closed,
Invalid,
}
impl fmt::Display for WsSendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
WsSendError::Full => "the client is not reading: output over its budget",
WsSendError::Closed => "the connection is closing",
WsSendError::Invalid => "ws-net refused the frame",
})
}
}
impl std::error::Error for WsSendError {}
pub struct WebSocketConn<'a> {
pub(crate) conn: &'a mut ws_net::Connection,
pub(crate) max_pending: usize,
}
impl WebSocketConn<'_> {
fn check(&mut self, len: usize) -> Result<(), WsSendError> {
if self.conn.state() != ws_net::State::Open {
return Err(WsSendError::Closed);
}
if self.conn.output().len().saturating_add(len) > self.max_pending {
return Err(WsSendError::Full);
}
Ok(())
}
pub fn send_text(&mut self, text: &str) -> Result<(), WsSendError> {
self.check(text.len())?;
self.conn.send_text(text).map_err(|_| WsSendError::Invalid)
}
pub fn send_binary(&mut self, data: &[u8]) -> Result<(), WsSendError> {
self.check(data.len())?;
self.conn
.send_binary(data)
.map_err(|_| WsSendError::Invalid)
}
pub fn close(&mut self, code: u16, reason: &str) -> Result<(), WsSendError> {
if self.conn.state() != ws_net::State::Open {
return Err(WsSendError::Closed);
}
self.conn
.close(ws_net::CloseCode::new(code), reason)
.map_err(|_| WsSendError::Invalid)
}
#[must_use]
pub fn pending_output(&mut self) -> usize {
self.conn.output().len()
}
}
impl fmt::Debug for WebSocketConn<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WebSocketConn")
.field("state", &self.conn.state())
.field("max_pending", &self.max_pending)
.finish()
}
}
#[derive(Clone)]
#[must_use]
pub struct WebSocketTarget {
pub(crate) service: Arc<dyn WebSocketService>,
pub(crate) limits: ws_net::Limits,
pub(crate) subprotocol: Option<&'static str>,
pub(crate) profile: Option<ProfileId>,
pub(crate) max_pending: Option<usize>,
pub(crate) ping_interval: Duration,
}
impl WebSocketTarget {
pub fn new<S: WebSocketService>(service: S) -> Self {
Self::shared(Arc::new(service))
}
pub fn shared(service: Arc<dyn WebSocketService>) -> Self {
WebSocketTarget {
service,
limits: ws_net::Limits::new()
.with_max_frame(DEFAULT_MAX_FRAME)
.with_max_message(DEFAULT_MAX_MESSAGE),
subprotocol: None,
profile: None,
max_pending: None,
ping_interval: DEFAULT_PING_INTERVAL,
}
}
pub fn limits(mut self, limits: ws_net::Limits) -> Self {
self.limits = limits;
self
}
pub fn subprotocol(mut self, name: &'static str) -> Self {
self.subprotocol = Some(name);
self
}
pub fn profile(mut self, id: ProfileId) -> Self {
self.profile = Some(id);
self
}
pub fn max_pending_output(mut self, bytes: usize) -> Self {
self.max_pending = Some(bytes);
self
}
pub fn ping_interval(mut self, interval: Duration) -> Self {
self.ping_interval = interval.max(Duration::from_secs(1));
self
}
}
impl fmt::Debug for WebSocketTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WebSocketTarget")
.field("limits", &self.limits)
.field("subprotocol", &self.subprotocol)
.field("profile", &self.profile)
.field("max_pending", &self.max_pending)
.field("ping_interval", &self.ping_interval)
.finish_non_exhaustive()
}
}
pub(crate) struct WsLink {
pub(crate) conn: ws_net::Connection,
pub(crate) service: Arc<dyn WebSocketService>,
pub(crate) max_pending: usize,
pub(crate) interval: Duration,
pub(crate) backlog: Vec<u8>,
pub(crate) scratch: Vec<u8>,
pub(crate) text: String,
pub(crate) subprotocol: Option<&'static str>,
pub(crate) last_input: Instant,
pub(crate) timer: Instant,
pub(crate) ping_at: Option<Instant>,
pub(crate) code: Option<u16>,
pub(crate) opened: bool,
pub(crate) finish: bool,
}
impl WsLink {
pub(crate) fn new(target: &WebSocketTarget, max_pending: usize, now: Instant) -> WsLink {
WsLink {
conn: ws_net::Connection::server().with_limits(target.limits),
service: Arc::clone(&target.service),
max_pending,
interval: target.ping_interval,
backlog: Vec::new(),
scratch: Vec::new(),
text: String::new(),
subprotocol: target.subprotocol,
last_input: now,
timer: now + target.ping_interval,
ping_at: None,
code: None,
opened: false,
finish: false,
}
}
}
impl WsLink {
pub(crate) fn clear_message(&mut self, wipe: bool, retain: usize) {
if wipe {
crate::engine::wipe_bytes(&mut self.scratch);
let mut text = core::mem::take(&mut self.text).into_bytes();
crate::engine::wipe_bytes(&mut text);
text.clear();
self.text = String::from_utf8(text).unwrap_or_default();
}
self.scratch.clear();
self.text.clear();
if self.scratch.capacity() > retain {
self.scratch = Vec::new();
}
if self.text.capacity() > retain {
self.text = String::new();
}
}
}
pub(crate) fn switch_http1(
engine: &mut crate::engine::Http1,
target: &WebSocketTarget,
unix_secs: u64,
out: &mut OutBuf,
) -> Result<(), EngineError> {
let internal = EngineError {
kind: RefusalKind::Internal,
status: 500,
};
let malformed = EngineError {
kind: RefusalKind::Malformed,
status: 400,
};
let Some(head) = engine.head() else {
return Err(internal);
};
if !head.is_upgrade() {
return Err(internal);
}
let handshake = match ws_net::ServerHandshake::from_request(
head.method_str(),
head.target(),
head.headers(),
) {
Ok(h) => h,
Err(ws_net::HandshakeError::MissingUpgrade) => return Err(internal),
Err(_) => return Err(malformed),
};
if let Some(p) = target.subprotocol {
if !handshake.protocols().any(|offered| offered == p) {
return Err(internal);
}
}
let mut fields: [(&str, &[u8]); 2] = [("", &[]); 2];
fields[0] = ("sec-websocket-accept", handshake.accept_key().as_bytes());
let mut n = 1;
if let Some(p) = target.subprotocol {
fields[1] = ("sec-websocket-protocol", p.as_bytes());
n = 2;
}
let mut response =
http_net::ResponseHeadRef::new(101, &fields[..n], http_net::OutgoingBody::Empty);
response.upgrade = Some("websocket");
let date = engine.dates.update(unix_secs);
let start = out.len();
if engine.conn.write_head(&response, Some(date), out).is_err() {
out.bytes.truncate(start);
return Err(internal);
}
Ok(())
}