use crate::cx::test_cx;
use crate::net::quic::{config::QuicConfig, endpoint::QuicEndpoint};
use crate::tls::RootCertStore;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RequirementLevel {
Must,
Should,
May,
}
impl RequirementLevel {
pub fn as_str(&self) -> &'static str {
match self {
RequirementLevel::Must => "MUST",
RequirementLevel::Should => "SHOULD",
RequirementLevel::May => "MAY",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConformanceResult {
Pass,
Fail { reason: String },
Skipped { reason: String },
ExpectedFailure { reason: String }, }
#[derive(Debug, Clone)]
pub struct ConformanceCase {
pub id: &'static str,
pub section: &'static str,
pub level: RequirementLevel,
pub description: &'static str,
pub test_fn: fn() -> ConformanceResult,
}
pub const RFC9000_HANDSHAKE_CASES: &[ConformanceCase] = &[
ConformanceCase {
id: "RFC9000-7.1",
section: "7",
level: RequirementLevel::Must,
description: "QUIC MUST use TLS 1.3 or later for cryptographic handshake",
test_fn: test_tls_version_requirement,
},
ConformanceCase {
id: "RFC9000-7.2",
section: "7",
level: RequirementLevel::Must,
description: "QUIC endpoints MUST support ALPN and include at least one protocol",
test_fn: test_alpn_support_requirement,
},
ConformanceCase {
id: "RFC9000-7.3",
section: "7",
level: RequirementLevel::Must,
description: "Client MUST verify server certificate unless explicitly disabled",
test_fn: test_certificate_verification_requirement,
},
ConformanceCase {
id: "RFC9000-7.4",
section: "7",
level: RequirementLevel::Should,
description: "Endpoints SHOULD support certificate-based client authentication",
test_fn: test_client_certificate_support,
},
ConformanceCase {
id: "RFC9000-4.1",
section: "4",
level: RequirementLevel::Must,
description: "Connection IDs MUST be generated uniquely per connection",
test_fn: test_connection_id_uniqueness,
},
ConformanceCase {
id: "RFC9000-4.2",
section: "4",
level: RequirementLevel::Must,
description: "Handshake MUST complete before application data transmission",
test_fn: test_handshake_completion_ordering,
},
ConformanceCase {
id: "RFC9000-6.1",
section: "6",
level: RequirementLevel::Must,
description: "Endpoints MUST handle version negotiation correctly",
test_fn: test_version_negotiation_support,
},
ConformanceCase {
id: "RFC9000-18.1",
section: "18",
level: RequirementLevel::Must,
description: "Endpoints MUST exchange transport parameters during handshake",
test_fn: test_transport_parameter_exchange,
},
ConformanceCase {
id: "RFC9000-18.2",
section: "18",
level: RequirementLevel::Must,
description: "Invalid transport parameters MUST cause handshake failure",
test_fn: test_invalid_transport_parameters,
},
ConformanceCase {
id: "RFC9000-17.1",
section: "17",
level: RequirementLevel::Must,
description: "Initial packets MUST be handled according to packet format rules",
test_fn: test_initial_packet_handling,
},
ConformanceCase {
id: "RFC9000-12.1",
section: "12",
level: RequirementLevel::Must,
description: "Connection errors during handshake MUST be signaled appropriately",
test_fn: test_handshake_error_signaling,
},
];
fn test_tls_version_requirement() -> ConformanceResult {
ConformanceResult::Skipped {
reason: "Requires a harness that can observe configured or negotiated TLS version"
.to_string(),
}
}
fn test_alpn_support_requirement() -> ConformanceResult {
ConformanceResult::Skipped {
reason:
"Requires a handshake harness that can observe advertised or negotiated ALPN protocols"
.to_string(),
}
}
fn test_certificate_verification_requirement() -> ConformanceResult {
let cx = test_cx();
let mut config = QuicConfig::default();
match QuicEndpoint::client(&cx, &config) {
Ok(_) => {}
Err(e) => {
return ConformanceResult::Fail {
reason: format!("Default client creation failed: {}", e),
};
}
}
config.insecure_skip_verify = true;
match QuicEndpoint::client(&cx, &config) {
Ok(_) => ConformanceResult::Pass,
Err(e) => ConformanceResult::Fail {
reason: format!("Client with skip verify failed: {}", e),
},
}
}
fn test_client_certificate_support() -> ConformanceResult {
ConformanceResult::Skipped {
reason: "Requires certificate infrastructure not available in unit tests".to_string(),
}
}
fn test_connection_id_uniqueness() -> ConformanceResult {
ConformanceResult::Skipped {
reason: "Connection ID generation is internal to Quinn library".to_string(),
}
}
fn test_handshake_completion_ordering() -> ConformanceResult {
ConformanceResult::Skipped {
reason: "Requires live connection testing not suitable for unit tests".to_string(),
}
}
fn test_version_negotiation_support() -> ConformanceResult {
ConformanceResult::Skipped {
reason: "Version negotiation is internal to Quinn library".to_string(),
}
}
fn test_transport_parameter_exchange() -> ConformanceResult {
ConformanceResult::Skipped {
reason:
"Requires a live client/server handshake harness to verify transport-parameter exchange"
.to_string(),
}
}
fn test_invalid_transport_parameters() -> ConformanceResult {
ConformanceResult::Skipped {
reason: "Transport parameter validation is internal to Quinn library".to_string(),
}
}
fn test_initial_packet_handling() -> ConformanceResult {
ConformanceResult::Skipped {
reason: "Packet format handling is internal to Quinn library".to_string(),
}
}
fn test_handshake_error_signaling() -> ConformanceResult {
let cx = test_cx();
let config = QuicConfig::default();
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
match QuicEndpoint::server(&cx, addr, &config) {
Ok(_) => ConformanceResult::Fail {
reason: "Server creation should fail without certificates".to_string(),
},
Err(_) => ConformanceResult::Pass, }
}
pub fn run_conformance_tests() -> ConformanceReport {
let mut results = HashMap::new();
let mut stats = ConformanceStats::default();
for case in RFC9000_HANDSHAKE_CASES {
let result = (case.test_fn)();
match case.level {
RequirementLevel::Must => stats.must_total += 1,
RequirementLevel::Should => stats.should_total += 1,
RequirementLevel::May => stats.may_total += 1,
}
match &result {
ConformanceResult::Pass => {
stats.passing += 1;
match case.level {
RequirementLevel::Must => stats.must_passing += 1,
RequirementLevel::Should => stats.should_passing += 1,
RequirementLevel::May => stats.may_passing += 1,
}
}
ConformanceResult::Fail { .. } => stats.failing += 1,
ConformanceResult::Skipped { .. } => stats.skipped += 1,
ConformanceResult::ExpectedFailure { .. } => stats.expected_failures += 1,
}
results.insert(
case.id.to_string(),
ConformanceTestResult {
case: case.clone(),
result,
},
);
}
ConformanceReport { results, stats }
}
#[derive(Debug, Default)]
pub struct ConformanceStats {
pub must_total: usize,
pub should_total: usize,
pub may_total: usize,
pub must_passing: usize,
pub should_passing: usize,
pub may_passing: usize,
pub passing: usize,
pub failing: usize,
pub skipped: usize,
pub expected_failures: usize,
}
impl ConformanceStats {
pub fn must_score(&self) -> f64 {
if self.must_total == 0 {
return 1.0;
}
self.must_passing as f64 / self.must_total as f64
}
pub fn overall_score(&self) -> f64 {
let total = self.must_total + self.should_total + self.may_total;
if total == 0 {
return 1.0;
}
self.passing as f64 / total as f64
}
}
#[derive(Debug)]
pub struct ConformanceTestResult {
pub case: ConformanceCase,
pub result: ConformanceResult,
}
#[derive(Debug)]
pub struct ConformanceReport {
pub results: HashMap<String, ConformanceTestResult>,
pub stats: ConformanceStats,
}
impl ConformanceReport {
pub fn generate_markdown_report(&self) -> String {
let mut report = String::new();
report.push_str("# RFC 9000 QUIC Handshake Conformance Report\n\n");
report.push_str(&format!(
"## Summary\n\
- **MUST clauses:** {}/{} ({:.1}%)\n\
- **SHOULD clauses:** {}/{} ({:.1}%)\n\
- **MAY clauses:** {}/{}\n\
- **Overall score:** {:.1}%\n\
- **Passing:** {}\n\
- **Failing:** {}\n\
- **Skipped:** {}\n\
- **Expected failures:** {}\n\n",
self.stats.must_passing,
self.stats.must_total,
self.stats.must_score() * 100.0,
self.stats.should_passing,
self.stats.should_total,
if self.stats.should_total > 0 {
self.stats.should_passing as f64 / self.stats.should_total as f64 * 100.0
} else {
100.0
},
self.stats.may_passing,
self.stats.may_total,
self.stats.overall_score() * 100.0,
self.stats.passing,
self.stats.failing,
self.stats.skipped,
self.stats.expected_failures
));
report.push_str("## Detailed Results\n\n");
report.push_str("| Test ID | Section | Level | Description | Result | Notes |\n");
report.push_str("|---------|---------|-------|-------------|--------|---------|\n");
let mut sorted_results: Vec<_> = self.results.values().collect();
sorted_results.sort_by_key(|r| r.case.id);
for test_result in sorted_results {
let (status, notes) = match &test_result.result {
ConformanceResult::Pass => ("✅ PASS", ""),
ConformanceResult::Fail { reason } => ("❌ FAIL", reason),
ConformanceResult::Skipped { reason } => ("⚠️ SKIP", reason),
ConformanceResult::ExpectedFailure { reason } => ("🔸 XFAIL", reason),
};
report.push_str(&format!(
"| {} | {} | {} | {} | {} | {} |\n",
test_result.case.id,
test_result.case.section,
test_result.case.level.as_str(),
test_result.case.description,
status,
notes
));
}
report.push_str("\n");
if self.stats.must_score() >= 0.95 {
report.push_str("## ✅ CONFORMANCE VERDICT: COMPLIANT\n\n");
report
.push_str("This implementation meets RFC 9000 MUST requirements (≥95% passing).\n");
} else {
report.push_str("## ❌ CONFORMANCE VERDICT: NON-COMPLIANT\n\n");
report.push_str(&format!(
"This implementation does not meet RFC 9000 MUST requirements ({:.1}% < 95%).\n",
self.stats.must_score() * 100.0
));
}
report
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::pedantic,
clippy::nursery,
clippy::expect_fun_call,
clippy::map_unwrap_or,
clippy::cast_possible_wrap,
clippy::future_not_send
)]
use super::*;
#[test]
fn run_rfc9000_conformance_suite() {
let report = run_conformance_tests();
println!("{}", report.generate_markdown_report());
for (test_id, result) in &report.results {
if let ConformanceResult::Fail { reason } = &result.result {
panic!("Conformance test {} failed: {}", test_id, reason);
}
}
assert_eq!(report.results.len(), RFC9000_HANDSHAKE_CASES.len());
}
#[test]
fn conformance_stats_calculation() {
let stats = ConformanceStats {
must_total: 10,
should_total: 5,
may_total: 2,
must_passing: 10,
should_passing: 2,
may_passing: 0,
passing: 12,
failing: 2,
skipped: 3,
expected_failures: 0,
};
assert_eq!(stats.must_score(), 1.0); assert_eq!(stats.overall_score(), 12.0 / 17.0); assert_eq!(
stats.should_passing as f64 / stats.should_total as f64,
2.0 / 5.0
);
}
}