use core::ffi::{c_uint, c_ushort, c_void};
use core::marker::{PhantomData, PhantomPinned};
use crate as uws;
use crate::app::uws_app_t;
use crate::thunk;
use crate::{Opcode, Request, SendStatus, Socket, WebSocketUpgradeContext, uws_res};
#[repr(C)]
pub struct NewWebSocket<const SSL_FLAG: i32> {
_p: core::cell::UnsafeCell<[u8; 0]>,
_m: PhantomData<(*mut u8, PhantomPinned)>,
}
impl<const SSL_FLAG: i32> NewWebSocket<SSL_FLAG> {
#[inline]
pub fn raw(&mut self) -> &mut RawWebSocket {
unsafe { &mut *std::ptr::from_mut::<Self>(self).cast::<RawWebSocket>() }
}
#[inline]
pub unsafe fn as_<T>(&mut self) -> Option<&mut T> {
unsafe {
let p = c::uws_ws_get_user_data(SSL_FLAG, self.raw());
p.cast::<T>().as_mut()
}
}
pub fn close(&mut self) {
c::uws_ws_close(SSL_FLAG, self.raw())
}
pub fn send(&mut self, message: &[u8], opcode: Opcode) -> SendStatus {
unsafe {
c::uws_ws_send(
SSL_FLAG,
self.raw(),
message.as_ptr(),
message.len(),
opcode,
)
}
}
pub fn send_with_options(
&mut self,
message: &[u8],
opcode: Opcode,
compress: bool,
fin: bool,
) -> SendStatus {
unsafe {
c::uws_ws_send_with_options(
SSL_FLAG,
self.raw(),
message.as_ptr(),
message.len(),
opcode,
compress,
fin,
)
}
}
pub fn memory_cost(&mut self) -> usize {
self.raw().memory_cost(SSL_FLAG)
}
pub fn send_last_fragment(&mut self, message: &[u8], compress: bool) -> SendStatus {
unsafe {
c::uws_ws_send_last_fragment(
SSL_FLAG,
self.raw(),
message.as_ptr(),
message.len(),
compress,
)
}
}
pub fn end(&mut self, code: i32, message: &[u8]) {
unsafe { c::uws_ws_end(SSL_FLAG, self.raw(), code, message.as_ptr(), message.len()) }
}
pub fn cork<C>(&mut self, ctx: &mut C, callback: fn(&mut C)) {
extern "C" fn wrap<C>(user_data: *mut c_void) {
let data = unsafe { bun_core::callback_ctx::<(*mut C, fn(&mut C))>(user_data) };
(data.1)(unsafe { &mut *data.0 });
}
let mut data: (*mut C, fn(&mut C)) = (std::ptr::from_mut::<C>(ctx), callback);
c::uws_ws_cork(
SSL_FLAG,
self.raw(),
Some(wrap::<C>),
(&raw mut data).cast::<c_void>(),
)
}
pub fn subscribe(&mut self, topic: &[u8]) -> bool {
unsafe { c::uws_ws_subscribe(SSL_FLAG, self.raw(), topic.as_ptr(), topic.len()) }
}
pub fn unsubscribe(&mut self, topic: &[u8]) -> bool {
unsafe { c::uws_ws_unsubscribe(SSL_FLAG, self.raw(), topic.as_ptr(), topic.len()) }
}
pub fn is_subscribed(&mut self, topic: &[u8]) -> bool {
unsafe { c::uws_ws_is_subscribed(SSL_FLAG, self.raw(), topic.as_ptr(), topic.len()) }
}
pub fn publish(&mut self, topic: &[u8], message: &[u8]) -> bool {
unsafe {
c::uws_ws_publish(
SSL_FLAG,
self.raw(),
topic.as_ptr(),
topic.len(),
message.as_ptr(),
message.len(),
)
}
}
pub fn publish_with_options(
&mut self,
topic: &[u8],
message: &[u8],
opcode: Opcode,
compress: bool,
) -> bool {
unsafe {
c::uws_ws_publish_with_options(
SSL_FLAG,
self.raw(),
topic.as_ptr(),
topic.len(),
message.as_ptr(),
message.len(),
opcode,
compress,
)
}
}
pub fn get_buffered_amount(&mut self) -> u32 {
u32::try_from(c::uws_ws_get_buffered_amount(SSL_FLAG, self.raw())).unwrap()
}
pub fn get_remote_address<'a>(&mut self, buf: &'a mut [u8]) -> &'a mut [u8] {
let mut ptr: *mut u8 = core::ptr::null_mut();
let len = c::uws_ws_get_remote_address(SSL_FLAG, self.raw(), &mut ptr);
let src = unsafe { bun_core::ffi::slice(ptr, len) };
buf[..len].copy_from_slice(src);
&mut buf[..len]
}
}
bun_opaque::opaque_ffi! { pub struct RawWebSocket; }
impl RawWebSocket {
pub fn memory_cost(&mut self, ssl_flag: i32) -> usize {
c::uws_ws_memory_cost(ssl_flag, self)
}
pub fn as_socket(&mut self) -> *mut Socket {
std::ptr::from_mut::<RawWebSocket>(self).cast::<Socket>()
}
}
#[derive(Clone, Copy)]
pub enum AnyWebSocket {
Ssl(*mut RawWebSocket),
Tcp(*mut RawWebSocket),
}
impl AnyWebSocket {
#[inline]
pub fn raw(self) -> *mut RawWebSocket {
match self {
AnyWebSocket::Ssl(p) => p,
AnyWebSocket::Tcp(p) => p,
}
}
#[inline]
pub unsafe fn as_<T>(self) -> Option<&'static mut T> {
let (ssl, ws) = self.split();
unsafe { c::uws_ws_get_user_data(ssl, ws).cast::<T>().as_mut() }
}
#[inline]
pub fn as_ptr<T>(self) -> *mut T {
let (ssl, ws) = self.split();
c::uws_ws_get_user_data(ssl, ws).cast::<T>()
}
#[inline]
fn split<'a>(self) -> (i32, &'a mut RawWebSocket) {
let (ssl, p) = match self {
AnyWebSocket::Ssl(p) => (1, p),
AnyWebSocket::Tcp(p) => (0, p),
};
(ssl, RawWebSocket::opaque_mut(p))
}
pub fn memory_cost(self) -> usize {
let (ssl, ws) = self.split();
ws.memory_cost(ssl)
}
pub fn close(self) {
let (ssl, ws) = self.split();
c::uws_ws_close(ssl, ws)
}
pub fn send(self, message: &[u8], opcode: Opcode, compress: bool, fin: bool) -> SendStatus {
let (ssl, ws) = self.split();
unsafe {
c::uws_ws_send_with_options(
ssl,
ws,
message.as_ptr(),
message.len(),
opcode,
compress,
fin,
)
}
}
pub fn send_last_fragment(self, message: &[u8], compress: bool) -> SendStatus {
let (ssl, ws) = self.split();
unsafe { c::uws_ws_send_last_fragment(ssl, ws, message.as_ptr(), message.len(), compress) }
}
pub fn end(self, code: i32, message: &[u8]) {
let (ssl, ws) = self.split();
unsafe { c::uws_ws_end(ssl, ws, code, message.as_ptr(), message.len()) }
}
pub fn cork<C>(self, ctx: &mut C, callback: fn(&mut C)) {
extern "C" fn wrap<C>(user_data: *mut c_void) {
let data = unsafe { bun_core::callback_ctx::<(*mut C, fn(&mut C))>(user_data) };
(data.1)(unsafe { &mut *data.0 });
}
let mut data: (*mut C, fn(&mut C)) = (std::ptr::from_mut::<C>(ctx), callback);
let ud = (&raw mut data).cast::<c_void>();
let (ssl, ws) = self.split();
c::uws_ws_cork(ssl, ws, Some(wrap::<C>), ud)
}
pub fn subscribe(self, topic: &[u8]) -> bool {
let (ssl, ws) = self.split();
unsafe { c::uws_ws_subscribe(ssl, ws, topic.as_ptr(), topic.len()) }
}
pub fn unsubscribe(self, topic: &[u8]) -> bool {
let (ssl, ws) = self.split();
unsafe { c::uws_ws_unsubscribe(ssl, ws, topic.as_ptr(), topic.len()) }
}
pub fn is_subscribed(self, topic: &[u8]) -> bool {
let (ssl, ws) = self.split();
unsafe { c::uws_ws_is_subscribed(ssl, ws, topic.as_ptr(), topic.len()) }
}
pub fn publish(self, topic: &[u8], message: &[u8], opcode: Opcode, compress: bool) -> bool {
let (ssl, ws) = self.split();
unsafe {
c::uws_ws_publish_with_options(
ssl,
ws,
topic.as_ptr(),
topic.len(),
message.as_ptr(),
message.len(),
opcode,
compress,
)
}
}
pub fn publish_with_options(
ssl: bool,
app: *mut c_void,
topic: &[u8],
message: &[u8],
opcode: Opcode,
compress: bool,
) -> bool {
if ssl {
uws::NewApp::<true>::publish_with_options(
bun_opaque::opaque_deref_mut(app.cast::<uws::NewApp<true>>()),
topic,
message,
opcode,
compress,
)
} else {
uws::NewApp::<false>::publish_with_options(
bun_opaque::opaque_deref_mut(app.cast::<uws::NewApp<false>>()),
topic,
message,
opcode,
compress,
)
}
}
pub fn get_buffered_amount(self) -> usize {
let (ssl, ws) = self.split();
c::uws_ws_get_buffered_amount(ssl, ws)
}
pub fn get_remote_address<'a>(self, buf: &'a mut [u8]) -> &'a mut [u8] {
let (ssl_flag, ws) = self.split();
let mut ptr: *mut u8 = core::ptr::null_mut();
let len = c::uws_ws_get_remote_address(ssl_flag, ws, &mut ptr);
let src = unsafe { bun_core::ffi::slice(ptr, len) };
buf[..len].copy_from_slice(src);
&mut buf[..len]
}
}
pub(crate) type uws_websocket_handler = Option<unsafe extern "C" fn(*mut RawWebSocket)>;
pub(crate) type uws_websocket_message_handler =
Option<unsafe extern "C" fn(*mut RawWebSocket, *const u8, usize, Opcode)>;
pub(crate) type uws_websocket_close_handler =
Option<unsafe extern "C" fn(*mut RawWebSocket, i32, *const u8, usize)>;
pub(crate) type uws_websocket_upgrade_handler = Option<
unsafe extern "C" fn(
*mut c_void,
*mut uws_res,
*mut Request,
*mut WebSocketUpgradeContext,
usize,
),
>;
pub(crate) type uws_websocket_ping_pong_handler =
Option<unsafe extern "C" fn(*mut RawWebSocket, *const u8, usize)>;
#[repr(C)]
pub struct WebSocketBehavior {
pub compression: c::uws_compress_options_t,
pub max_payload_length: c_uint,
pub idle_timeout: c_ushort,
pub max_backpressure: c_uint,
pub close_on_backpressure_limit: bool,
pub reset_idle_timeout_on_send: bool,
pub send_pings_automatically: bool,
pub max_lifetime: c_ushort,
pub upgrade: uws_websocket_upgrade_handler,
pub open: uws_websocket_handler,
pub message: uws_websocket_message_handler,
pub drain: uws_websocket_handler,
pub ping: uws_websocket_ping_pong_handler,
pub pong: uws_websocket_ping_pong_handler,
pub close: uws_websocket_close_handler,
}
impl Default for WebSocketBehavior {
fn default() -> Self {
Self {
compression: 0,
max_payload_length: u32::MAX,
idle_timeout: 120,
max_backpressure: 1024 * 1024,
close_on_backpressure_limit: false,
reset_idle_timeout_on_send: true,
send_pings_automatically: true,
max_lifetime: 0,
upgrade: None,
open: None,
message: None,
drain: None,
ping: None,
pong: None,
close: None,
}
}
}
pub trait WebSocketHandler: Sized + 'static {
const HAS_ON_MESSAGE: bool = true;
const HAS_ON_DRAIN: bool = true;
const HAS_ON_PING: bool = true;
const HAS_ON_PONG: bool = true;
unsafe fn on_open(this: *mut Self, ws: AnyWebSocket);
unsafe fn on_message(this: *mut Self, ws: AnyWebSocket, message: &[u8], opcode: Opcode);
unsafe fn on_drain(this: *mut Self, ws: AnyWebSocket);
unsafe fn on_ping(this: *mut Self, ws: AnyWebSocket, message: &[u8]);
unsafe fn on_pong(this: *mut Self, ws: AnyWebSocket, message: &[u8]);
unsafe fn on_close(this: *mut Self, ws: AnyWebSocket, code: i32, message: &[u8]);
}
pub trait WebSocketUpgradeServer<const SSL: bool>: Sized + 'static {
unsafe fn on_websocket_upgrade(
this: *mut Self,
res: *mut uws::NewAppResponse<SSL>,
req: &mut Request,
context: &mut WebSocketUpgradeContext,
id: usize,
);
}
pub struct Wrap<Server, T, const SSL: bool>(PhantomData<(Server, T)>);
impl<Server, T, const SSL: bool> Wrap<Server, T, SSL>
where
Server: WebSocketUpgradeServer<SSL>,
T: WebSocketHandler,
{
#[inline(always)]
fn make_ws(raw_ws: *mut RawWebSocket) -> AnyWebSocket {
if SSL {
AnyWebSocket::Ssl(raw_ws)
} else {
AnyWebSocket::Tcp(raw_ws)
}
}
pub(crate) extern "C" fn on_open(raw_ws: *mut RawWebSocket) {
let ws = Self::make_ws(raw_ws);
let this = ws.as_ptr::<T>();
if this.is_null() {
return;
}
unsafe { T::on_open(this, ws) };
}
pub(crate) extern "C" fn on_message(
raw_ws: *mut RawWebSocket,
message: *const u8,
length: usize,
opcode: Opcode,
) {
let ws = Self::make_ws(raw_ws);
let this = ws.as_ptr::<T>();
if this.is_null() {
return;
}
unsafe { T::on_message(this, ws, thunk::c_slice(message, length), opcode) };
}
pub(crate) extern "C" fn on_drain(raw_ws: *mut RawWebSocket) {
let ws = Self::make_ws(raw_ws);
let this = ws.as_ptr::<T>();
if this.is_null() {
return;
}
unsafe { T::on_drain(this, ws) };
}
pub(crate) extern "C" fn on_ping(raw_ws: *mut RawWebSocket, message: *const u8, length: usize) {
let ws = Self::make_ws(raw_ws);
let this = ws.as_ptr::<T>();
if this.is_null() {
return;
}
unsafe { T::on_ping(this, ws, thunk::c_slice(message, length)) };
}
pub(crate) extern "C" fn on_pong(raw_ws: *mut RawWebSocket, message: *const u8, length: usize) {
let ws = Self::make_ws(raw_ws);
let this = ws.as_ptr::<T>();
if this.is_null() {
return;
}
unsafe { T::on_pong(this, ws, thunk::c_slice(message, length)) };
}
pub(crate) extern "C" fn on_close(
raw_ws: *mut RawWebSocket,
code: i32,
message: *const u8,
length: usize,
) {
let ws = Self::make_ws(raw_ws);
let this = ws.as_ptr::<T>();
if this.is_null() {
return;
}
unsafe { T::on_close(this, ws, code, thunk::c_slice(message, length)) };
}
pub(crate) extern "C" fn on_upgrade(
ptr: *mut c_void,
res: *mut uws_res,
req: *mut Request,
context: *mut WebSocketUpgradeContext,
id: usize,
) {
if ptr.is_null() {
return;
}
unsafe {
Server::on_websocket_upgrade(
ptr.cast::<Server>(),
res.cast::<uws::NewAppResponse<SSL>>(),
thunk::handle_mut(req),
thunk::handle_mut(context),
id,
);
}
}
pub fn apply(behavior: &WebSocketBehavior) -> WebSocketBehavior {
WebSocketBehavior {
compression: behavior.compression,
max_payload_length: behavior.max_payload_length,
idle_timeout: behavior.idle_timeout,
max_backpressure: behavior.max_backpressure,
close_on_backpressure_limit: behavior.close_on_backpressure_limit,
reset_idle_timeout_on_send: behavior.reset_idle_timeout_on_send,
send_pings_automatically: behavior.send_pings_automatically,
max_lifetime: behavior.max_lifetime,
upgrade: Some(Self::on_upgrade),
open: Some(Self::on_open),
message: if T::HAS_ON_MESSAGE {
Some(Self::on_message)
} else {
None
},
drain: if T::HAS_ON_DRAIN {
Some(Self::on_drain)
} else {
None
},
ping: if T::HAS_ON_PING {
Some(Self::on_ping)
} else {
None
},
pong: if T::HAS_ON_PONG {
Some(Self::on_pong)
} else {
None
},
close: Some(Self::on_close),
}
}
}
pub mod c {
use super::*;
pub(crate) type uws_compress_options_t = i32;
unsafe extern "C" {
pub(crate) safe fn uws_ws_memory_cost(ssl: i32, ws: &mut RawWebSocket) -> usize;
pub(crate) fn uws_ws(
ssl: i32,
app: *mut uws_app_t,
ctx: *mut c_void,
pattern: *const u8,
pattern_len: usize,
id: usize,
behavior: *const WebSocketBehavior,
);
pub safe fn uws_ws_get_user_data(ssl: i32, ws: &mut RawWebSocket) -> *mut c_void;
pub(crate) safe fn uws_ws_close(ssl: i32, ws: &mut RawWebSocket);
pub(crate) fn uws_ws_send(
ssl: i32,
ws: *mut RawWebSocket,
message: *const u8,
length: usize,
opcode: Opcode,
) -> SendStatus;
pub(crate) fn uws_ws_send_with_options(
ssl: i32,
ws: *mut RawWebSocket,
message: *const u8,
length: usize,
opcode: Opcode,
compress: bool,
fin: bool,
) -> SendStatus;
pub fn uws_ws_send_fragment(
ssl: i32,
ws: *mut RawWebSocket,
message: *const u8,
length: usize,
compress: bool,
) -> SendStatus;
pub fn uws_ws_send_first_fragment(
ssl: i32,
ws: *mut RawWebSocket,
message: *const u8,
length: usize,
compress: bool,
) -> SendStatus;
pub fn uws_ws_send_first_fragment_with_opcode(
ssl: i32,
ws: *mut RawWebSocket,
message: *const u8,
length: usize,
opcode: Opcode,
compress: bool,
) -> SendStatus;
pub(crate) fn uws_ws_send_last_fragment(
ssl: i32,
ws: *mut RawWebSocket,
message: *const u8,
length: usize,
compress: bool,
) -> SendStatus;
pub(crate) fn uws_ws_end(
ssl: i32,
ws: *mut RawWebSocket,
code: i32,
message: *const u8,
length: usize,
);
pub(crate) safe fn uws_ws_cork(
ssl: i32,
ws: &mut RawWebSocket,
handler: Option<unsafe extern "C" fn(*mut c_void)>,
user_data: *mut c_void,
);
pub(crate) fn uws_ws_subscribe(
ssl: i32,
ws: *mut RawWebSocket,
topic: *const u8,
length: usize,
) -> bool;
pub(crate) fn uws_ws_unsubscribe(
ssl: i32,
ws: *mut RawWebSocket,
topic: *const u8,
length: usize,
) -> bool;
pub(crate) fn uws_ws_is_subscribed(
ssl: i32,
ws: *mut RawWebSocket,
topic: *const u8,
length: usize,
) -> bool;
pub fn uws_ws_iterate_topics(
ssl: i32,
ws: *mut RawWebSocket,
callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>,
user_data: *mut c_void,
);
pub(crate) fn uws_ws_publish(
ssl: i32,
ws: *mut RawWebSocket,
topic: *const u8,
topic_length: usize,
message: *const u8,
message_length: usize,
) -> bool;
pub(crate) fn uws_ws_publish_with_options(
ssl: i32,
ws: *mut RawWebSocket,
topic: *const u8,
topic_length: usize,
message: *const u8,
message_length: usize,
opcode: Opcode,
compress: bool,
) -> bool;
pub(crate) safe fn uws_ws_get_buffered_amount(ssl: i32, ws: &mut RawWebSocket) -> usize;
pub(crate) safe fn uws_ws_get_remote_address(
ssl: i32,
ws: &mut RawWebSocket,
dest: &mut *mut u8,
) -> usize;
pub safe fn uws_ws_get_remote_address_as_text(
ssl: i32,
ws: &mut RawWebSocket,
dest: &mut *mut u8,
) -> usize;
}
}