#![cfg(feature = "dangerous-test-tls")]
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use boardwalk::{Boardwalk, Device, DeviceConfig, DeviceError, TransitionInput};
use futures::future::BoxFuture;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use serde_json::Value as Json;
use tokio_rustls::TlsAcceptor;
#[derive(Default)]
struct Led {
on: bool,
}
impl Device for Led {
fn config(&self, cfg: &mut DeviceConfig) {
cfg.type_("led")
.name("LED")
.state(self.state())
.when("off", &["turn-on"])
.when("on", &["turn-off"]);
}
fn state(&self) -> &str {
if self.on { "on" } else { "off" }
}
fn transition<'a>(
&'a mut self,
_name: &'a str,
_input: TransitionInput,
) -> BoxFuture<'a, Result<(), DeviceError>> {
Box::pin(async { Ok(()) })
}
}
async fn serve_tls(router: axum::Router) -> SocketAddr {
let _ = rustls::crypto::CryptoProvider::install_default(
rustls::crypto::aws_lc_rs::default_provider(),
);
let cert =
rcgen::generate_simple_self_signed(vec!["localhost".to_string(), "127.0.0.1".to_string()])
.unwrap();
let cert_der: CertificateDer<'static> = cert.cert.der().clone();
let key_der: PrivateKeyDer<'static> =
PrivateKeyDer::try_from(cert.key_pair.serialize_der()).unwrap();
let server_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert_der], key_der)
.unwrap();
let acceptor = TlsAcceptor::from(Arc::new(server_config));
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
loop {
let (tcp, _) = match listener.accept().await {
Ok(v) => v,
Err(_) => return,
};
let acceptor = acceptor.clone();
let router = router.clone();
tokio::spawn(async move {
let Ok(tls) = acceptor.accept(tcp).await else {
return;
};
let io = hyper_util::rt::TokioIo::new(tls);
let service = router.into_service::<hyper::body::Incoming>();
let svc = hyper_util::service::TowerToHyperService::new(service);
let _ = hyper::server::conn::http1::Builder::new()
.serve_connection(io, svc)
.with_upgrades()
.await;
});
}
});
addr
}
#[tokio::test]
async fn hub_links_to_cloud_over_tls() {
let cloud = Boardwalk::new().name("cloud").build().unwrap();
let cloud_acceptors = cloud.acceptors.clone();
let tls_addr = serve_tls(cloud.router).await;
let hub = Boardwalk::new()
.name("hub")
.use_device(Led::default())
.link(format!("https://localhost:{}/", tls_addr.port()))
.build()
.unwrap();
let hub_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
tokio::spawn(async move {
axum::serve(hub_listener, hub.router).await.unwrap();
});
assert!(
cloud_acceptors
.wait_for_first(Duration::from_secs(10))
.await,
"cloud should have received a confirmed peer over TLS within 10s"
);
let cloud_url = format!("https://localhost:{}/", tls_addr.port());
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let root: Json = client
.get(&cloud_url)
.send()
.await
.unwrap()
.json()
.await
.unwrap();
let has_peer = root["links"]
.as_array()
.unwrap()
.iter()
.any(|l| l["title"] == "hub");
assert!(has_peer);
}