use crate::error::{self, Result};
use native_tls::{TlsConnector, TlsStream};
use std::marker::{self, PhantomData};
use std::net::TcpStream;
use tungstenite::{
Message, WebSocket,
client::client,
handshake::client::{Request, generate_key},
};
use url::Url;
static BASE_URL: &str = "wss://127.0.0.1:64443";
pub struct Connected;
pub struct Disconnected;
pub struct Client<State> {
pub socket: WebSocket<TlsStream<TcpStream>>,
_state: marker::PhantomData<State>,
}
impl Client<Disconnected> {
pub fn connect<T>(url: Option<T>) -> Result<Client<Connected>>
where
T: ToString,
{
let url = match url {
Some(v) => v.to_string(),
None => BASE_URL.to_string(),
};
let url = Url::parse(&format!("{url}/service/cryptapi"))?;
let tls = TlsConnector::builder()
.danger_accept_invalid_certs(true)
.build()?;
let remote_addr = match (url.host(), url.port()) {
(Some(host), Some(port)) => Some(format!("{host}:{port}")),
_ => None,
}
.ok_or(())
.unwrap();
let req = Request::builder()
.method("GET")
.header("Host", "localhost")
.header("Connection", "Upgrade")
.header("Upgrade", "websocket")
.header("Origin", "https://localhost")
.header("Sec-WebSocket-Version", "13")
.header("Sec-WebSocket-Key", generate_key())
.uri(url.to_string())
.body(())
.unwrap();
let tcp_stream = std::net::TcpStream::connect(remote_addr.clone())?;
let tls_stream = tls.connect(remote_addr.as_str(), tcp_stream)?;
let (socket, _) = client(req, tls_stream)?;
let connection = Client {
socket,
_state: PhantomData,
};
Ok(connection)
}
}
impl Client<Connected> {
pub fn send_and_wait(&mut self, message: Message) -> Result<Message, error::EIMZOError> {
self.socket.send(message)?;
loop {
let msg = self.socket.read()?;
match msg {
Message::Ping(payload) => {
self.socket.send(Message::Pong(payload))?;
}
Message::Pong(_) => continue,
Message::Text(msg) => return Ok(Message::Text(msg)),
Message::Close(msg) => {
return Err(error::EIMZOError::WebsocketClosed(msg));
}
_ => {
return Err(error::EIMZOError::Unknown);
}
}
}
}
}