Compare commits

...

2 Commits

8 changed files with 97 additions and 28 deletions

8
Cargo.lock generated
View File

@@ -985,18 +985,18 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.2"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.2"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
dependencies = [
"heck",
"proc-macro2",

View File

@@ -23,7 +23,7 @@ rustls = { version = "0.23", default-features = false, features = [
] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
strum = { version = "0.27", features = ["derive"] }
strum = { version = "0.28", features = ["derive"] }
thiserror = "2"
tokio = { version = "1", features = ["full"] }
tokio-rustls = { version = "0.26", default-features = false, features = [

View File

@@ -5,7 +5,6 @@ authors.workspace = true
edition.workspace = true
[dependencies]
claims.workspace = true
miette.workspace = true
rcgen.workspace = true
rustls.workspace = true
@@ -17,6 +16,7 @@ tokio.workspace = true
toml.workspace = true
[dev-dependencies]
claims.workspace = true
cargo-husky.workspace = true
[lints]

View File

@@ -121,4 +121,15 @@ mod tests {
));
assert_eq!(mode, KeyExchangeMode::X25519Mlkem768);
}
#[test]
fn key_exchange_mode_serde_case_insensitive() {
let mode_lower = assert_ok!(serde_json::from_str::<KeyExchangeMode>(r#""x25519""#));
assert_eq!(mode_lower, KeyExchangeMode::X25519);
let mode_mlkem_lower = assert_ok!(serde_json::from_str::<KeyExchangeMode>(
r#""x25519mlkem768""#
));
assert_eq!(mode_mlkem_lower, KeyExchangeMode::X25519Mlkem768);
}
}

View File

@@ -103,6 +103,7 @@ pub async fn read_payload<R: AsyncReadExt + Unpin>(
#[cfg(test)]
mod tests {
use super::*;
use claims::assert_ok;
use std::io::Cursor;
#[test]
@@ -115,18 +116,38 @@ mod tests {
assert_eq!(payload[299], 43);
}
#[test]
fn generate_payload_empty() {
let payload = generate_payload(0);
assert_eq!(payload.len(), 0);
}
#[test]
fn generate_payload_chunk_boundary() {
let payload = generate_payload(64 * 1024);
assert_eq!(payload.len(), 65_536);
assert_eq!(payload[255], 0xFF);
assert_eq!(payload[256], 0x00);
assert_eq!(payload[65_535], 255);
}
#[test]
fn generate_payload_at_max_size() {
let payload = generate_payload(MAX_PAYLOAD_SIZE);
assert_eq!(payload.len(), 16_777_216);
assert_eq!(payload[255], 0xFF);
assert_eq!(payload[256], 0x00);
assert_eq!(payload[MAX_PAYLOAD_SIZE as usize - 1], 255);
}
#[tokio::test]
async fn roundtrip_request() {
let mut buf = Vec::new();
write_request(&mut buf, 12345)
.await
.expect("write should succeed");
assert_ok!(write_request(&mut buf, 12_345).await);
assert_eq!(buf.len(), REQUEST_SIZE);
let mut cursor = Cursor::new(buf);
let size = read_request(&mut cursor)
.await
.expect("read should succeed");
let size = assert_ok!(read_request(&mut cursor).await);
assert_eq!(size, 12345);
}
@@ -137,4 +158,22 @@ mod tests {
let result = read_request(&mut cursor).await;
assert!(result.is_err());
}
#[tokio::test]
async fn read_payload_exact_size() {
let payload = generate_payload(500);
let mut cursor = Cursor::new(payload.clone());
let read = assert_ok!(read_payload(&mut cursor, 500).await);
assert_eq!(read, 500);
}
#[tokio::test]
async fn write_payload_chunk_boundary() {
let size = 64 * 1024;
let mut buf = Vec::new();
assert_ok!(write_payload(&mut buf, size as u64).await);
assert_eq!(buf.len(), size);
assert_eq!(buf[0], 0x00);
assert_eq!(buf[size - 1], 0xFF);
}
}

View File

@@ -17,6 +17,8 @@ toml.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
uuid.workspace = true
[dev-dependencies]
claims.workspace = true
[lints]

View File

@@ -11,7 +11,11 @@ use common::{
BenchRecord, KeyExchangeMode,
protocol::{read_payload, write_request},
};
use miette::miette;
use miette::{Context, IntoDiagnostic};
use runner::{
args::Args,
config::{load_from_cli, load_from_file},
};
use rustls::{
ClientConfig, DigitallySignedStruct, SignatureScheme,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
@@ -35,9 +39,6 @@ use tracing::info;
use tracing_subscriber::EnvFilter;
use uuid::Uuid;
use runner::args::Args;
use runner::config::{load_from_cli, load_from_file};
/// Result of a single benchmark iteration.
struct IterationResult {
handshake_ns: u64,
@@ -105,7 +106,8 @@ fn build_tls_config(mode: KeyExchangeMode) -> miette::Result<Arc<ClientConfig>>
let config = ClientConfig::builder_with_provider(Arc::new(provider))
.with_protocol_versions(&[&TLS13])
.map_err(|e| miette!("failed to set TLS versions: {e}"))?
.into_diagnostic()
.context("failed to set TLS versions")?
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoVerifier))
.with_no_client_auth();
@@ -125,22 +127,26 @@ async fn run_iteration(
let stream = TcpStream::connect(server)
.await
.map_err(|e| miette!("TCP connection failed: {e}"))?;
.into_diagnostic()
.context("TCP connection failed")?;
let mut tls_stream = tls_connector
.connect(server_name.clone(), stream)
.await
.map_err(|e| miette!("TLS handshake failed: {e}"))?;
.into_diagnostic()
.context("TLS handshake failed")?;
let handshake_ns = start.elapsed().as_nanos() as u64;
write_request(&mut tls_stream, u64::from(payload_bytes))
.await
.map_err(|e| miette!("write request failed: {e}"))?;
.into_diagnostic()
.context("write request failed")?;
read_payload(&mut tls_stream, u64::from(payload_bytes))
.await
.map_err(|e| miette!("read payload failed: {e}"))?;
.into_diagnostic()
.context("read payload failed")?;
let ttlb_ns = start.elapsed().as_nanos() as u64;
@@ -175,10 +181,12 @@ async fn run_benchmark(
server_name.clone(),
TcpStream::connect(server)
.await
.map_err(|e| miette!("failed to connect to server {}: {e}", server))?,
.into_diagnostic()
.context(format!("failed to connect to server {server}"))?,
)
.await
.map_err(|e| miette!("TLS handshake failed: {e}"))?;
.into_diagnostic()
.context("TLS handshake failed")?;
let cipher = test_conn.get_ref().1.negotiated_cipher_suite();
info!(cipher = ?cipher, "TLS handshake complete");
@@ -193,7 +201,8 @@ async fn run_benchmark(
write_results(&mut output, tasks).await?;
output
.flush()
.map_err(|e| miette!("failed to flush output: {e}"))?;
.into_diagnostic()
.context("failed to flush output")?;
}
info!("benchmark complete");
@@ -261,12 +270,17 @@ fn spawn_single_iteration(
})
}
#[allow(clippy::future_not_send)] // dyn Write is not Send
async fn write_results(output: &mut dyn Write, tasks: Vec<ReturnHandle>) -> miette::Result<()> {
// #[allow(clippy::future_not_send)] // dyn Write is not Send
async fn write_results<W: Write + Send>(
output: &mut W,
tasks: Vec<ReturnHandle>,
) -> miette::Result<()> {
for task in tasks {
let (_result, record) = task.await.expect("task should not panic");
if let Some(record) = record {
writeln!(output, "{record}").map_err(|e| miette!("failed to write record: {e}"))?;
writeln!(output, "{record}")
.into_diagnostic()
.context("failed to write record")?;
}
}
Ok(())
@@ -304,7 +318,8 @@ async fn main() -> miette::Result<()> {
let tls_connector = TlsConnector::from(tls_config);
let server_name = ServerName::try_from("localhost".to_string())
.map_err(|e| miette!("invalid server name: {e}"))?;
.into_diagnostic()
.context("invalid server name")?;
for benchmark in &config.benchmarks {
info!(

View File

@@ -16,6 +16,8 @@ tokio.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
uuid.workspace = true
[dev-dependencies]
claims.workspace = true
[lints]