asupersync 0.3.4

Spec-first, cancel-correct, capability-secure async runtime for Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
//! RFC 9000 QUIC Handshake Conformance Test Harness
//!
//! This module implements conformance testing for QUIC handshake behavior
//! against RFC 9000 requirements. The tests verify that our QuicEndpoint
//! wrapper correctly implements the QUIC handshake protocol.
//!
//! # Organization
//!
//! Tests are organized by RFC 9000 section and requirement level:
//! - MUST clauses: Critical for conformance, failures block compliance
//! - SHOULD clauses: Recommended behavior, tracked but may be acceptable divergences
//! - MAY clauses: Optional behavior, documented for completeness
//!
//! # Architecture
//!
//! This follows Pattern 4: Spec-Derived Test Matrix from the conformance harness skill.
//! Each test maps to a specific RFC requirement with structured results.

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;

/// Requirement level from RFC 9000
#[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",
        }
    }
}

/// Test result for conformance verification
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConformanceResult {
    Pass,
    Fail { reason: String },
    Skipped { reason: String },
    ExpectedFailure { reason: String }, // Known divergence (XFAIL)
}

/// A single conformance test case mapping to RFC 9000 requirements
#[derive(Debug, Clone)]
pub struct ConformanceCase {
    /// Unique identifier (e.g., "RFC9000-7.2.1")
    pub id: &'static str,
    /// RFC section number
    pub section: &'static str,
    /// Requirement level
    pub level: RequirementLevel,
    /// Human-readable description
    pub description: &'static str,
    /// Test implementation
    pub test_fn: fn() -> ConformanceResult,
}

/// RFC 9000 QUIC Handshake conformance test cases
pub const RFC9000_HANDSHAKE_CASES: &[ConformanceCase] = &[
    // Section 7: Cryptographic and Transport Handshake
    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,
    },
    // Section 4: Connection Establishment
    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,
    },
    // Section 6: Version Negotiation
    ConformanceCase {
        id: "RFC9000-6.1",
        section: "6",
        level: RequirementLevel::Must,
        description: "Endpoints MUST handle version negotiation correctly",
        test_fn: test_version_negotiation_support,
    },
    // Section 18: Transport Parameters
    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,
    },
    // Section 17: Packet Format
    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,
    },
    // Section 12: Error 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,
    },
];

// =============================================================================
// Test Implementations
// =============================================================================

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();

    // Test 1: Normal certificate verification should be enabled by default
    match QuicEndpoint::client(&cx, &config) {
        Ok(_) => {}
        Err(e) => {
            return ConformanceResult::Fail {
                reason: format!("Default client creation failed: {}", e),
            };
        }
    }

    // Test 2: When insecure_skip_verify is enabled, verification should be skipped
    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 {
    // br-asupersync-b56zt9: dropped unused cx/config/addr bindings — this
    // arm currently returns Skipped pending a real cert harness.
    ConformanceResult::Skipped {
        reason: "Requires certificate infrastructure not available in unit tests".to_string(),
    }
}

fn test_connection_id_uniqueness() -> ConformanceResult {
    // Connection ID uniqueness is handled by Quinn internally
    // We can't easily test this without creating actual connections
    ConformanceResult::Skipped {
        reason: "Connection ID generation is internal to Quinn library".to_string(),
    }
}

fn test_handshake_completion_ordering() -> ConformanceResult {
    // This would require creating actual connections and verifying handshake ordering
    ConformanceResult::Skipped {
        reason: "Requires live connection testing not suitable for unit tests".to_string(),
    }
}

fn test_version_negotiation_support() -> ConformanceResult {
    // Version negotiation is handled by Quinn
    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 {
    // This would require testing quinn's behavior with invalid parameters
    ConformanceResult::Skipped {
        reason: "Transport parameter validation is internal to Quinn library".to_string(),
    }
}

fn test_initial_packet_handling() -> ConformanceResult {
    // Packet handling is internal to Quinn
    ConformanceResult::Skipped {
        reason: "Packet format handling is internal to Quinn library".to_string(),
    }
}

fn test_handshake_error_signaling() -> ConformanceResult {
    let cx = test_cx();

    // br-asupersync-b56zt9: was `let mut config` but never mutated.
    let config = QuicConfig::default();

    // Test server without certificates
    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, // Expected to fail
    }
}

// =============================================================================
// Conformance Test Runner
// =============================================================================

/// Run all RFC 9000 handshake conformance tests
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)();

        // Update statistics
        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 }
}

/// Statistics for conformance test execution
#[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
    }
}

/// Result of a single conformance test
#[derive(Debug)]
pub struct ConformanceTestResult {
    pub case: ConformanceCase,
    pub result: ConformanceResult,
}

/// Complete conformance report
#[derive(Debug)]
pub struct ConformanceReport {
    pub results: HashMap<String, ConformanceTestResult>,
    pub stats: ConformanceStats,
}

impl ConformanceReport {
    /// Generate a markdown compliance report
    pub fn generate_markdown_report(&self) -> String {
        let mut report = String::new();

        report.push_str("# RFC 9000 QUIC Handshake Conformance Report\n\n");

        // Summary statistics
        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
        ));

        // Detailed results table
        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");

        // Compliance verdict
        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();

        // Print the report
        println!("{}", report.generate_markdown_report());

        // Ensure we don't have any unexpected failures
        // (Skipped tests are acceptable for unit test context)
        for (test_id, result) in &report.results {
            if let ConformanceResult::Fail { reason } = &result.result {
                panic!("Conformance test {} failed: {}", test_id, reason);
            }
        }

        // Verify we have the expected number of test cases
        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); // All MUST tests passing
        assert_eq!(stats.overall_score(), 12.0 / 17.0); // 12 passing out of 17 total
        assert_eq!(
            stats.should_passing as f64 / stats.should_total as f64,
            2.0 / 5.0
        );
    }
}