use std::collections::HashSet;
use std::fmt::Debug;
use std::io::Result as IOResult;
use std::path::PathBuf;
use crate::codec::{FrameCodec, FrameDecoder, FrameEncoder};
use crate::frame::{Frame, OpCode};
use bytes::BytesMut;
use futures::SinkExt;
use futures::StreamExt;
use protocol::handle_handshake;
use protocol::perform_handshake;
use stream::WsStream;
use tokio::io::{ReadHalf, WriteHalf};
use tokio::net::TcpStream;
pub mod config;
pub mod errors;
pub mod frame;
pub mod protocol;
pub mod proxy;
pub mod stream;
pub mod codec;
use errors::WsError;
use tokio_util::codec::{Framed, FramedRead, FramedWrite};
use crate::protocol::wrap_tls;
use crate::protocol::Mode;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectionState {
Created,
HandShaking,
Running,
Closing,
Closed,
}
pub struct ConnBuilder {
uri: String,
proxy_uri: Option<String>,
protocols: Vec<String>,
extensions: Vec<String>,
certs: HashSet<PathBuf>,
}
impl ConnBuilder {
pub fn new(uri: &str) -> Self {
Self {
uri: uri.to_string(),
proxy_uri: None,
protocols: Vec::new(),
extensions: Vec::new(),
certs: HashSet::new(),
}
}
pub fn proxy(self, uri: &str) -> Self {
Self {
proxy_uri: Some(uri.to_string()),
..self
}
}
pub fn protocol(mut self, protocol: String) -> Self {
self.protocols.push(protocol);
self
}
pub fn protocols(self, protocols: Vec<String>) -> Self {
Self { protocols, ..self }
}
pub fn extension(mut self, extension: String) -> Self {
self.extensions.push(extension);
self
}
pub fn extensions(self, extensions: Vec<String>) -> Self {
Self { extensions, ..self }
}
pub fn cert(mut self, cert: PathBuf) -> Self {
self.certs.insert(cert);
self
}
pub fn certs(self, certs: HashSet<PathBuf>) -> Self {
Self { certs, ..self }
}
pub async fn build(&self) -> Result<Connection, WsError> {
let Self {
uri,
proxy_uri,
protocols,
extensions,
certs,
} = self;
let uri = uri
.parse::<http::Uri>()
.map_err(|e| WsError::InvalidUri(format!("{} {}", uri, e.to_string())))?;
let mode = if let Some(schema) = uri.scheme_str() {
match schema.to_ascii_lowercase().as_str() {
"ws" => Ok(Mode::WS),
"wss" => Ok(Mode::WSS),
_ => Err(WsError::InvalidUri(format!("invalid schema {}", schema))),
}
} else {
Err(WsError::InvalidUri("missing ws or wss schema".to_string()))
}?;
if mode == Mode::WS && !certs.is_empty() {
log::warn!("setting tls cert has no effect on insecure ws")
}
let ws_proxy: Option<proxy::Proxy> = match proxy_uri {
Some(uri) => Some(uri.parse()?),
None => None,
};
let host = uri
.host()
.ok_or_else(|| WsError::InvalidUri(format!("can not find host {}", self.uri)))?;
let port = match uri.port_u16() {
Some(port) => port,
None => mode.default_port(),
};
let stream = match &ws_proxy {
Some(proxy_conf) => proxy_conf.connect((host, port)).await?,
None => TcpStream::connect((host, port)).await.map_err(|e| {
WsError::ConnectionFailed(format!(
"failed to create tcp connection {}",
e.to_string()
))
})?,
};
log::debug!("tcp connection established");
let stream = match mode {
Mode::WS => WsStream::Plain(stream),
Mode::WSS => {
let tls_stream = wrap_tls(stream, host, &self.certs).await?;
WsStream::Tls(tls_stream)
}
};
Ok(Connection {
uri,
mode,
framed: Framed::new(stream, FrameCodec::default()),
state: ConnectionState::Created,
certs: certs.clone(),
proxy: ws_proxy,
protocols: protocols.to_owned(),
extensions: extensions.to_owned(),
})
}
}
#[derive(Debug)]
pub struct Connection {
uri: http::Uri,
mode: Mode,
framed: Framed<stream::WsStream, FrameCodec>,
certs: HashSet<PathBuf>,
state: ConnectionState,
proxy: Option<proxy::Proxy>,
protocols: Vec<String>,
extensions: Vec<String>,
}
impl Connection {
pub async fn handshake(&mut self) -> Result<protocol::HandshakeResponse, WsError> {
self.state = ConnectionState::HandShaking;
let protocols = self.protocols.join(" ");
let extensions = self.extensions.join(" ");
let resp = perform_handshake(
self.framed.get_mut(),
&self.mode,
&self.uri,
protocols,
extensions,
13,
)
.await?;
self.state = ConnectionState::Running;
Ok(resp)
}
pub async fn write(&mut self, item: Frame) -> IOResult<()> {
if self.state != ConnectionState::Running {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"connection closed",
));
}
self.framed.send(item).await
}
pub async fn read(&mut self) -> Option<IOResult<Frame>> {
match self.framed.next().await {
Some(maybe_frame) => {
let msg = match maybe_frame {
Ok(frame) => Ok(frame),
Err(err) => {
let ret = match self.close(1002, err.to_string()).await {
Ok(_) => Err(err),
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::ConnectionAborted,
"send close failed",
)),
};
self.state = ConnectionState::Closed;
ret
}
};
Some(msg)
}
None => None,
}
}
pub async fn close(&mut self, code: u16, reason: String) -> IOResult<()> {
self.state = ConnectionState::Closing;
let mut payload = BytesMut::with_capacity(2 + reason.as_bytes().len());
payload.extend_from_slice(&code.to_be_bytes());
payload.extend_from_slice(reason.as_bytes());
let close = Frame::new_with_payload(OpCode::Close, &payload);
self.framed.send(close).await
}
pub fn split(
self,
) -> (
FramedRead<ReadHalf<WsStream>, FrameDecoder>,
FramedWrite<WriteHalf<WsStream>, FrameEncoder>,
) {
if self.state != ConnectionState::Running {
panic!("should split after connection is running");
}
let Self { framed, .. } = self;
let parts = framed.into_parts();
let (read_stream, write_stream) = tokio::io::split(parts.io);
let frame_r = FramedRead::new(read_stream, parts.codec.decoder);
let frame_w = FramedWrite::new(write_stream, parts.codec.encoder);
(frame_r, frame_w)
}
}
#[derive(Debug)]
pub struct Server {
pub framed: Framed<stream::WsStream, FrameCodec>,
pub state: ConnectionState,
pub protocols: Vec<String>,
pub extensions: Vec<String>,
}
impl Server {
pub fn from_stream(stream: TcpStream) -> Self {
Self {
state: ConnectionState::Created,
framed: Framed::new(WsStream::Plain(stream), FrameCodec::default()),
protocols: vec![],
extensions: vec![],
}
}
pub async fn handle_handshake(&mut self) -> Result<(), WsError> {
let stream = self.framed.get_mut();
handle_handshake(stream).await?;
self.state = ConnectionState::Running;
Ok(())
}
pub async fn read(&mut self) -> Option<IOResult<Frame>> {
match self.framed.next().await {
Some(maybe_frame) => {
let msg = match maybe_frame {
Ok(frame) => Ok(frame),
Err(err) => {
let ret = match self.close(1002, err.to_string()).await {
Ok(_) => Err(err),
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::ConnectionAborted,
"send close failed",
)),
};
self.state = ConnectionState::Closed;
ret
}
};
Some(msg)
}
None => None,
}
}
pub async fn write(&mut self, item: Frame) -> IOResult<()> {
if self.state != ConnectionState::Running {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"connection closed",
));
}
self.framed.send(item).await
}
pub async fn close(&mut self, code: u16, reason: String) -> IOResult<()> {
self.state = ConnectionState::Closing;
let mut payload = BytesMut::with_capacity(2 + reason.as_bytes().len());
payload.extend_from_slice(&code.to_be_bytes());
payload.extend_from_slice(reason.as_bytes());
let close = Frame::new_with_payload(OpCode::Close, &payload);
self.framed.send(close).await
}
pub fn split(
self,
) -> (
FramedRead<ReadHalf<WsStream>, FrameDecoder>,
FramedWrite<WriteHalf<WsStream>, FrameEncoder>,
) {
if self.state != ConnectionState::Running {
panic!("should split after connection is running");
}
let Self { framed, .. } = self;
let parts = framed.into_parts();
let (read_stream, write_stream) = tokio::io::split(parts.io);
let frame_r = FramedRead::new(read_stream, parts.codec.decoder);
let frame_w = FramedWrite::new(write_stream, parts.codec.encoder);
(frame_r, frame_w)
}
}