mod common;
use std::sync::Arc;
use anyhow::Context;
use axum::{Router, routing::post};
use pocketscion::util::topologies::{IA132, IA212, UnderlayType, minimal::minimal_topology};
use scion_h3_axum::ScionH3AxumServer;
use scion_quic::{
h3::client::Http3Client, quic::config::QuicConfig, socket::GenericScionUdpSocket,
};
use scion_stack::{ScionStack, UdpScionSocket};
use tempfile::NamedTempFile;
use tokio_util::sync::CancellationToken;
const SERVER_NAME: &str = "localhost";
const ECHO_PAYLOAD: &str = "round-trip over HTTP/3 over SCION";
#[tokio::main]
async fn main() -> anyhow::Result<()> {
run().await
}
async fn run() -> anyhow::Result<()> {
scion_sdk_utils::rustls::select_ring_crypto_provider();
let ps = minimal_topology(UnderlayType::Snap).await;
let (cert_file, key_file) = server_certs()?;
let (server_task, server_addr, shutdown) = {
let server_stack: ScionStack = common::build_stack(&ps, IA212).await?;
let udp_socket: UdpScionSocket = server_stack.bind(None).await?;
let server_socket: Arc<dyn GenericScionUdpSocket> = Arc::new(udp_socket);
let server_addr = server_socket.local_addr();
let mut server_config = QuicConfig::builder()
.build()
.to_quiche_config()
.context("building quiche server config")?;
server_config.load_cert_chain_from_pem_file(
cert_file.path().to_str().context("cert path not UTF-8")?,
)?;
server_config
.load_priv_key_from_pem_file(key_file.path().to_str().context("key path not UTF-8")?)?;
let shutdown = CancellationToken::new();
let server_task = tokio::spawn({
let shutdown = shutdown.clone();
async move {
let app = Router::new()
.route("/echo", post(|body: String| async move { body }));
ScionH3AxumServer::serve_with_graceful_shutdown(
server_socket,
app,
server_config,
shutdown,
)
.await
}
});
println!("HTTP/3 server listening on {server_addr}");
(server_task, server_addr, shutdown)
};
let client = {
let client_stack: ScionStack = common::build_stack(&ps, IA132).await?;
let client_udp_socket: UdpScionSocket = client_stack.bind(None).await?;
let client_socket: Arc<dyn GenericScionUdpSocket> = Arc::new(client_udp_socket);
let client_config = QuicConfig::builder()
.ca_certs_file(cert_file.path().to_str().context("cert path not UTF-8")?)
.build();
Http3Client::with_config(
server_addr,
client_socket,
Some(SERVER_NAME.to_string()),
client_config,
)
};
{
let request = http::Request::builder()
.method(http::Method::POST)
.uri(format!("https://{SERVER_NAME}/echo"))
.header(http::header::CONTENT_TYPE, "text/plain; charset=utf-8")
.body(ECHO_PAYLOAD.to_string())?;
let response = client.request(request).await?;
if !response.status().is_success() {
anyhow::bail!("request failed with status {}", response.status());
}
let max_response_size = Some(1024);
let (response, _trailers) = response.into_body().text(max_response_size).await?;
println!("Response body: {response:?}");
}
shutdown.cancel();
server_task
.await
.expect("server task should not panic")
.expect("server task should not return an error");
Ok(())
}
fn server_certs() -> anyhow::Result<(NamedTempFile, NamedTempFile)> {
use std::io::Write;
let cert = rcgen::generate_simple_self_signed(vec![SERVER_NAME.to_string()])?;
let mut cert_file = NamedTempFile::new()?;
cert_file.write_all(cert.cert.pem().as_bytes())?;
let mut key_file = NamedTempFile::new()?;
key_file.write_all(cert.signing_key.serialize_pem().as_bytes())?;
Ok((cert_file, key_file))
}
#[cfg(test)]
mod tests {
use test_log::test;
#[test(tokio::test)]
#[ntest::timeout(60_000)]
async fn axum_h3_roundtrip() {
super::run().await.expect("axum_h3 example should succeed");
}
}