use std::net::{IpAddr, TcpStream};
use std::path::PathBuf;
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;
pub mod frame;
pub mod hello;
pub mod leaf;
pub mod material;
pub mod reach;
pub mod tls;
use material::Material;
pub use reach::Reach;
const READ_TIMEOUT: Duration = Duration::from_mins(2);
#[derive(Debug)]
pub struct Channel {
config: Arc<ClientConfig>,
address: String,
name: ServerName<'static>,
anchors: PathBuf,
chain: PathBuf,
}
impl Channel {
pub fn open(m: &Material) -> Result<Self, String> {
Ok(Self {
config: tls::client_config(m)?,
address: m.address.clone(),
name: server_name(&m.address)?,
anchors: m.anchors.clone(),
chain: m.chain.clone(),
})
}
pub fn address(&self) -> String {
self.address.clone()
}
pub fn ask(&self, request: &Value) -> Result<Vec<Value>, Reach> {
let mut stream = Vec::new();
self.follow(request, &mut |frame| {
stream.push(frame);
true
})?;
Ok(stream)
}
pub fn follow(
&self,
request: &Value,
on_frame: &mut dyn FnMut(Value) -> bool,
) -> Result<(), Reach> {
let mut tls = self.dial(request)?;
while let Some(chunk) =
frame::read_value(&mut tls).map_err(|e| Reach::Unanswered(format!("receive: {e}")))?
{
if !on_frame(chunk) {
return Ok(());
}
}
Ok(())
}
fn dial(&self, request: &Value) -> Result<StreamOwned<ClientConnection, TcpStream>, Reach> {
let tcp = TcpStream::connect(&self.address)
.and_then(|tcp| tcp.set_read_timeout(Some(READ_TIMEOUT)).map(|()| tcp))
.map_err(|e| Reach::Unsent(format!("connect {}: {e}", self.address)))?;
let conn = ClientConnection::new(Arc::clone(&self.config), self.name.clone())
.map_err(|e| Reach::Unsent(format!("tls {}: {e}", self.address)))?;
let mut tls = StreamOwned::new(conn, tcp);
hello::state(&mut tls).map_err(|e| Reach::Unsent(self.wrote(&e)))?;
frame::write_value(&mut tls, request).map_err(|e| Reach::Unsent(self.wrote(&e)))?;
hello::confirm(&mut tls)?;
Ok(tls)
}
fn wrote(&self, e: &std::io::Error) -> String {
let Some(rustls::Error::InvalidCertificate(fault)) = e
.get_ref()
.and_then(|inner| inner.downcast_ref::<rustls::Error>())
else {
return format!("send: {e}");
};
format!(
"the handshake with {} did not verify ({fault:?}): {} must hold the anchors of THAT engine's CA, and {} must be a leaf that CA issued. Both are carried here by hand; the seat mints nothing",
self.address,
self.anchors.display(),
self.chain.display()
)
}
}
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;