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 const DEFAULT_MESSAGE_TIMEOUT: 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,
pub(crate) outbox: &'a Arc<Outbox>,
}
#[derive(Clone)]
pub struct WsSender {
outbox: Arc<Outbox>,
}
impl WsSender {
pub fn send_text(&self, text: &str) -> Result<(), WsSendError> {
self.outbox.push(Queued::Text(text.to_owned()))
}
pub fn send_binary(&self, bytes: &[u8]) -> Result<(), WsSendError> {
self.outbox.push(Queued::Binary(bytes.to_vec()))
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.outbox.lock().closed
}
}
impl fmt::Debug for WsSender {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.outbox.lock();
f.debug_struct("WsSender")
.field("queued", &state.items.len())
.field("closed", &state.closed)
.finish()
}
}
pub(crate) enum Queued {
Text(String),
Binary(Vec<u8>),
}
impl Queued {
pub(crate) fn len(&self) -> usize {
match self {
Queued::Text(t) => t.len(),
Queued::Binary(b) => b.len(),
}
}
}
pub(crate) struct OutboxState {
pub(crate) items: std::collections::VecDeque<Queued>,
pub(crate) bytes: usize,
pub(crate) closed: bool,
}
pub(crate) struct Outbox {
state: crate::sync::Mutex<OutboxState>,
waker: Arc<dyn crate::reply::Waker>,
connection: crate::net::ConnectionId,
max_bytes: usize,
}
impl Outbox {
pub(crate) fn new(
waker: Arc<dyn crate::reply::Waker>,
connection: crate::net::ConnectionId,
max_bytes: usize,
) -> Self {
Outbox {
state: crate::sync::Mutex::new(OutboxState {
items: std::collections::VecDeque::new(),
bytes: 0,
closed: false,
}),
waker,
connection,
max_bytes,
}
}
pub(crate) fn lock(&self) -> crate::sync::MutexGuard<'_, OutboxState> {
self.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(serve_net_loom)]
pub(crate) fn push_text_for_tests(&self, text: &str) -> Result<(), WsSendError> {
self.push(Queued::Text(text.to_owned()))
}
fn push(&self, message: Queued) -> Result<(), WsSendError> {
let len = message.len();
let mut state = self.lock();
if state.closed {
return Err(WsSendError::Closed);
}
if state.bytes.saturating_add(len) > self.max_bytes {
return Err(WsSendError::Full);
}
let first = state.items.is_empty();
state.items.push_back(message);
state.bytes += len;
drop(state);
if first {
let _ = self.waker.post(self.connection, crate::reply::WAKE_PUSH, 0);
}
Ok(())
}
pub(crate) fn close(&self, wipe: bool) {
let mut state = self.lock();
state.closed = true;
if wipe {
for item in &mut state.items {
match item {
Queued::Text(t) => {
let mut bytes = core::mem::take(t).into_bytes();
crate::engine::wipe_bytes(&mut bytes);
}
Queued::Binary(b) => crate::engine::wipe_bytes(b),
}
}
}
state.items.clear();
state.bytes = 0;
}
}
impl WebSocketConn<'_> {
#[must_use]
pub fn sender(&self) -> WsSender {
WsSender {
outbox: Arc::clone(self.outbox),
}
}
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,
pub(crate) message_timeout: 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,
message_timeout: DEFAULT_MESSAGE_TIMEOUT,
}
}
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
}
pub fn message_timeout(mut self, timeout: Duration) -> Self {
self.message_timeout = timeout.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) outbox: Arc<Outbox>,
pub(crate) partial_since: Option<Instant>,
pub(crate) message_timeout: Duration,
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,
outbox: Arc<Outbox>,
) -> 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,
outbox,
partial_since: None,
message_timeout: target.message_timeout,
code: None,
opened: false,
finish: false,
}
}
}
impl WsLink {
pub(crate) fn deadline(&self, base: Instant) -> Instant {
self.partial_since
.map_or(base, |since| base.min(since + self.message_timeout))
}
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(())
}