use std::io::{Read, Write};
use std::net::TcpStream;
use std::time::Duration;
use tungstenite::protocol::{WebSocket, WebSocketConfig};
use tungstenite::stream::MaybeTlsStream;
use tungstenite::{HandshakeError, Message};
pub(super) enum Link {
Tcp(TcpStream),
Ws {
socket: Box<WebSocket<MaybeTlsStream<TcpStream>>>,
message: Vec<u8>,
at: usize,
},
}
pub(super) fn open(address: &str, timeout: Duration, bound: usize) -> Result<Link, String> {
let websocket = crate::rail_link::is_websocket(address);
let authority = if websocket {
authority(address)?
} else {
address.to_owned()
};
let stream = TcpStream::connect(&authority).map_err(|e| format!("{authority}: {e}"))?;
stream
.set_read_timeout(Some(timeout))
.and_then(|()| stream.set_write_timeout(Some(timeout)))
.map_err(|e| e.to_string())?;
if !websocket {
return Ok(Link::Tcp(stream));
}
let config = WebSocketConfig::default()
.max_message_size(Some(bound))
.max_frame_size(Some(bound));
match tungstenite::client_tls_with_config(address, stream, Some(config), None) {
Ok((socket, _)) => Ok(Link::Ws {
socket: Box::new(socket),
message: Vec::new(),
at: 0,
}),
Err(HandshakeError::Failure(error)) => Err(format!(
"websocket handshake with {address} failed: {error}"
)),
Err(HandshakeError::Interrupted(_)) => Err(format!(
"websocket handshake with {address} was interrupted on a blocking socket"
)),
}
}
fn authority(address: &str) -> Result<String, String> {
let uri: tungstenite::http::Uri = address
.parse()
.map_err(|e| format!("invalid websocket address {address}: {e}"))?;
let host = uri
.host()
.ok_or_else(|| format!("websocket address {address} has no host"))?;
let scheme_port = if uri.scheme_str() == Some("wss") {
443
} else {
80
};
let port = uri.port_u16().unwrap_or(scheme_port);
Ok(format!("{host}:{port}"))
}
impl Link {
pub(super) fn send(&mut self, frame: &[u8]) -> Result<(), String> {
match self {
Self::Tcp(socket) => socket
.write_all(frame)
.and_then(|()| socket.flush())
.map_err(|e| e.to_string()),
Self::Ws { socket, .. } => socket
.send(Message::Binary(frame.to_vec().into()))
.map_err(|e| e.to_string()),
}
}
pub(super) fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), String> {
match self {
Self::Tcp(socket) => socket.read_exact(buf).map_err(|e| e.to_string()),
Self::Ws {
socket,
message,
at,
} => {
if *at == message.len() {
*message = next_binary(socket)?;
*at = 0;
}
let end = at
.checked_add(buf.len())
.filter(|end| *end <= message.len())
.ok_or("a websocket message ended inside a liminal frame")?;
buf.copy_from_slice(&message[*at..end]);
*at = end;
Ok(())
}
}
}
pub(super) fn frame_ended(&mut self) -> Result<(), String> {
match self {
Self::Tcp(_) => Ok(()),
Self::Ws { message, at, .. } if *at == message.len() => Ok(()),
Self::Ws { .. } => {
Err("one websocket message carried more than one liminal frame".to_owned())
}
}
}
}
fn next_binary(socket: &mut WebSocket<MaybeTlsStream<TcpStream>>) -> Result<Vec<u8>, String> {
loop {
match socket.read().map_err(|e| e.to_string())? {
Message::Binary(bytes) => return Ok(bytes.to_vec()),
Message::Ping(_) | Message::Pong(_) => {}
Message::Close(_) => return Err("the exchange closed the publish door".to_owned()),
Message::Text(_) | Message::Frame(_) => {
return Err("the publish door sent a message that is not a frame".to_owned());
}
}
}
}