use std::net::TcpStream;
use std::path::Path;
use std::sync::{Arc, Mutex, PoisonError};
use std::time::Duration;
use rustls::{ServerConfig, ServerConnection, StreamOwned};
use serde_json::{Value, json};
use crate::channel::frame;
use crate::rendezvous::punch::Punch;
const PATIENCE: Duration = Duration::from_secs(10);
pub(crate) enum Turn {
Answer(Vec<Value>),
Vanish,
Silence,
}
pub(crate) struct Punched {
port: u16,
seen: Arc<Mutex<Vec<Value>>>,
}
impl Punched {
pub(crate) fn start(dir: &Path, script: Vec<Turn>) -> Punched {
let config = super::server_config(dir);
let punch = Punch::bind(0).expect("a punch port");
let port = punch.port();
let seen = Arc::new(Mutex::new(Vec::new()));
let recorded = Arc::clone(&seen);
std::thread::spawn(move || serve(&punch, &config, script, &recorded));
Punched { port, seen }
}
pub(crate) fn port(&self) -> u16 {
self.port
}
pub(crate) fn heard(&self) -> Vec<Value> {
self.seen
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone()
}
}
fn serve(
punch: &Punch,
config: &Arc<ServerConfig>,
script: Vec<Turn>,
seen: &Arc<Mutex<Vec<Value>>>,
) {
let mut turns = script.into_iter();
while let Some(tcp) = punch.punch(vec![], PATIENCE) {
let _ = tcp.set_read_timeout(Some(PATIENCE));
let mut tls = StreamOwned::new(
ServerConnection::new(Arc::clone(config)).expect("a server connection"),
tcp,
);
let _ = frame::write_value(
&mut tls,
&json!({ "protocol": crate::corpus::PROTOCOL, "edition": crate::corpus::ledger::FLOOR }),
);
if !record(&mut tls, seen) {
continue;
}
while record(&mut tls, seen) {
match turns.next() {
Some(Turn::Answer(frames)) => {
for value in &frames {
let _ = frame::write_value(&mut tls, value);
}
let _ = frame::write_end(&mut tls);
}
Some(Turn::Vanish) => break,
Some(Turn::Silence) => {
let _ = frame::read_value(&mut tls);
break;
}
None => return,
}
}
}
}
fn record(
tls: &mut StreamOwned<ServerConnection, TcpStream>,
seen: &Arc<Mutex<Vec<Value>>>,
) -> bool {
let Ok(Some(said)) = frame::read_value(tls) else {
return false;
};
seen.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(said);
true
}