1use std::cell::RefCell;
23use std::ffi::c_void;
24use std::future;
25use std::io;
26use std::os::raw::c_int;
27use std::ptr;
28use std::slice;
29use std::sync::Arc;
30use std::time::{Duration, Instant};
31
32use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
33use tokio::sync::{mpsc, Mutex};
34use wslay_sys::*;
35
36const READ_CHUNK: usize = 64 * 1024;
37
38#[derive(Default)]
42struct Shared {
43 inbound: Vec<u8>,
45 inbound_pos: usize,
46 outbound: Vec<u8>,
48 to_peer: Vec<u8>,
50 cur_is_data: bool,
52 control_events: Vec<ControlEvent>,
54}
55
56enum ControlEvent {
58 Close { code: u16, reason: Vec<u8> },
59 Ping(Vec<u8>),
60 Pong(Vec<u8>),
61}
62
63#[inline]
64unsafe fn shared<'a>(user_data: *mut c_void) -> &'a RefCell<Shared> {
65 &*(user_data as *const RefCell<Shared>)
66}
67
68unsafe extern "C" fn recv_cb(
70 ctx: wslay_event_context_ptr,
71 buf: *mut u8,
72 len: usize,
73 _flags: c_int,
74 user_data: *mut c_void,
75) -> isize {
76 let mut s = shared(user_data).borrow_mut();
77 let avail = s.inbound.len() - s.inbound_pos;
78 if avail == 0 {
79 wslay_event_set_error(ctx, wslay_error_WSLAY_ERR_WOULDBLOCK); return -1;
81 }
82 let n = len.min(avail);
83 let from = s.inbound_pos;
84 ptr::copy_nonoverlapping(s.inbound.as_ptr().add(from), buf, n);
85 s.inbound_pos += n;
86 n as isize
87}
88
89unsafe extern "C" fn send_cb(
91 _ctx: wslay_event_context_ptr,
92 data: *const u8,
93 len: usize,
94 _flags: c_int,
95 user_data: *mut c_void,
96) -> isize {
97 shared(user_data)
98 .borrow_mut()
99 .outbound
100 .extend_from_slice(slice::from_raw_parts(data, len));
101 len as isize
102}
103
104unsafe extern "C" fn frame_start_cb(
106 _ctx: wslay_event_context_ptr,
107 arg: *const wslay_event_on_frame_recv_start_arg,
108 user_data: *mut c_void,
109) {
110 shared(user_data).borrow_mut().cur_is_data = ((*arg).opcode >> 3) & 1 == 0;
112}
113
114unsafe extern "C" fn frame_chunk_cb(
117 _ctx: wslay_event_context_ptr,
118 arg: *const wslay_event_on_frame_recv_chunk_arg,
119 user_data: *mut c_void,
120) {
121 let mut s = shared(user_data).borrow_mut();
122 if s.cur_is_data {
123 let chunk = slice::from_raw_parts((*arg).data, (*arg).data_length);
124 s.to_peer.extend_from_slice(chunk);
125 }
126}
127
128unsafe extern "C" fn msg_recv_cb(
134 _ctx: wslay_event_context_ptr,
135 arg: *const wslay_event_on_msg_recv_arg,
136 user_data: *mut c_void,
137) {
138 let arg = &*arg;
139 let event = match arg.opcode {
142 0x8 => {
143 let reason = if arg.msg_length >= 2 && !arg.msg.is_null() {
145 slice::from_raw_parts(arg.msg.add(2), arg.msg_length - 2).to_vec()
146 } else {
147 Vec::new()
148 };
149 Some(ControlEvent::Close {
150 code: arg.status_code,
151 reason,
152 })
153 }
154 0x9 | 0xA => {
155 let payload = if arg.msg.is_null() || arg.msg_length == 0 {
156 Vec::new()
157 } else {
158 slice::from_raw_parts(arg.msg, arg.msg_length).to_vec()
159 };
160 Some(if arg.opcode == 0x9 {
161 ControlEvent::Ping(payload)
162 } else {
163 ControlEvent::Pong(payload)
164 })
165 }
166 _ => None,
167 };
168 if let Some(ev) = event {
169 shared(user_data).borrow_mut().control_events.push(ev);
170 }
171}
172
173struct CtxGuard(usize);
177impl Drop for CtxGuard {
178 fn drop(&mut self) {
179 unsafe { wslay_event_context_free(self.0 as wslay_event_context_ptr) };
180 }
181}
182
183#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct CloseFrame {
187 pub code: u16,
188 pub reason: String,
189}
190
191impl Default for CloseFrame {
192 fn default() -> Self {
194 Self {
195 code: wslay_status_code_WSLAY_CODE_NORMAL_CLOSURE as u16,
196 reason: String::new(),
197 }
198 }
199}
200
201enum ControlCmd {
203 Ping(Vec<u8>),
204 Pong(Vec<u8>),
205 Close(CloseFrame),
206}
207
208#[derive(Clone)]
212pub struct WsControl {
213 tx: mpsc::UnboundedSender<ControlCmd>,
214}
215
216impl WsControl {
217 pub fn ping(&self, payload: impl Into<Vec<u8>>) -> io::Result<()> {
220 self.send(ControlCmd::Ping(payload.into()))
221 }
222
223 pub fn pong(&self, payload: impl Into<Vec<u8>>) -> io::Result<()> {
225 self.send(ControlCmd::Pong(payload.into()))
226 }
227
228 pub fn close(&self, code: u16, reason: impl Into<String>) -> io::Result<()> {
231 self.send(ControlCmd::Close(CloseFrame {
232 code,
233 reason: reason.into(),
234 }))
235 }
236
237 fn send(&self, cmd: ControlCmd) -> io::Result<()> {
238 self.tx
239 .send(cmd)
240 .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "bridge has ended"))
241 }
242}
243
244pub struct ControlReceiver(mpsc::UnboundedReceiver<ControlCmd>);
247
248pub fn control_channel() -> (WsControl, ControlReceiver) {
251 let (tx, rx) = mpsc::unbounded_channel();
252 (WsControl { tx }, ControlReceiver(rx))
253}
254
255pub type CloseHook = Box<dyn FnMut(&CloseFrame) + Send>;
257pub type ControlHook = Box<dyn FnMut(&[u8]) + Send>;
259
260#[derive(Debug, Clone)]
268pub struct KeepAlive {
269 pub interval: Duration,
271 pub timeout: Duration,
273 pub close: CloseFrame,
276}
277
278impl KeepAlive {
279 pub fn new(interval: Duration, timeout: Duration) -> Self {
282 Self {
283 interval,
284 timeout,
285 close: CloseFrame {
286 code: wslay_status_code_WSLAY_CODE_GOING_AWAY as u16,
287 reason: "keepalive timeout".to_string(),
288 },
289 }
290 }
291}
292
293#[derive(Default)]
300pub struct BridgeConfig {
301 pub close: CloseFrame,
304 pub keepalive: Option<KeepAlive>,
307 pub control: Option<ControlReceiver>,
309 pub on_close: Option<CloseHook>,
313 pub on_ping: Option<ControlHook>,
315 pub on_pong: Option<ControlHook>,
317}
318
319enum EndReason {
321 PeerClose(CloseFrame),
323 LocalClose(CloseFrame),
325 Abnormal,
327}
328
329unsafe fn queue_msg(ctx: wslay_event_context_ptr, opcode: u8, payload: &[u8]) {
331 let msg = wslay_event_msg {
332 opcode,
333 msg: payload.as_ptr(),
334 msg_length: payload.len(),
335 };
336 wslay_event_queue_msg(ctx, &msg);
337}
338
339unsafe fn queue_close(ctx: wslay_event_context_ptr, code: u16, reason: &[u8]) {
341 let (ptr, len) = if reason.is_empty() {
342 (ptr::null(), 0)
343 } else {
344 (reason.as_ptr(), reason.len())
345 };
346 wslay_event_queue_close(ctx, code, ptr, len);
347}
348
349pub async fn bridge<S, P>(ws_io: S, peer: P) -> io::Result<()>
357where
358 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
359 P: AsyncRead + AsyncWrite + Unpin + Send + 'static,
360{
361 bridge_with(ws_io, peer, BridgeConfig::default()).await
362}
363
364pub async fn bridge_with<S, P>(ws_io: S, peer: P, config: BridgeConfig) -> io::Result<()>
371where
372 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
373 P: AsyncRead + AsyncWrite + Unpin + Send + 'static,
374{
375 let BridgeConfig {
376 close,
377 keepalive,
378 control,
379 mut on_close,
380 mut on_ping,
381 mut on_pong,
382 } = config;
383
384 let (mut ws_read, ws_write) = tokio::io::split(ws_io);
385 let (mut peer_read, mut peer_write) = tokio::io::split(peer);
386 let ws_write = Arc::new(Mutex::new(ws_write));
387
388 let last_activity = Arc::new(std::sync::Mutex::new(Instant::now()));
392
393 let shared: Box<RefCell<Shared>> = Box::new(RefCell::new(Shared::default()));
396 let sp_addr = &*shared as *const RefCell<Shared> as usize;
397
398 let (ctx_addr, _guard) = {
400 let callbacks = wslay_event_callbacks {
401 recv_callback: Some(recv_cb),
402 send_callback: Some(send_cb),
403 genmask_callback: None, on_frame_recv_start_callback: Some(frame_start_cb),
405 on_frame_recv_chunk_callback: Some(frame_chunk_cb),
406 on_frame_recv_end_callback: None,
407 on_msg_recv_callback: Some(msg_recv_cb), };
409 let mut ctx_raw: wslay_event_context_ptr = ptr::null_mut();
410 let rc = unsafe {
411 wslay_event_context_server_init(&mut ctx_raw, &callbacks, sp_addr as *mut c_void)
412 };
413 if rc != 0 || ctx_raw.is_null() {
414 return Err(io::Error::other("wslay_event_context_server_init failed"));
415 }
416 unsafe { wslay_event_config_set_no_buffering(ctx_raw, 1) }; let addr = ctx_raw as usize;
418 (addr, CtxGuard(addr))
419 };
420
421 let ws_to_peer = {
424 let ws_write = ws_write.clone();
425 let last_activity = last_activity.clone();
426 async move {
427 let mut buf = vec![0u8; READ_CHUNK];
428 loop {
429 let n = ws_read.read(&mut buf).await?;
430 if n == 0 {
431 let _ = peer_write.shutdown().await;
432 return Ok(EndReason::Abnormal); }
434 *last_activity.lock().unwrap() = Instant::now();
435 let (to_peer, outbound, closed, events) = unsafe {
436 let ctx = ctx_addr as wslay_event_context_ptr;
437 let sp = &*(sp_addr as *const RefCell<Shared>);
438 sp.borrow_mut().inbound.extend_from_slice(&buf[..n]);
439 let recv_rc = wslay_event_recv(ctx); wslay_event_send(ctx); let mut s = sp.borrow_mut();
442 let pos = s.inbound_pos;
443 s.inbound.drain(..pos);
444 s.inbound_pos = 0;
445 let closed = recv_rc != 0 || wslay_event_get_close_received(ctx) != 0;
446 (
447 std::mem::take(&mut s.to_peer),
448 std::mem::take(&mut s.outbound),
449 closed,
450 std::mem::take(&mut s.control_events),
451 )
452 };
453 if !to_peer.is_empty() {
454 peer_write.write_all(&to_peer).await?;
455 }
456 let mut peer_close = None;
457 for event in events {
458 match event {
459 ControlEvent::Close { code, reason } => {
460 let code = if code == 0 {
461 wslay_status_code_WSLAY_CODE_NO_STATUS_RCVD as u16
462 } else {
463 code
464 };
465 peer_close = Some(CloseFrame {
466 code,
467 reason: String::from_utf8_lossy(&reason).into_owned(),
468 });
469 }
470 ControlEvent::Ping(p) => {
471 if let Some(cb) = on_ping.as_mut() {
472 cb(&p);
473 }
474 }
475 ControlEvent::Pong(p) => {
476 if let Some(cb) = on_pong.as_mut() {
477 cb(&p);
478 }
479 }
480 }
481 }
482 if !outbound.is_empty() {
483 ws_write.lock().await.write_all(&outbound).await?;
484 }
485 if closed {
486 let _ = peer_write.shutdown().await;
487 return Ok(peer_close.map_or(EndReason::Abnormal, EndReason::PeerClose));
488 }
489 }
490 }
491 };
492
493 let peer_to_ws = {
495 let ws_write = ws_write.clone();
496 async move {
497 let mut buf = vec![0u8; READ_CHUNK];
498 loop {
499 let n = match peer_read.read(&mut buf).await {
500 Ok(0) | Err(_) => {
501 let outbound = unsafe {
502 let ctx = ctx_addr as wslay_event_context_ptr;
503 queue_close(ctx, close.code, close.reason.as_bytes());
504 wslay_event_send(ctx);
505 std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
506 };
507 if !outbound.is_empty() {
508 let _ = ws_write.lock().await.write_all(&outbound).await;
509 }
510 return Ok(EndReason::LocalClose(close));
511 }
512 Ok(n) => n,
513 };
514 let outbound = unsafe {
515 let ctx = ctx_addr as wslay_event_context_ptr;
516 queue_msg(ctx, wslay_opcode_WSLAY_BINARY_FRAME as u8, &buf[..n]);
517 wslay_event_send(ctx);
518 std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
519 };
520 if !outbound.is_empty() {
521 ws_write.lock().await.write_all(&outbound).await?;
522 }
523 }
524 }
525 };
526
527 let control_dir = {
529 let ws_write = ws_write.clone();
530 async move {
531 let mut rx = match control {
532 Some(ControlReceiver(rx)) => rx,
533 None => return future::pending::<io::Result<EndReason>>().await,
536 };
537 while let Some(cmd) = rx.recv().await {
538 let outbound = unsafe {
539 let ctx = ctx_addr as wslay_event_context_ptr;
540 match &cmd {
541 ControlCmd::Ping(p) => queue_msg(ctx, wslay_opcode_WSLAY_PING as u8, p),
542 ControlCmd::Pong(p) => queue_msg(ctx, wslay_opcode_WSLAY_PONG as u8, p),
543 ControlCmd::Close(cf) => queue_close(ctx, cf.code, cf.reason.as_bytes()),
544 }
545 wslay_event_send(ctx);
546 std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
547 };
548 if !outbound.is_empty() {
549 ws_write.lock().await.write_all(&outbound).await?;
550 }
551 }
552 future::pending::<io::Result<EndReason>>().await
554 }
555 };
556
557 let keepalive_dir = {
559 let ws_write = ws_write.clone();
560 let last_activity = last_activity.clone();
561 async move {
562 let ka = match keepalive {
563 Some(ka) => ka,
564 None => return future::pending::<io::Result<EndReason>>().await,
565 };
566 loop {
567 let idle = last_activity.lock().unwrap().elapsed();
569 if idle < ka.interval {
570 tokio::time::sleep(ka.interval - idle).await;
571 continue;
572 }
573 let outbound = unsafe {
575 let ctx = ctx_addr as wslay_event_context_ptr;
576 queue_msg(ctx, wslay_opcode_WSLAY_PING as u8, b"");
577 wslay_event_send(ctx);
578 std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
579 };
580 if !outbound.is_empty() {
581 ws_write.lock().await.write_all(&outbound).await?;
582 }
583 let pinged_at = Instant::now();
584 tokio::time::sleep(ka.timeout).await;
585 if *last_activity.lock().unwrap() <= pinged_at {
587 let outbound = unsafe {
588 let ctx = ctx_addr as wslay_event_context_ptr;
589 queue_close(ctx, ka.close.code, ka.close.reason.as_bytes());
590 wslay_event_send(ctx);
591 std::mem::take(&mut (*(sp_addr as *const RefCell<Shared>)).borrow_mut().outbound)
592 };
593 if !outbound.is_empty() {
594 let _ = ws_write.lock().await.write_all(&outbound).await;
595 }
596 return Ok(EndReason::LocalClose(ka.close));
597 }
598 }
599 }
600 };
601
602 let result = tokio::select! {
605 r = ws_to_peer => r,
606 r = peer_to_ws => r,
607 r = control_dir => r,
608 r = keepalive_dir => r,
609 };
610 drop(_guard); drop(shared);
612
613 let closed_with = match &result {
615 Ok(EndReason::PeerClose(cf)) | Ok(EndReason::LocalClose(cf)) => cf.clone(),
616 Ok(EndReason::Abnormal) => CloseFrame {
617 code: wslay_status_code_WSLAY_CODE_ABNORMAL_CLOSURE as u16,
618 reason: String::new(),
619 },
620 Err(e) => CloseFrame {
621 code: wslay_status_code_WSLAY_CODE_ABNORMAL_CLOSURE as u16,
622 reason: e.to_string(),
623 },
624 };
625 if let Some(cb) = on_close.as_mut() {
626 cb(&closed_with);
627 }
628 result.map(|_| ())
629}