use std::cell::RefCell;
use std::ffi::c_void;
use std::future;
use std::io;
use std::os::raw::c_int;
use std::ptr;
use std::slice;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::sync::{mpsc, Mutex};
use wslay_sys::*;
const READ_CHUNK: usize = 64 * 1024;
#[derive(Default)]
struct Shared {
inbound: Vec<u8>,
inbound_pos: usize,
outbound: Vec<u8>,
to_peer: Vec<u8>,
cur_is_data: bool,
control_events: Vec<ControlEvent>,
}
enum ControlEvent {
Close { code: u16, reason: Vec<u8> },
Ping(Vec<u8>),
Pong(Vec<u8>),
}
#[inline]
unsafe fn shared<'a>(user_data: *mut c_void) -> &'a RefCell<Shared> {
&*(user_data as *const RefCell<Shared>)
}
unsafe extern "C" fn recv_cb(
ctx: wslay_event_context_ptr,
buf: *mut u8,
len: usize,
_flags: c_int,
user_data: *mut c_void,
) -> isize {
let mut s = shared(user_data).borrow_mut();
let avail = s.inbound.len() - s.inbound_pos;
if avail == 0 {
wslay_event_set_error(ctx, wslay_error_WSLAY_ERR_WOULDBLOCK); return -1;
}
let n = len.min(avail);
let from = s.inbound_pos;
ptr::copy_nonoverlapping(s.inbound.as_ptr().add(from), buf, n);
s.inbound_pos += n;
n as isize
}
unsafe extern "C" fn send_cb(
_ctx: wslay_event_context_ptr,
data: *const u8,
len: usize,
_flags: c_int,
user_data: *mut c_void,
) -> isize {
shared(user_data)
.borrow_mut()
.outbound
.extend_from_slice(slice::from_raw_parts(data, len));
len as isize
}
unsafe extern "C" fn frame_start_cb(
_ctx: wslay_event_context_ptr,
arg: *const wslay_event_on_frame_recv_start_arg,
user_data: *mut c_void,
) {
shared(user_data).borrow_mut().cur_is_data = ((*arg).opcode >> 3) & 1 == 0;
}
unsafe extern "C" fn frame_chunk_cb(
_ctx: wslay_event_context_ptr,
arg: *const wslay_event_on_frame_recv_chunk_arg,
user_data: *mut c_void,
) {
let mut s = shared(user_data).borrow_mut();
if s.cur_is_data {
let chunk = slice::from_raw_parts((*arg).data, (*arg).data_length);
s.to_peer.extend_from_slice(chunk);
}
}
unsafe extern "C" fn msg_recv_cb(
_ctx: wslay_event_context_ptr,
arg: *const wslay_event_on_msg_recv_arg,
user_data: *mut c_void,
) {
let arg = &*arg;
let event = match arg.opcode {
0x8 => {
let reason = if arg.msg_length >= 2 && !arg.msg.is_null() {
slice::from_raw_parts(arg.msg.add(2), arg.msg_length - 2).to_vec()
} else {
Vec::new()
};
Some(ControlEvent::Close {
code: arg.status_code,
reason,
})
}
0x9 | 0xA => {
let payload = if arg.msg.is_null() || arg.msg_length == 0 {
Vec::new()
} else {
slice::from_raw_parts(arg.msg, arg.msg_length).to_vec()
};
Some(if arg.opcode == 0x9 {
ControlEvent::Ping(payload)
} else {
ControlEvent::Pong(payload)
})
}
_ => None,
};
if let Some(ev) = event {
shared(user_data).borrow_mut().control_events.push(ev);
}
}
struct CtxGuard(usize);
impl Drop for CtxGuard {
fn drop(&mut self) {
unsafe { wslay_event_context_free(self.0 as wslay_event_context_ptr) };
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CloseFrame {
pub code: u16,
pub reason: String,
}
impl Default for CloseFrame {
fn default() -> Self {
Self {
code: wslay_status_code_WSLAY_CODE_NORMAL_CLOSURE as u16,
reason: String::new(),
}
}
}
enum ControlCmd {
Ping(Vec<u8>),
Pong(Vec<u8>),
Close(CloseFrame),
}
#[derive(Clone)]
pub struct WsControl {
tx: mpsc::UnboundedSender<ControlCmd>,
}
impl WsControl {
pub fn ping(&self, payload: impl Into<Vec<u8>>) -> io::Result<()> {
self.send(ControlCmd::Ping(payload.into()))
}
pub fn pong(&self, payload: impl Into<Vec<u8>>) -> io::Result<()> {
self.send(ControlCmd::Pong(payload.into()))
}
pub fn close(&self, code: u16, reason: impl Into<String>) -> io::Result<()> {
self.send(ControlCmd::Close(CloseFrame {
code,
reason: reason.into(),
}))
}
fn send(&self, cmd: ControlCmd) -> io::Result<()> {
self.tx
.send(cmd)
.map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "bridge has ended"))
}
}
pub struct ControlReceiver(mpsc::UnboundedReceiver<ControlCmd>);
pub fn control_channel() -> (WsControl, ControlReceiver) {
let (tx, rx) = mpsc::unbounded_channel();
(WsControl { tx }, ControlReceiver(rx))
}
pub type CloseHook = Box<dyn FnMut(&CloseFrame) + Send>;
pub type ControlHook = Box<dyn FnMut(&[u8]) + Send>;
#[derive(Debug, Clone)]
pub struct KeepAlive {
pub interval: Duration,
pub timeout: Duration,
pub close: CloseFrame,
}
impl KeepAlive {
pub fn new(interval: Duration, timeout: Duration) -> Self {
Self {
interval,
timeout,
close: CloseFrame {
code: wslay_status_code_WSLAY_CODE_GOING_AWAY as u16,
reason: "keepalive timeout".to_string(),
},
}
}
}
#[derive(Default)]
pub struct BridgeConfig {
pub close: CloseFrame,
pub keepalive: Option<KeepAlive>,
pub control: Option<ControlReceiver>,
pub on_close: Option<CloseHook>,
pub on_ping: Option<ControlHook>,
pub on_pong: Option<ControlHook>,
}
enum EndReason {
PeerClose(CloseFrame),
LocalClose(CloseFrame),
Abnormal,
}
unsafe fn queue_msg(ctx: wslay_event_context_ptr, opcode: u8, payload: &[u8]) {
let msg = wslay_event_msg {
opcode,
msg: payload.as_ptr(),
msg_length: payload.len(),
};
wslay_event_queue_msg(ctx, &msg);
}
unsafe fn queue_close(ctx: wslay_event_context_ptr, code: u16, reason: &[u8]) {
let (ptr, len) = if reason.is_empty() {
(ptr::null(), 0)
} else {
(reason.as_ptr(), reason.len())
};
wslay_event_queue_close(ctx, code, ptr, len);
}
pub async fn bridge<S, P>(ws_io: S, peer: P) -> io::Result<()>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
P: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
bridge_with(ws_io, peer, BridgeConfig::default()).await
}
pub async fn bridge_with<S, P>(ws_io: S, peer: P, config: BridgeConfig) -> io::Result<()>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
P: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
let BridgeConfig {
close,
keepalive,
control,
mut on_close,
mut on_ping,
mut on_pong,
} = config;
let (mut ws_read, ws_write) = tokio::io::split(ws_io);
let (mut peer_read, mut peer_write) = tokio::io::split(peer);
let ws_write = Arc::new(Mutex::new(ws_write));
let last_activity = Arc::new(std::sync::Mutex::new(Instant::now()));
let shared: Box<RefCell<Shared>> = Box::new(RefCell::new(Shared::default()));
let sp_addr = &*shared as *const RefCell<Shared> as usize;
let (ctx_addr, _guard) = {
let callbacks = wslay_event_callbacks {
recv_callback: Some(recv_cb),
send_callback: Some(send_cb),
genmask_callback: None, on_frame_recv_start_callback: Some(frame_start_cb),
on_frame_recv_chunk_callback: Some(frame_chunk_cb),
on_frame_recv_end_callback: None,
on_msg_recv_callback: Some(msg_recv_cb), };
let mut ctx_raw: wslay_event_context_ptr = ptr::null_mut();
let rc = unsafe {
wslay_event_context_server_init(&mut ctx_raw, &callbacks, sp_addr as *mut c_void)
};
if rc != 0 || ctx_raw.is_null() {
return Err(io::Error::other("wslay_event_context_server_init failed"));
}
unsafe { wslay_event_config_set_no_buffering(ctx_raw, 1) }; let addr = ctx_raw as usize;
(addr, CtxGuard(addr))
};
let ws_to_peer = {
let ws_write = ws_write.clone();
let last_activity = last_activity.clone();
async move {
let mut buf = vec![0u8; READ_CHUNK];
loop {
let n = ws_read.read(&mut buf).await?;
if n == 0 {
let _ = peer_write.shutdown().await;
return Ok(EndReason::Abnormal); }
*last_activity.lock().unwrap() = Instant::now();
let (to_peer, outbound, closed, events) = unsafe {
let ctx = ctx_addr as wslay_event_context_ptr;
let sp = &*(sp_addr as *const RefCell<Shared>);
sp.borrow_mut().inbound.extend_from_slice(&buf[..n]);
let recv_rc = wslay_event_recv(ctx); wslay_event_send(ctx); let mut s = sp.borrow_mut();
let pos = s.inbound_pos;
s.inbound.drain(..pos);
s.inbound_pos = 0;
let closed = recv_rc != 0 || wslay_event_get_close_received(ctx) != 0;
(
std::mem::take(&mut s.to_peer),
std::mem::take(&mut s.outbound),
closed,
std::mem::take(&mut s.control_events),
)
};
if !to_peer.is_empty() {
peer_write.write_all(&to_peer).await?;
}
let mut peer_close = None;
for event in events {
match event {
ControlEvent::Close { code, reason } => {
let code = if code == 0 {
wslay_status_code_WSLAY_CODE_NO_STATUS_RCVD as u16
} else {
code
};
peer_close = Some(CloseFrame {
code,
reason: String::from_utf8_lossy(&reason).into_owned(),
});
}
ControlEvent::Ping(p) => {
if let Some(cb) = on_ping.as_mut() {
cb(&p);
}
}
ControlEvent::Pong(p) => {
if let Some(cb) = on_pong.as_mut() {
cb(&p);
}
}
}
}
if !outbound.is_empty() {
ws_write.lock().await.write_all(&outbound).await?;
}
if closed {
let _ = peer_write.shutdown().await;
return Ok(peer_close.map_or(EndReason::Abnormal, EndReason::PeerClose));
}
}
}
};
let peer_to_ws = {
let ws_write = ws_write.clone();
async move {
let mut buf = vec![0u8; READ_CHUNK];
loop {
let n = match peer_read.read(&mut buf).await {
Ok(0) | Err(_) => {
let outbound = unsafe {
let ctx = ctx_addr as wslay_event_context_ptr;
queue_close(ctx, close.code, close.reason.as_bytes());
wslay_event_send(ctx);
std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
};
if !outbound.is_empty() {
let _ = ws_write.lock().await.write_all(&outbound).await;
}
return Ok(EndReason::LocalClose(close));
}
Ok(n) => n,
};
let outbound = unsafe {
let ctx = ctx_addr as wslay_event_context_ptr;
queue_msg(ctx, wslay_opcode_WSLAY_BINARY_FRAME as u8, &buf[..n]);
wslay_event_send(ctx);
std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
};
if !outbound.is_empty() {
ws_write.lock().await.write_all(&outbound).await?;
}
}
}
};
let control_dir = {
let ws_write = ws_write.clone();
async move {
let mut rx = match control {
Some(ControlReceiver(rx)) => rx,
None => return future::pending::<io::Result<EndReason>>().await,
};
while let Some(cmd) = rx.recv().await {
let outbound = unsafe {
let ctx = ctx_addr as wslay_event_context_ptr;
match &cmd {
ControlCmd::Ping(p) => queue_msg(ctx, wslay_opcode_WSLAY_PING as u8, p),
ControlCmd::Pong(p) => queue_msg(ctx, wslay_opcode_WSLAY_PONG as u8, p),
ControlCmd::Close(cf) => queue_close(ctx, cf.code, cf.reason.as_bytes()),
}
wslay_event_send(ctx);
std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
};
if !outbound.is_empty() {
ws_write.lock().await.write_all(&outbound).await?;
}
}
future::pending::<io::Result<EndReason>>().await
}
};
let keepalive_dir = {
let ws_write = ws_write.clone();
let last_activity = last_activity.clone();
async move {
let ka = match keepalive {
Some(ka) => ka,
None => return future::pending::<io::Result<EndReason>>().await,
};
loop {
let idle = last_activity.lock().unwrap().elapsed();
if idle < ka.interval {
tokio::time::sleep(ka.interval - idle).await;
continue;
}
let outbound = unsafe {
let ctx = ctx_addr as wslay_event_context_ptr;
queue_msg(ctx, wslay_opcode_WSLAY_PING as u8, b"");
wslay_event_send(ctx);
std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
};
if !outbound.is_empty() {
ws_write.lock().await.write_all(&outbound).await?;
}
let pinged_at = Instant::now();
tokio::time::sleep(ka.timeout).await;
if *last_activity.lock().unwrap() <= pinged_at {
let outbound = unsafe {
let ctx = ctx_addr as wslay_event_context_ptr;
queue_close(ctx, ka.close.code, ka.close.reason.as_bytes());
wslay_event_send(ctx);
std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
};
if !outbound.is_empty() {
let _ = ws_write.lock().await.write_all(&outbound).await;
}
return Ok(EndReason::LocalClose(ka.close));
}
}
}
};
let result = tokio::select! {
r = ws_to_peer => r,
r = peer_to_ws => r,
r = control_dir => r,
r = keepalive_dir => r,
};
drop(_guard); drop(shared);
let closed_with = match &result {
Ok(EndReason::PeerClose(cf)) | Ok(EndReason::LocalClose(cf)) => cf.clone(),
Ok(EndReason::Abnormal) => CloseFrame {
code: wslay_status_code_WSLAY_CODE_ABNORMAL_CLOSURE as u16,
reason: String::new(),
},
Err(e) => CloseFrame {
code: wslay_status_code_WSLAY_CODE_ABNORMAL_CLOSURE as u16,
reason: e.to_string(),
},
};
if let Some(cb) = on_close.as_mut() {
cb(&closed_with);
}
result.map(|_| ())
}