use std::net::IpAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use rustls::ClientConfig;
use rustls::pki_types::ServerName;
use serde_json::Value;
pub mod clock;
mod dial;
pub mod edition;
pub mod entries;
pub mod frame;
pub mod hello;
pub mod ladder;
pub mod leaf;
pub(crate) mod line;
pub mod material;
pub mod reach;
pub mod rendezvous;
pub mod say;
pub mod tls;
use material::Material;
pub use reach::Reach;
#[derive(Debug)]
pub struct Channel {
config: Arc<ClientConfig>,
address: String,
name: ServerName<'static>,
pairing: Option<rendezvous::Pairing>,
key: String,
clock: Arc<dyn clock::Clock>,
roving: ladder::Roving,
anchors: PathBuf,
chain: PathBuf,
spelled: AtomicU32,
}
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)?,
pairing: m.pairing,
key: m
.anchors
.parent()
.map(|dir| dir.display().to_string())
.unwrap_or_default(),
clock: clock::system(),
roving: ladder::Roving::default(),
anchors: m.anchors.clone(),
chain: m.chain.clone(),
spelled: AtomicU32::new(edition::FLOOR),
})
}
#[cfg(test)]
pub(crate) fn tuned(self, roving: ladder::Roving, clock: Arc<dyn clock::Clock>) -> Self {
Self {
clock,
roving,
..self
}
}
pub fn spells(&self, shape: &str, key: &str) -> bool {
edition::spells(self.spelled.load(Ordering::Relaxed), shape, key)
}
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 line = self.dial(request)?;
let mut pings = 0;
while let Some(chunk) = line::read(&mut line.tls, &mut pings)
.map_err(|e| Reach::Unanswered(format!("receive: {e}")))?
{
if !on_frame(chunk) {
return Ok(());
}
}
say::tell(
self,
"pings",
(pings > 0).then(say::pinged).into_iter().collect(),
);
line.give_back(&self.key, self.clock.now());
Ok(())
}
}
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;