pub mod base64;
pub mod client;
pub mod conn;
pub mod deflate;
pub mod frame;
pub mod handshake;
pub mod sha1;
pub use conn::{Message, Role, WsConfig, WsError, WsReader, WsWriter, close_code};
pub use deflate::{DeflateConfig, DeflateParams};
use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
pub fn server<S>(
io: S,
cfg: WsConfig,
deflate: Option<(DeflateParams, DeflateConfig)>,
) -> (WsReader<ReadHalf<S>>, WsWriter<WriteHalf<S>>)
where
S: AsyncRead + AsyncWrite,
{
let (r, w) = tokio::io::split(io);
(
WsReader::new(r, Role::Server, cfg, deflate.map(|(p, _)| p), Vec::new()),
WsWriter::new(w, Role::Server, deflate),
)
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::io::duplex;
type End = (
WsReader<ReadHalf<tokio::io::DuplexStream>>,
WsWriter<WriteHalf<tokio::io::DuplexStream>>,
);
fn pair(deflate: bool) -> (End, End) {
let (a, b) = duplex(1 << 16);
let params = DeflateParams {
client_no_context_takeover: false,
server_no_context_takeover: true,
};
let cfg = DeflateConfig {
min_compress_size: 0,
..Default::default()
};
let d = deflate.then_some((params, cfg));
let server = server(a, WsConfig::default(), d);
let (r, w) = tokio::io::split(b);
let client = (
WsReader::new(
r,
Role::Client,
WsConfig::default(),
d.map(|x| x.0),
Vec::new(),
),
WsWriter::new(w, Role::Client, d),
);
(server, client)
}
#[tokio::test]
async fn text_both_directions_with_and_without_deflate() {
for deflate in [false, true] {
let ((mut sr, mut sw), (mut cr, mut cw)) = pair(deflate);
for i in 0..50 {
let msg = format!(r#"[2,"{i}","Heartbeat",{{}}] {}"#, "é".repeat(i * 20));
cw.send_text(&msg).await.unwrap();
assert_eq!(sr.recv().await.unwrap(), Some(Message::Text(msg.clone())));
sw.send_text(&msg).await.unwrap();
assert_eq!(cr.recv().await.unwrap(), Some(Message::Text(msg)));
}
cw.send_ping(b"hi").await.unwrap();
assert_eq!(
sr.recv().await.unwrap(),
Some(Message::Ping(b"hi".to_vec()))
);
sw.send_close(close_code::NORMAL, "bye").await.unwrap();
assert_eq!(
cr.recv().await.unwrap(),
Some(Message::Close(Some((1000, "bye".into()))))
);
assert!(sw.send_text("x").await.is_err());
}
}
#[tokio::test]
async fn rejects_unmasked_client_frames_and_reassembles_fragments() {
use tokio::io::AsyncWriteExt;
let (a, mut b) = duplex(1 << 16);
let (mut r, _w) = server(a, WsConfig::default(), None);
let mut raw = Vec::new();
let m = [1, 2, 3, 4];
let mut f1 = vec![0x01, 0x83];
f1.extend_from_slice(&m);
let mut p = b"Hel".to_vec();
frame::apply_mask(&mut p, m);
f1.extend_from_slice(&p);
raw.extend_from_slice(&f1);
let mut ping = Vec::new();
frame::encode(&mut ping, frame::OpCode::Ping, false, b"", Some(m));
raw.extend_from_slice(&ping);
let mut f2 = vec![0x80, 0x82];
f2.extend_from_slice(&m);
let mut p = b"lo".to_vec();
frame::apply_mask(&mut p, m);
f2.extend_from_slice(&p);
raw.extend_from_slice(&f2);
for byte in raw {
b.write_all(&[byte]).await.unwrap();
}
assert_eq!(r.recv().await.unwrap(), Some(Message::Ping(vec![])));
assert_eq!(r.recv().await.unwrap(), Some(Message::Text("Hello".into())));
b.write_all(&[0x81, 0x01, b'x']).await.unwrap();
assert!(matches!(r.recv().await, Err(WsError::Protocol(_))));
}
#[tokio::test]
async fn enforces_max_message_size() {
let (a, b) = duplex(1 << 20);
let (mut r, _w) = server(
a,
WsConfig {
max_message_size: 1000,
..Default::default()
},
None,
);
let (_cr, mut cw) = {
let (r, w) = tokio::io::split(b);
(
WsReader::new(r, Role::Client, WsConfig::default(), None, Vec::new()),
WsWriter::new(w, Role::Client, None),
)
};
cw.send_text(&"x".repeat(5000)).await.unwrap();
assert!(matches!(r.recv().await, Err(WsError::MessageTooBig)));
}
}
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
pub struct ReadmeDoctests;