use std::net::{IpAddr, TcpStream};
use std::sync::Arc;
use std::time::Duration;
use rustls::pki_types::ServerName;
use rustls::{ClientConfig, ClientConnection, StreamOwned};
use serde_json::Value;
pub mod entries;
mod failure;
pub mod frame;
pub mod hello;
mod ladder;
pub mod leaf;
pub mod material;
pub mod tls;
use crate::rendezvous::call::{Roving, Tuning};
pub use failure::Failure;
use material::Material;
const READ_TIMEOUT: Duration = Duration::from_mins(2);
type Tls = StreamOwned<ClientConnection, TcpStream>;
#[derive(Debug)]
pub struct Channel {
config: Arc<ClientConfig>,
address: String,
name: ServerName<'static>,
client: String,
roving: Option<Roving>,
held: Option<Tls>,
quiet: Duration,
}
impl Channel {
pub fn open(m: &Material) -> Result<Self, String> {
let client = leaf::foot(&m.chain)?;
Ok(Self {
config: tls::client_config(m)?,
address: m.address.clone(),
name: server_name(&m.address)?,
client,
roving: m
.rendezvous
.clone()
.map(|pairing| Roving::new(pairing, Tuning::default())),
held: None,
quiet: READ_TIMEOUT,
})
}
#[cfg(test)]
pub(crate) fn tune(&mut self, quiet: Duration, tuning: Tuning) {
self.quiet = quiet;
if let Some(roving) = self.roving.as_mut() {
roving.tuning = tuning;
}
}
pub fn client(&self) -> String {
self.client.clone()
}
pub fn address(&self) -> String {
self.address.clone()
}
pub fn ask(&mut self, request: &Value) -> Result<Vec<Value>, Failure> {
let (mut tls, punched) = match self.held.take() {
Some(mut tls) => {
frame::write_value(&mut tls, request)
.map_err(|e| Failure::Wire(self.failed("send", &e)))?;
(tls, true)
}
None => self.dial(request)?,
};
let answer = self.answer(&mut tls)?;
if punched {
self.held = Some(tls);
}
Ok(answer)
}
fn answer(&self, tls: &mut Tls) -> Result<Vec<Value>, Failure> {
let mut stream = Vec::new();
loop {
match frame::read_value(tls).map_err(|e| Failure::Wire(self.failed("receive", &e)))? {
Some(chunk) if is_ping(&chunk) => {}
Some(chunk) => stream.push(chunk),
None => return Ok(stream),
}
}
}
fn failed(&self, leg: &str, e: &std::io::Error) -> String {
format!(
"{leg} {}: the channel to the engine failed. What the {leg} \
reported: {e}",
self.address
)
}
fn dial(&mut self, request: &Value) -> Result<(Tls, bool), Failure> {
let (tcp, punched) =
ladder::climb(&self.address, self.roving.as_mut()).map_err(Failure::Wire)?;
tcp.set_read_timeout(Some(self.quiet))
.map_err(|e| Failure::Wire(format!("connect {}: {e}", self.address)))?;
let conn = ClientConnection::new(Arc::clone(&self.config), self.name.clone())
.map_err(|e| Failure::Wire(format!("tls {}: {e}", self.address)))?;
let mut tls = StreamOwned::new(conn, tcp);
hello::state(&mut tls).map_err(|e| Failure::Wire(self.failed("send", &e)))?;
frame::write_value(&mut tls, request)
.map_err(|e| Failure::Wire(self.failed("send", &e)))?;
hello::confirm(&mut tls)?;
Ok((tls, punched))
}
}
fn is_ping(frame: &Value) -> bool {
frame.get("ping").and_then(Value::as_bool) == Some(true)
}
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;