use super::frame;
use super::material::Material;
use rustls::pki_types::ServerName;
use rustls::{ClientConfig, ClientConnection, StreamOwned};
use serde_json::Value;
use std::net::{IpAddr, TcpStream};
use std::sync::Arc;
use std::time::Duration;
const ASK_TIMEOUT: Duration = Duration::from_mins(2);
pub struct Seat {
config: Arc<ClientConfig>,
address: String,
name: ServerName<'static>,
}
impl Seat {
pub fn open(m: &Material) -> Result<Self, String> {
Ok(Self {
config: super::tls::client_config(m)?,
address: m.address.clone(),
name: server_name(&m.address)?,
})
}
pub fn address(&self) -> String {
self.address.clone()
}
pub fn answered(&self, request: &Value) -> crate::wire::link::Landed {
let stream = self.ask(request)?;
let last = stream
.last()
.ok_or_else(|| "the engine ended the stream without answering".to_owned())?;
crate::boundary::reply::decode(last).unwrap_or_else(Err)
}
pub fn ask(&self, request: &Value) -> Result<Vec<Value>, String> {
let mut tls = self.dial(request)?;
let mut stream = Vec::new();
loop {
match frame::read_value(&mut tls).map_err(|e| format!("receive: {e}"))? {
Some(chunk) => stream.push(chunk),
None => return Ok(stream),
}
}
}
pub fn followed(
&self,
request: &Value,
on_frame: &mut dyn FnMut(crate::wire::link::Landed) -> bool,
) -> Result<(), String> {
let mut tls = self.dial(request)?;
while let Some(chunk) = frame::read_value(&mut tls).map_err(|e| format!("receive: {e}"))? {
if !on_frame(crate::boundary::reply::decode(&chunk).unwrap_or_else(Err)) {
return Ok(());
}
}
Ok(())
}
fn dial(&self, request: &Value) -> Result<StreamOwned<ClientConnection, TcpStream>, String> {
let tcp = TcpStream::connect(&self.address)
.map_err(|e| format!("connect {}: {e}", self.address))?;
tcp.set_read_timeout(Some(ASK_TIMEOUT))
.map_err(|e| format!("connect {}: {e}", self.address))?;
let conn = ClientConnection::new(Arc::clone(&self.config), self.name.clone())
.map_err(|e| format!("tls {}: {e}", self.address))?;
let mut tls = StreamOwned::new(conn, tcp);
frame::write_value(&mut tls, request).map_err(|e| format!("send: {e}"))?;
Ok(tls)
}
}
fn server_name(address: &str) -> Result<ServerName<'static>, String> {
let host = address.rsplit_once(':').map_or(address, |(head, _)| head);
let host = host.trim_start_matches('[').trim_end_matches(']');
if let Ok(ip) = host.parse::<IpAddr>() {
return Ok(ServerName::IpAddress(ip.into()));
}
ServerName::try_from(host.to_owned()).map_err(|e| format!("{address}: not a server name: {e}"))
}
#[cfg(test)]
mod tests;