#![allow(dead_code)]
use std::collections::VecDeque;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Opcode {
Continuation = 0x0,
Text = 0x1,
Binary = 0x2,
Close = 0x8,
Ping = 0x9,
Pong = 0xA,
}
impl Opcode {
pub fn from_byte(byte: u8) -> Option<Self> {
match byte & 0x0F {
0x0 => Some(Opcode::Continuation),
0x1 => Some(Opcode::Text),
0x2 => Some(Opcode::Binary),
0x8 => Some(Opcode::Close),
0x9 => Some(Opcode::Ping),
0xA => Some(Opcode::Pong),
_ => None,
}
}
pub fn is_control(&self) -> bool {
matches!(self, Opcode::Close | Opcode::Ping | Opcode::Pong)
}
pub fn is_data(&self) -> bool {
matches!(self, Opcode::Text | Opcode::Binary | Opcode::Continuation)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CloseCode {
Normal,
GoingAway,
ProtocolError,
UnsupportedData,
NoStatus,
Abnormal,
InvalidPayload,
PolicyViolation,
MessageTooBig,
MissingExtension,
InternalError,
TlsHandshake,
Custom(u16),
}
impl CloseCode {
pub fn to_u16(&self) -> u16 {
match self {
CloseCode::Normal => 1000,
CloseCode::GoingAway => 1001,
CloseCode::ProtocolError => 1002,
CloseCode::UnsupportedData => 1003,
CloseCode::NoStatus => 1005,
CloseCode::Abnormal => 1006,
CloseCode::InvalidPayload => 1007,
CloseCode::PolicyViolation => 1008,
CloseCode::MessageTooBig => 1009,
CloseCode::MissingExtension => 1010,
CloseCode::InternalError => 1011,
CloseCode::TlsHandshake => 1015,
CloseCode::Custom(code) => *code,
}
}
pub fn from_u16(code: u16) -> Self {
match code {
1000 => CloseCode::Normal,
1001 => CloseCode::GoingAway,
1002 => CloseCode::ProtocolError,
1003 => CloseCode::UnsupportedData,
1005 => CloseCode::NoStatus,
1006 => CloseCode::Abnormal,
1007 => CloseCode::InvalidPayload,
1008 => CloseCode::PolicyViolation,
1009 => CloseCode::MessageTooBig,
1010 => CloseCode::MissingExtension,
1011 => CloseCode::InternalError,
1015 => CloseCode::TlsHandshake,
code => CloseCode::Custom(code),
}
}
}
#[derive(Debug, Clone)]
pub struct CloseFrame {
pub code: CloseCode,
pub reason: String,
}
impl CloseFrame {
pub fn new(code: CloseCode, reason: impl Into<String>) -> Self {
Self {
code,
reason: reason.into(),
}
}
pub fn normal() -> Self {
Self::new(CloseCode::Normal, "")
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(2 + self.reason.len());
let code = self.code.to_u16();
bytes.push((code >> 8) as u8);
bytes.push(code as u8);
bytes.extend(self.reason.as_bytes());
bytes
}
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 2 {
return Some(Self::new(CloseCode::NoStatus, ""));
}
let code = u16::from_be_bytes([data[0], data[1]]);
let reason = String::from_utf8_lossy(&data[2..]).to_string();
Some(Self::new(CloseCode::from_u16(code), reason))
}
}
#[derive(Debug, Clone)]
pub enum Message {
Text(String),
Binary(Vec<u8>),
Ping(Vec<u8>),
Pong(Vec<u8>),
Close(Option<CloseFrame>),
}
impl Message {
pub fn text(text: impl Into<String>) -> Self {
Message::Text(text.into())
}
pub fn binary(data: impl Into<Vec<u8>>) -> Self {
Message::Binary(data.into())
}
pub fn ping(data: impl Into<Vec<u8>>) -> Self {
Message::Ping(data.into())
}
pub fn pong(data: impl Into<Vec<u8>>) -> Self {
Message::Pong(data.into())
}
pub fn close(code: CloseCode, reason: impl Into<String>) -> Self {
Message::Close(Some(CloseFrame::new(code, reason)))
}
pub fn is_text(&self) -> bool {
matches!(self, Message::Text(_))
}
pub fn is_binary(&self) -> bool {
matches!(self, Message::Binary(_))
}
pub fn is_close(&self) -> bool {
matches!(self, Message::Close(_))
}
pub fn len(&self) -> usize {
match self {
Message::Text(s) => s.len(),
Message::Binary(d) => d.len(),
Message::Ping(d) => d.len(),
Message::Pong(d) => d.len(),
Message::Close(Some(f)) => 2 + f.reason.len(),
Message::Close(None) => 0,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn as_text(&self) -> Option<&str> {
match self {
Message::Text(s) => Some(s),
_ => None,
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
Message::Binary(d) => Some(d),
_ => None,
}
}
pub fn opcode(&self) -> Opcode {
match self {
Message::Text(_) => Opcode::Text,
Message::Binary(_) => Opcode::Binary,
Message::Ping(_) => Opcode::Ping,
Message::Pong(_) => Opcode::Pong,
Message::Close(_) => Opcode::Close,
}
}
pub fn payload(&self) -> Vec<u8> {
match self {
Message::Text(s) => s.as_bytes().to_vec(),
Message::Binary(d) => d.clone(),
Message::Ping(d) => d.clone(),
Message::Pong(d) => d.clone(),
Message::Close(Some(f)) => f.to_bytes(),
Message::Close(None) => vec![],
}
}
}
#[derive(Debug, Clone)]
pub struct Frame {
pub fin: bool,
pub rsv1: bool,
pub rsv2: bool,
pub rsv3: bool,
pub opcode: Opcode,
pub mask: Option<[u8; 4]>,
pub payload: Vec<u8>,
}
impl Frame {
pub fn new(opcode: Opcode, payload: Vec<u8>) -> Self {
Self {
fin: true,
rsv1: false,
rsv2: false,
rsv3: false,
opcode,
mask: None,
payload,
}
}
pub fn text(text: impl Into<String>) -> Self {
Self::new(Opcode::Text, text.into().into_bytes())
}
pub fn binary(data: impl Into<Vec<u8>>) -> Self {
Self::new(Opcode::Binary, data.into())
}
pub fn ping(data: impl Into<Vec<u8>>) -> Self {
Self::new(Opcode::Ping, data.into())
}
pub fn pong(data: impl Into<Vec<u8>>) -> Self {
Self::new(Opcode::Pong, data.into())
}
pub fn close(code: CloseCode, reason: &str) -> Self {
let payload = CloseFrame::new(code, reason).to_bytes();
Self::new(Opcode::Close, payload)
}
pub fn with_fin(mut self, fin: bool) -> Self {
self.fin = fin;
self
}
pub fn with_mask(mut self, mask: [u8; 4]) -> Self {
self.mask = Some(mask);
self
}
pub fn with_random_mask(mut self) -> Self {
let mask: [u8; 4] = rand::random();
self.mask = Some(mask);
self
}
fn masked_payload(&self) -> Vec<u8> {
match self.mask {
Some(mask) => self
.payload
.iter()
.enumerate()
.map(|(i, b)| b ^ mask[i % 4])
.collect(),
None => self.payload.clone(),
}
}
pub fn encode(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(14 + self.payload.len());
let mut byte0 = self.opcode as u8;
if self.fin {
byte0 |= 0x80;
}
if self.rsv1 {
byte0 |= 0x40;
}
if self.rsv2 {
byte0 |= 0x20;
}
if self.rsv3 {
byte0 |= 0x10;
}
bytes.push(byte0);
let len = self.payload.len();
let mask_bit = if self.mask.is_some() { 0x80 } else { 0 };
if len < 126 {
bytes.push(mask_bit | len as u8);
} else if len < 65536 {
bytes.push(mask_bit | 126);
bytes.extend(&(len as u16).to_be_bytes());
} else {
bytes.push(mask_bit | 127);
bytes.extend(&(len as u64).to_be_bytes());
}
if let Some(mask) = self.mask {
bytes.extend(&mask);
}
bytes.extend(self.masked_payload());
bytes
}
pub fn decode(data: &[u8]) -> Result<(Self, usize), WebSocketError> {
if data.len() < 2 {
return Err(WebSocketError::IncompleteFrame);
}
let byte0 = data[0];
let byte1 = data[1];
let fin = byte0 & 0x80 != 0;
let rsv1 = byte0 & 0x40 != 0;
let rsv2 = byte0 & 0x20 != 0;
let rsv3 = byte0 & 0x10 != 0;
let opcode = Opcode::from_byte(byte0).ok_or(WebSocketError::InvalidOpcode(byte0 & 0x0F))?;
let masked = byte1 & 0x80 != 0;
let len_byte = byte1 & 0x7F;
let mut offset = 2;
let payload_len: usize;
if len_byte < 126 {
payload_len = len_byte as usize;
} else if len_byte == 126 {
if data.len() < 4 {
return Err(WebSocketError::IncompleteFrame);
}
payload_len = u16::from_be_bytes([data[2], data[3]]) as usize;
offset = 4;
} else {
if data.len() < 10 {
return Err(WebSocketError::IncompleteFrame);
}
payload_len = u64::from_be_bytes([
data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9],
]) as usize;
offset = 10;
}
let mask = if masked {
if data.len() < offset + 4 {
return Err(WebSocketError::IncompleteFrame);
}
let m = [
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
];
offset += 4;
Some(m)
} else {
None
};
if data.len() < offset + payload_len {
return Err(WebSocketError::IncompleteFrame);
}
let mut payload = data[offset..offset + payload_len].to_vec();
if let Some(m) = mask {
for (i, b) in payload.iter_mut().enumerate() {
*b ^= m[i % 4];
}
}
let frame = Frame {
fin,
rsv1,
rsv2,
rsv3,
opcode,
mask,
payload,
};
Ok((frame, offset + payload_len))
}
}
#[derive(Debug, Clone)]
pub enum WebSocketError {
ConnectionClosed,
ConnectionFailed(String),
SendFailed(String),
InvalidOpcode(u8),
IncompleteFrame,
InvalidUtf8,
MessageTooLarge(usize),
ProtocolError(String),
HandshakeFailed(String),
IoError(String),
Timeout,
}
impl std::fmt::Display for WebSocketError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WebSocketError::ConnectionClosed => write!(f, "Connection closed"),
WebSocketError::ConnectionFailed(msg) => write!(f, "Connection failed: {}", msg),
WebSocketError::SendFailed(msg) => write!(f, "Send failed: {}", msg),
WebSocketError::InvalidOpcode(op) => write!(f, "Invalid opcode: {}", op),
WebSocketError::IncompleteFrame => write!(f, "Incomplete frame"),
WebSocketError::InvalidUtf8 => write!(f, "Invalid UTF-8 in text frame"),
WebSocketError::MessageTooLarge(size) => write!(f, "Message too large: {} bytes", size),
WebSocketError::ProtocolError(msg) => write!(f, "Protocol error: {}", msg),
WebSocketError::HandshakeFailed(msg) => write!(f, "Handshake failed: {}", msg),
WebSocketError::IoError(msg) => write!(f, "IO error: {}", msg),
WebSocketError::Timeout => write!(f, "Timeout"),
}
}
}
impl std::error::Error for WebSocketError {}
#[derive(Debug, Clone)]
pub struct WebSocketConfig {
pub max_message_size: usize,
pub max_frame_size: usize,
pub compression: bool,
pub ping_interval: Option<Duration>,
pub pong_timeout: Duration,
pub write_buffer_size: usize,
pub read_buffer_size: usize,
pub auto_pong: bool,
pub auto_close: bool,
}
impl Default for WebSocketConfig {
fn default() -> Self {
Self {
max_message_size: 64 * 1024 * 1024, max_frame_size: 16 * 1024 * 1024, compression: false,
ping_interval: Some(Duration::from_secs(30)),
pong_timeout: Duration::from_secs(30),
write_buffer_size: 128 * 1024, read_buffer_size: 128 * 1024, auto_pong: true,
auto_close: true,
}
}
}
impl WebSocketConfig {
pub fn new() -> Self {
Self::default()
}
pub fn max_message_size(mut self, size: usize) -> Self {
self.max_message_size = size;
self
}
pub fn max_frame_size(mut self, size: usize) -> Self {
self.max_frame_size = size;
self
}
pub fn with_compression(mut self) -> Self {
self.compression = true;
self
}
pub fn ping_interval(mut self, interval: Duration) -> Self {
self.ping_interval = Some(interval);
self
}
pub fn no_ping(mut self) -> Self {
self.ping_interval = None;
self
}
pub fn tor_optimized() -> Self {
Self {
max_message_size: 16 * 1024 * 1024, max_frame_size: 4 * 1024 * 1024, compression: true, ping_interval: Some(Duration::from_secs(60)), pong_timeout: Duration::from_secs(120), write_buffer_size: 64 * 1024,
read_buffer_size: 64 * 1024,
auto_pong: true,
auto_close: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WebSocketState {
Connecting,
Open,
Closing,
Closed,
}
#[derive(Debug)]
pub struct WebSocketClient {
state: WebSocketState,
config: WebSocketConfig,
send_queue: VecDeque<Frame>,
recv_queue: VecDeque<Message>,
fragment_buffer: Option<(Opcode, Vec<u8>)>,
bytes_sent: AtomicU64,
bytes_received: AtomicU64,
messages_sent: AtomicU64,
messages_received: AtomicU64,
last_ping: Option<Instant>,
awaiting_pong: AtomicBool,
url: String,
}
impl WebSocketClient {
pub fn new(url: impl Into<String>) -> Self {
Self::with_config(url, WebSocketConfig::default())
}
pub fn with_config(url: impl Into<String>, config: WebSocketConfig) -> Self {
Self {
state: WebSocketState::Connecting,
config,
send_queue: VecDeque::new(),
recv_queue: VecDeque::new(),
fragment_buffer: None,
bytes_sent: AtomicU64::new(0),
bytes_received: AtomicU64::new(0),
messages_sent: AtomicU64::new(0),
messages_received: AtomicU64::new(0),
last_ping: None,
awaiting_pong: AtomicBool::new(false),
url: url.into(),
}
}
pub fn tor_optimized(url: impl Into<String>) -> Self {
Self::with_config(url, WebSocketConfig::tor_optimized())
}
pub fn url(&self) -> &str {
&self.url
}
pub fn state(&self) -> WebSocketState {
self.state
}
pub fn is_open(&self) -> bool {
self.state == WebSocketState::Open
}
pub fn is_closed(&self) -> bool {
self.state == WebSocketState::Closed
}
pub fn open(&mut self) {
self.state = WebSocketState::Open;
}
pub fn send(&mut self, msg: Message) -> Result<(), WebSocketError> {
if self.state != WebSocketState::Open {
return Err(WebSocketError::ConnectionClosed);
}
if msg.len() > self.config.max_message_size {
return Err(WebSocketError::MessageTooLarge(msg.len()));
}
let frame = match msg {
Message::Text(text) => Frame::text(text).with_random_mask(),
Message::Binary(data) => Frame::binary(data).with_random_mask(),
Message::Ping(data) => Frame::ping(data).with_random_mask(),
Message::Pong(data) => Frame::pong(data).with_random_mask(),
Message::Close(frame) => {
let (code, reason) = frame
.map(|f| (f.code, f.reason))
.unwrap_or((CloseCode::Normal, String::new()));
Frame::close(code, &reason).with_random_mask()
}
};
self.send_queue.push_back(frame);
self.messages_sent.fetch_add(1, Ordering::Relaxed);
Ok(())
}
pub fn send_text(&mut self, text: impl Into<String>) -> Result<(), WebSocketError> {
self.send(Message::text(text))
}
pub fn send_binary(&mut self, data: impl Into<Vec<u8>>) -> Result<(), WebSocketError> {
self.send(Message::binary(data))
}
pub fn process_frame(&mut self, frame: Frame) -> Result<Option<Message>, WebSocketError> {
self.bytes_received
.fetch_add(frame.payload.len() as u64, Ordering::Relaxed);
match frame.opcode {
Opcode::Text => {
if frame.fin {
let text = String::from_utf8(frame.payload)
.map_err(|_| WebSocketError::InvalidUtf8)?;
self.messages_received.fetch_add(1, Ordering::Relaxed);
Ok(Some(Message::Text(text)))
} else {
self.fragment_buffer = Some((Opcode::Text, frame.payload));
Ok(None)
}
}
Opcode::Binary => {
if frame.fin {
self.messages_received.fetch_add(1, Ordering::Relaxed);
Ok(Some(Message::Binary(frame.payload)))
} else {
self.fragment_buffer = Some((Opcode::Binary, frame.payload));
Ok(None)
}
}
Opcode::Continuation => {
if let Some((opcode, mut buffer)) = self.fragment_buffer.take() {
buffer.extend(frame.payload);
if buffer.len() > self.config.max_message_size {
return Err(WebSocketError::MessageTooLarge(buffer.len()));
}
if frame.fin {
self.messages_received.fetch_add(1, Ordering::Relaxed);
match opcode {
Opcode::Text => {
let text = String::from_utf8(buffer)
.map_err(|_| WebSocketError::InvalidUtf8)?;
Ok(Some(Message::Text(text)))
}
Opcode::Binary => Ok(Some(Message::Binary(buffer))),
_ => Err(WebSocketError::ProtocolError("Invalid continuation".into())),
}
} else {
self.fragment_buffer = Some((opcode, buffer));
Ok(None)
}
} else {
Err(WebSocketError::ProtocolError(
"Unexpected continuation".into(),
))
}
}
Opcode::Ping => {
if self.config.auto_pong {
self.send(Message::Pong(frame.payload.clone()))?;
}
Ok(Some(Message::Ping(frame.payload)))
}
Opcode::Pong => {
self.awaiting_pong.store(false, Ordering::Relaxed);
Ok(Some(Message::Pong(frame.payload)))
}
Opcode::Close => {
let close_frame = CloseFrame::from_bytes(&frame.payload);
self.state = WebSocketState::Closing;
if self.config.auto_close {
let _ = self.send(Message::Close(close_frame.clone()));
self.state = WebSocketState::Closed;
}
Ok(Some(Message::Close(close_frame)))
}
}
}
pub fn should_ping(&self) -> bool {
if let Some(interval) = self.config.ping_interval {
if let Some(last) = self.last_ping {
return last.elapsed() >= interval && !self.awaiting_pong.load(Ordering::Relaxed);
}
return true;
}
false
}
pub fn send_ping(&mut self) -> Result<(), WebSocketError> {
let data = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
.to_be_bytes()
.to_vec();
self.send(Message::Ping(data))?;
self.last_ping = Some(Instant::now());
self.awaiting_pong.store(true, Ordering::Relaxed);
Ok(())
}
pub fn close(&mut self, code: CloseCode, reason: &str) -> Result<(), WebSocketError> {
if self.state == WebSocketState::Open {
self.state = WebSocketState::Closing;
self.send(Message::close(code, reason))?;
}
Ok(())
}
pub fn pending_frames(&mut self) -> Vec<Frame> {
self.send_queue.drain(..).collect()
}
pub fn stats(&self) -> WebSocketStats {
WebSocketStats {
bytes_sent: self.bytes_sent.load(Ordering::Relaxed),
bytes_received: self.bytes_received.load(Ordering::Relaxed),
messages_sent: self.messages_sent.load(Ordering::Relaxed),
messages_received: self.messages_received.load(Ordering::Relaxed),
}
}
}
#[derive(Debug, Clone)]
pub struct WebSocketStats {
pub bytes_sent: u64,
pub bytes_received: u64,
pub messages_sent: u64,
pub messages_received: u64,
}
pub trait WebSocketHandler: Send + Sync {
fn on_open(&self, _conn_id: u64) {}
fn on_message(&self, conn_id: u64, message: Message) -> Option<Message>;
fn on_error(&self, _conn_id: u64, _error: WebSocketError) {}
fn on_close(&self, _conn_id: u64, _frame: Option<CloseFrame>) {}
}
#[derive(Debug, Default)]
pub struct EchoHandler;
impl WebSocketHandler for EchoHandler {
fn on_message(&self, _conn_id: u64, message: Message) -> Option<Message> {
match message {
Message::Text(_) | Message::Binary(_) => Some(message),
_ => None,
}
}
}
#[derive(Debug)]
pub struct WebSocketConnection {
pub id: u64,
state: WebSocketState,
config: WebSocketConfig,
fragment_buffer: Option<(Opcode, Vec<u8>)>,
pub remote: String,
}
impl WebSocketConnection {
pub fn new(id: u64, remote: impl Into<String>, config: WebSocketConfig) -> Self {
Self {
id,
state: WebSocketState::Open,
config,
fragment_buffer: None,
remote: remote.into(),
}
}
pub fn state(&self) -> WebSocketState {
self.state
}
pub fn process_frame(&mut self, frame: Frame) -> Result<Option<Message>, WebSocketError> {
match frame.opcode {
Opcode::Text => {
if frame.fin {
let text = String::from_utf8(frame.payload)
.map_err(|_| WebSocketError::InvalidUtf8)?;
Ok(Some(Message::Text(text)))
} else {
self.fragment_buffer = Some((Opcode::Text, frame.payload));
Ok(None)
}
}
Opcode::Binary => {
if frame.fin {
Ok(Some(Message::Binary(frame.payload)))
} else {
self.fragment_buffer = Some((Opcode::Binary, frame.payload));
Ok(None)
}
}
Opcode::Continuation => {
if let Some((opcode, mut buffer)) = self.fragment_buffer.take() {
buffer.extend(frame.payload);
if buffer.len() > self.config.max_message_size {
return Err(WebSocketError::MessageTooLarge(buffer.len()));
}
if frame.fin {
match opcode {
Opcode::Text => {
let text = String::from_utf8(buffer)
.map_err(|_| WebSocketError::InvalidUtf8)?;
Ok(Some(Message::Text(text)))
}
Opcode::Binary => Ok(Some(Message::Binary(buffer))),
_ => Err(WebSocketError::ProtocolError("Invalid continuation".into())),
}
} else {
self.fragment_buffer = Some((opcode, buffer));
Ok(None)
}
} else {
Err(WebSocketError::ProtocolError(
"Unexpected continuation".into(),
))
}
}
Opcode::Ping => Ok(Some(Message::Ping(frame.payload))),
Opcode::Pong => Ok(Some(Message::Pong(frame.payload))),
Opcode::Close => {
let close_frame = CloseFrame::from_bytes(&frame.payload);
self.state = WebSocketState::Closed;
Ok(Some(Message::Close(close_frame)))
}
}
}
pub fn create_frame(&self, msg: Message) -> Frame {
match msg {
Message::Text(text) => Frame::text(text),
Message::Binary(data) => Frame::binary(data),
Message::Ping(data) => Frame::ping(data),
Message::Pong(data) => Frame::pong(data),
Message::Close(frame) => {
let (code, reason) = frame
.map(|f| (f.code, f.reason))
.unwrap_or((CloseCode::Normal, String::new()));
Frame::close(code, &reason)
}
}
}
}
pub struct WebSocketServer {
config: WebSocketConfig,
connections: std::collections::HashMap<u64, WebSocketConnection>,
next_conn_id: AtomicU64,
handler: Arc<dyn WebSocketHandler>,
}
impl std::fmt::Debug for WebSocketServer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WebSocketServer")
.field("config", &self.config)
.field("connections", &self.connections.len())
.field("next_conn_id", &self.next_conn_id)
.finish()
}
}
impl WebSocketServer {
pub fn new<H: WebSocketHandler + 'static>(handler: H) -> Self {
Self::with_config(handler, WebSocketConfig::default())
}
pub fn with_config<H: WebSocketHandler + 'static>(handler: H, config: WebSocketConfig) -> Self {
Self {
config,
connections: std::collections::HashMap::new(),
next_conn_id: AtomicU64::new(1),
handler: Arc::new(handler),
}
}
pub fn accept(&mut self, remote: impl Into<String>) -> u64 {
let conn_id = self.next_conn_id.fetch_add(1, Ordering::Relaxed);
let conn = WebSocketConnection::new(conn_id, remote, self.config.clone());
self.connections.insert(conn_id, conn);
self.handler.on_open(conn_id);
conn_id
}
pub fn handle_frame(&mut self, conn_id: u64, frame: Frame) -> Option<Frame> {
let conn = self.connections.get_mut(&conn_id)?;
match conn.process_frame(frame) {
Ok(Some(msg)) => {
if let Message::Ping(data) = &msg {
return Some(Frame::pong(data.clone()));
}
if let Message::Close(ref cf) = msg {
self.handler.on_close(conn_id, cf.clone());
self.connections.remove(&conn_id);
let (code, reason) = cf
.clone()
.map(|f| (f.code, f.reason))
.unwrap_or((CloseCode::Normal, String::new()));
return Some(Frame::close(code, &reason));
}
if let Some(response) = self.handler.on_message(conn_id, msg) {
let conn = self.connections.get(&conn_id)?;
return Some(conn.create_frame(response));
}
}
Ok(None) => {
}
Err(e) => {
self.handler.on_error(conn_id, e);
}
}
None
}
pub fn close(&mut self, conn_id: u64, code: CloseCode, reason: &str) -> Option<Frame> {
if self.connections.remove(&conn_id).is_some() {
self.handler
.on_close(conn_id, Some(CloseFrame::new(code, reason.to_string())));
Some(Frame::close(code, reason))
} else {
None
}
}
pub fn connection_count(&self) -> usize {
self.connections.len()
}
pub fn connection_ids(&self) -> Vec<u64> {
self.connections.keys().copied().collect()
}
}
pub fn generate_accept_key(key: &str) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
let combined = format!("{}{}", key, magic);
let mut hasher = DefaultHasher::new();
combined.hash(&mut hasher);
let hash = hasher.finish();
base64_encode(&hash.to_be_bytes())
}
fn base64_encode(data: &[u8]) -> String {
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut result = String::new();
let mut i = 0;
while i < data.len() {
let b0 = data[i] as u32;
let b1 = data.get(i + 1).copied().unwrap_or(0) as u32;
let b2 = data.get(i + 2).copied().unwrap_or(0) as u32;
let n = (b0 << 16) | (b1 << 8) | b2;
result.push(CHARS[((n >> 18) & 0x3F) as usize] as char);
result.push(CHARS[((n >> 12) & 0x3F) as usize] as char);
if i + 1 < data.len() {
result.push(CHARS[((n >> 6) & 0x3F) as usize] as char);
} else {
result.push('=');
}
if i + 2 < data.len() {
result.push(CHARS[(n & 0x3F) as usize] as char);
} else {
result.push('=');
}
i += 3;
}
result
}
pub fn generate_client_key() -> String {
let random_bytes: [u8; 16] = rand::random();
base64_encode(&random_bytes)
}
#[derive(Debug)]
pub struct UpgradeRequest {
pub host: String,
pub path: String,
pub key: String,
pub version: u8,
pub protocols: Vec<String>,
pub extensions: Vec<String>,
}
impl UpgradeRequest {
pub fn new(host: impl Into<String>, path: impl Into<String>) -> Self {
Self {
host: host.into(),
path: path.into(),
key: generate_client_key(),
version: 13,
protocols: Vec::new(),
extensions: Vec::new(),
}
}
pub fn with_protocol(mut self, protocol: impl Into<String>) -> Self {
self.protocols.push(protocol.into());
self
}
pub fn with_extension(mut self, extension: impl Into<String>) -> Self {
self.extensions.push(extension.into());
self
}
pub fn build(&self) -> String {
let mut request = format!(
"GET {} HTTP/1.1\r\n\
Host: {}\r\n\
Upgrade: websocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Key: {}\r\n\
Sec-WebSocket-Version: {}\r\n",
self.path, self.host, self.key, self.version
);
if !self.protocols.is_empty() {
request.push_str(&format!(
"Sec-WebSocket-Protocol: {}\r\n",
self.protocols.join(", ")
));
}
if !self.extensions.is_empty() {
request.push_str(&format!(
"Sec-WebSocket-Extensions: {}\r\n",
self.extensions.join(", ")
));
}
request.push_str("\r\n");
request
}
pub fn expected_accept(&self) -> String {
generate_accept_key(&self.key)
}
}
#[derive(Debug)]
pub struct UpgradeResponse {
pub accept: String,
pub protocol: Option<String>,
pub extensions: Vec<String>,
}
impl UpgradeResponse {
pub fn from_key(key: &str) -> Self {
Self {
accept: generate_accept_key(key),
protocol: None,
extensions: Vec::new(),
}
}
pub fn with_protocol(mut self, protocol: impl Into<String>) -> Self {
self.protocol = Some(protocol.into());
self
}
pub fn build(&self) -> String {
let mut response = format!(
"HTTP/1.1 101 Switching Protocols\r\n\
Upgrade: websocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Accept: {}\r\n",
self.accept
);
if let Some(ref proto) = self.protocol {
response.push_str(&format!("Sec-WebSocket-Protocol: {}\r\n", proto));
}
if !self.extensions.is_empty() {
response.push_str(&format!(
"Sec-WebSocket-Extensions: {}\r\n",
self.extensions.join(", ")
));
}
response.push_str("\r\n");
response
}
}
use arti_client::{IsolationToken as ArtiIsolationToken, StreamPrefs, TorClient as ArtiClient};
use futures::{SinkExt, StreamExt};
use tokio_tungstenite::WebSocketStream;
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
use tokio_tungstenite::tungstenite::http::Uri;
use tokio_tungstenite::tungstenite::protocol::Message as TungsteniteMessage;
use tor_rtcompat::PreferredRuntime;
pub struct TorWebSocket {
inner: WebSocketStream<arti_client::DataStream>,
url: String,
}
impl TorWebSocket {
pub async fn connect(
tor_client: &Arc<ArtiClient<PreferredRuntime>>,
url: &str,
) -> Result<Self, WebSocketError> {
Self::connect_with_isolation(tor_client, url, None).await
}
pub async fn connect_with_isolation(
tor_client: &Arc<ArtiClient<PreferredRuntime>>,
url: &str,
isolation_token: Option<ArtiIsolationToken>,
) -> Result<Self, WebSocketError> {
let uri: Uri = url
.parse()
.map_err(|e| WebSocketError::ConnectionFailed(format!("Invalid URL: {}", e)))?;
let host = uri
.host()
.ok_or_else(|| WebSocketError::ConnectionFailed("Missing host in URL".into()))?;
let is_tls = uri.scheme_str() == Some("wss");
let port = uri.port_u16().unwrap_or(if is_tls { 443 } else { 80 });
if is_tls && host.ends_with(".onion") {
tracing::warn!(
"Using wss:// with .onion address is unnecessary - Tor provides encryption"
);
}
let data_stream = if let Some(token) = isolation_token {
let mut prefs = StreamPrefs::new();
prefs.set_isolation(token);
tor_client.connect_with_prefs((host, port), &prefs).await
} else {
tor_client.connect((host, port)).await
}
.map_err(|e| WebSocketError::ConnectionFailed(format!("Tor connection failed: {}", e)))?;
tracing::debug!("Tor connection established to {}:{}", host, port);
let request = url
.into_client_request()
.map_err(|e| WebSocketError::ConnectionFailed(format!("Invalid request: {}", e)))?;
let (ws_stream, _response) = tokio_tungstenite::client_async(request, data_stream)
.await
.map_err(|e| {
WebSocketError::ConnectionFailed(format!("WebSocket handshake failed: {}", e))
})?;
tracing::info!("WebSocket connection established over Tor to {}", url);
Ok(Self {
inner: ws_stream,
url: url.to_string(),
})
}
pub async fn send_text(&mut self, text: impl Into<String>) -> Result<(), WebSocketError> {
self.inner
.send(TungsteniteMessage::Text(text.into().into()))
.await
.map_err(|e| WebSocketError::SendFailed(e.to_string()))
}
pub async fn send_binary(&mut self, data: impl Into<Vec<u8>>) -> Result<(), WebSocketError> {
self.inner
.send(TungsteniteMessage::Binary(data.into().into()))
.await
.map_err(|e| WebSocketError::SendFailed(e.to_string()))
}
pub async fn send_ping(&mut self, data: Vec<u8>) -> Result<(), WebSocketError> {
self.inner
.send(TungsteniteMessage::Ping(data.into()))
.await
.map_err(|e| WebSocketError::SendFailed(e.to_string()))
}
pub async fn close(&mut self) -> Result<(), WebSocketError> {
self.inner
.close(None)
.await
.map_err(|e| WebSocketError::ConnectionFailed(e.to_string()))
}
pub async fn recv(&mut self) -> Result<Option<Message>, WebSocketError> {
loop {
match self.inner.next().await {
Some(Ok(msg)) => {
let converted = match msg {
TungsteniteMessage::Text(s) => Message::Text(s.to_string()),
TungsteniteMessage::Binary(b) => Message::Binary(b.to_vec()),
TungsteniteMessage::Ping(p) => Message::Ping(p.to_vec()),
TungsteniteMessage::Pong(p) => Message::Pong(p.to_vec()),
TungsteniteMessage::Close(c) => {
let frame = c.map(|cf| CloseFrame {
code: CloseCode::from_u16(cf.code.into()),
reason: cf.reason.to_string(),
});
Message::Close(frame)
}
TungsteniteMessage::Frame(_) => {
continue;
}
};
return Ok(Some(converted));
}
Some(Err(e)) => return Err(WebSocketError::ProtocolError(e.to_string())),
None => return Ok(None),
}
}
}
pub fn url(&self) -> &str {
&self.url
}
}
pub struct TorWebSocketBuilder {
tor_client: Arc<ArtiClient<PreferredRuntime>>,
isolation_token: Option<ArtiIsolationToken>,
subprotocols: Vec<String>,
}
impl TorWebSocketBuilder {
pub fn new(tor_client: Arc<ArtiClient<PreferredRuntime>>) -> Self {
Self {
tor_client,
isolation_token: None,
subprotocols: Vec::new(),
}
}
pub fn isolation(mut self, token: ArtiIsolationToken) -> Self {
self.isolation_token = Some(token);
self
}
pub fn subprotocol(mut self, protocol: impl Into<String>) -> Self {
self.subprotocols.push(protocol.into());
self
}
pub async fn connect(self, url: &str) -> Result<TorWebSocket, WebSocketError> {
TorWebSocket::connect_with_isolation(&self.tor_client, url, self.isolation_token).await
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
#[test]
fn test_frame_encode_decode() {
let original = Frame::text("Hello, World!");
let encoded = original.encode();
let (decoded, len) = Frame::decode(&encoded).unwrap();
assert_eq!(len, encoded.len());
assert_eq!(decoded.opcode, Opcode::Text);
assert!(decoded.fin);
assert_eq!(decoded.payload, b"Hello, World!");
}
#[test]
fn test_frame_with_mask() {
let frame = Frame::text("Test").with_mask([0x37, 0xfa, 0x21, 0x3d]);
let encoded = frame.encode();
let (decoded, _) = Frame::decode(&encoded).unwrap();
assert_eq!(decoded.payload, b"Test");
}
#[test]
fn test_message_types() {
assert!(Message::text("hello").is_text());
assert!(Message::binary(vec![1, 2, 3]).is_binary());
assert!(Message::close(CloseCode::Normal, "bye").is_close());
}
#[test]
fn test_close_codes() {
assert_eq!(CloseCode::Normal.to_u16(), 1000);
assert_eq!(CloseCode::from_u16(1001), CloseCode::GoingAway);
assert_eq!(CloseCode::from_u16(4000), CloseCode::Custom(4000));
}
#[test]
fn test_client_send_receive() {
let mut client = WebSocketClient::new("ws://test.onion/ws");
client.open();
client.send_text("Hello").unwrap();
assert_eq!(client.stats().messages_sent, 1);
let frame = Frame::text("World");
let msg = client.process_frame(frame).unwrap().unwrap();
assert_eq!(msg.as_text(), Some("World"));
}
#[test]
fn test_echo_server() {
let mut server = WebSocketServer::new(EchoHandler::default());
let conn_id = server.accept("127.0.0.1:12345");
assert_eq!(server.connection_count(), 1);
let frame = Frame::text("Echo test").with_mask([1, 2, 3, 4]);
let response = server.handle_frame(conn_id, frame).unwrap();
assert_eq!(response.opcode, Opcode::Text);
assert_eq!(response.payload, b"Echo test");
let ping = Frame::ping(vec![1, 2, 3]).with_mask([4, 5, 6, 7]);
let pong = server.handle_frame(conn_id, ping).unwrap();
assert_eq!(pong.opcode, Opcode::Pong);
}
#[test]
fn test_fragmented_message() {
let mut client = WebSocketClient::new("ws://test.onion");
client.open();
let frame1 = Frame::new(Opcode::Text, b"Hello, ".to_vec()).with_fin(false);
assert!(client.process_frame(frame1).unwrap().is_none());
let frame2 = Frame::new(Opcode::Continuation, b"World!".to_vec());
let msg = client.process_frame(frame2).unwrap().unwrap();
assert_eq!(msg.as_text(), Some("Hello, World!"));
}
#[test]
fn test_upgrade_handshake() {
let request = UpgradeRequest::new("example.onion", "/ws").with_protocol("chat");
let req_str = request.build();
assert!(req_str.contains("Upgrade: websocket"));
assert!(req_str.contains("Sec-WebSocket-Version: 13"));
assert!(req_str.contains("Sec-WebSocket-Protocol: chat"));
let response = UpgradeResponse::from_key(&request.key).with_protocol("chat");
let resp_str = response.build();
assert!(resp_str.contains("101 Switching Protocols"));
assert!(resp_str.contains(&request.expected_accept()));
}
#[test]
fn test_tor_optimized_config() {
let config = WebSocketConfig::tor_optimized();
assert!(config.compression);
assert!(config.ping_interval.unwrap() >= Duration::from_secs(60));
assert!(config.pong_timeout >= Duration::from_secs(60));
}
#[test]
fn test_large_frame() {
let data = vec![0u8; 100_000];
let frame = Frame::binary(data.clone());
let encoded = frame.encode();
let (decoded, _) = Frame::decode(&encoded).unwrap();
assert_eq!(decoded.payload.len(), 100_000);
assert_eq!(decoded.payload, data);
}
}