ant_quic/compliance_validator/
endpoint_tester.rs

1/// Endpoint Testing Module
2///
3/// Tests QUIC implementation against real-world endpoints
4use super::{EndpointResult, EndpointValidationReport, ValidationError};
5use crate::{
6    ClientConfig, EndpointConfig, VarInt,
7    high_level::{Connection, Endpoint},
8    transport_parameters::TransportParameters,
9};
10use std::collections::HashMap;
11use std::net::ToSocketAddrs;
12use std::time::Duration;
13use tokio::time::timeout;
14use tracing::{error, info, warn};
15
16/// Known public QUIC endpoints for testing
17/// Last verified: 2025-07-25
18pub const PUBLIC_QUIC_ENDPOINTS: &[&str] = &[
19    // Major providers (production)
20    "quic.nginx.org:443", // NGINX official QUIC endpoint
21    "cloudflare.com:443", // Cloudflare production
22    "www.google.com:443", // Google production
23    "facebook.com:443",   // Meta/Facebook production
24    // Dedicated test servers
25    "cloudflare-quic.com:443",           // Cloudflare QUIC test site
26    "quic.rocks:4433",                   // Google QUIC test endpoint
27    "http3-test.litespeedtech.com:4433", // LiteSpeed standard test
28    "http3-test.litespeedtech.com:4434", // LiteSpeed with stateless retry
29    "test.privateoctopus.com:4433",      // Picoquic test server
30    "test.privateoctopus.com:4434",      // Picoquic retry test
31    "test.pquic.org:443",                // PQUIC research server
32    "www.litespeedtech.com:443",         // LiteSpeed production
33    // Additional endpoints from previous list
34    "quic.tech:4433",
35    "quic.westus.cloudapp.azure.com:4433",
36    "h3.vortex.data.msn.com:443",
37];
38
39/// Endpoint tester for validating against real QUIC servers
40pub struct EndpointTester {
41    /// Local endpoint for testing
42    endpoint: Option<Endpoint>,
43    /// Test timeout
44    timeout_duration: Duration,
45    /// Custom test endpoints
46    custom_endpoints: Vec<String>,
47}
48
49impl Default for EndpointTester {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl EndpointTester {
56    /// Create a new endpoint tester
57    pub fn new() -> Self {
58        Self {
59            endpoint: None,
60            timeout_duration: Duration::from_secs(10),
61            custom_endpoints: Vec::new(),
62        }
63    }
64
65    /// Set custom timeout duration
66    pub fn with_timeout(mut self, duration: Duration) -> Self {
67        self.timeout_duration = duration;
68        self
69    }
70
71    /// Add custom endpoint for testing
72    pub fn add_endpoint(&mut self, endpoint: String) {
73        self.custom_endpoints.push(endpoint);
74    }
75
76    /// Initialize the local endpoint
77    async fn init_endpoint(&mut self) -> Result<(), ValidationError> {
78        if self.endpoint.is_none() {
79            let socket = std::net::UdpSocket::bind("0.0.0.0:0").map_err(|e| {
80                ValidationError::ValidationError(format!("Failed to bind socket: {e}"))
81            })?;
82            let runtime = crate::high_level::default_runtime().ok_or_else(|| {
83                ValidationError::ValidationError("No compatible async runtime found".to_string())
84            })?;
85            let endpoint = Endpoint::new(
86                EndpointConfig::default(),
87                None, // No server config for client
88                socket,
89                runtime,
90            )
91            .map_err(|e| {
92                ValidationError::ValidationError(format!("Failed to create endpoint: {e}"))
93            })?;
94
95            self.endpoint = Some(endpoint);
96        }
97        Ok(())
98    }
99
100    /// Test all endpoints
101    pub async fn test_all_endpoints(&mut self) -> EndpointValidationReport {
102        self.init_endpoint().await.unwrap_or_else(|e| {
103            error!("Failed to initialize endpoint: {}", e);
104        });
105
106        let mut all_endpoints = PUBLIC_QUIC_ENDPOINTS
107            .iter()
108            .map(|&s| s.to_string())
109            .collect::<Vec<_>>();
110        all_endpoints.extend(self.custom_endpoints.clone());
111
112        let mut endpoint_results = HashMap::new();
113        let mut successful = 0;
114        let mut common_issues = HashMap::new();
115
116        for endpoint_str in &all_endpoints {
117            info!("Testing endpoint: {}", endpoint_str);
118
119            match self.test_endpoint(endpoint_str).await {
120                Ok(result) => {
121                    if result.connected {
122                        successful += 1;
123                    }
124
125                    // Track common issues
126                    for issue in &result.issues {
127                        *common_issues.entry(issue.clone()).or_insert(0) += 1;
128                    }
129
130                    endpoint_results.insert(endpoint_str.clone(), result);
131                }
132                Err(e) => {
133                    warn!("Failed to test endpoint {}: {}", endpoint_str, e);
134                    endpoint_results.insert(
135                        endpoint_str.clone(),
136                        EndpointResult {
137                            endpoint: endpoint_str.clone(),
138                            connected: false,
139                            quic_versions: vec![],
140                            extensions: vec![],
141                            issues: vec![format!("Test failed: {}", e)],
142                        },
143                    );
144                }
145            }
146        }
147
148        let success_rate = if all_endpoints.is_empty() {
149            0.0
150        } else {
151            successful as f64 / all_endpoints.len() as f64
152        };
153
154        // Extract most common issues
155        let mut common_issues_vec: Vec<_> = common_issues.into_iter().collect();
156        common_issues_vec.sort_by(|a, b| b.1.cmp(&a.1));
157        let common_issues = common_issues_vec
158            .into_iter()
159            .take(5)
160            .map(|(issue, _)| issue)
161            .collect();
162
163        EndpointValidationReport {
164            endpoint_results,
165            success_rate,
166            common_issues,
167        }
168    }
169
170    /// Test a single endpoint
171    async fn test_endpoint(&self, endpoint_str: &str) -> Result<EndpointResult, ValidationError> {
172        let addr = endpoint_str
173            .to_socket_addrs()
174            .map_err(|e| ValidationError::ValidationError(format!("Invalid address: {e}")))?
175            .next()
176            .ok_or_else(|| ValidationError::ValidationError("No address resolved".to_string()))?;
177
178        let endpoint = self.endpoint.as_ref().ok_or_else(|| {
179            ValidationError::ValidationError("Endpoint not initialized".to_string())
180        })?;
181
182        // Extract server name from endpoint string
183        let server_name = endpoint_str.split(':').next().unwrap_or(endpoint_str);
184
185        // Create client config
186        let client_config = create_test_client_config(server_name)?;
187
188        // Attempt connection
189        let connecting = match endpoint.connect_with(client_config, addr, server_name) {
190            Ok(connecting) => connecting,
191            Err(e) => {
192                return Ok(EndpointResult {
193                    endpoint: endpoint_str.to_string(),
194                    connected: false,
195                    quic_versions: vec![],
196                    extensions: vec![],
197                    issues: vec![format!("Failed to start connection: {}", e)],
198                });
199            }
200        };
201
202        let connect_result = timeout(self.timeout_duration, connecting).await;
203
204        match connect_result {
205            Ok(Ok(connection)) => {
206                // Connection successful - analyze capabilities
207                let result = self.analyze_connection(endpoint_str, connection).await?;
208                Ok(result)
209            }
210            Ok(Err(e)) => {
211                // Connection failed
212                Ok(EndpointResult {
213                    endpoint: endpoint_str.to_string(),
214                    connected: false,
215                    quic_versions: vec![],
216                    extensions: vec![],
217                    issues: vec![format!("Connection failed: {}", e)],
218                })
219            }
220            Err(_) => {
221                // Timeout
222                Ok(EndpointResult {
223                    endpoint: endpoint_str.to_string(),
224                    connected: false,
225                    quic_versions: vec![],
226                    extensions: vec![],
227                    issues: vec!["Connection timeout".to_string()],
228                })
229            }
230        }
231    }
232
233    /// Analyze a successful connection
234    async fn analyze_connection(
235        &self,
236        endpoint_str: &str,
237        connection: Connection,
238    ) -> Result<EndpointResult, ValidationError> {
239        let mut issues = Vec::new();
240
241        // TODO: Get actual transport parameters from connection
242        // For now, use placeholder values
243        let quic_versions = vec![0x00000001]; // QUIC v1
244
245        // Check for extensions
246        let extensions = Vec::new();
247
248        // TODO: Check for address discovery and NAT traversal support
249        // when we have access to transport parameters
250
251        // Test basic data exchange
252        match self.test_data_exchange(&connection).await {
253            Ok(()) => {
254                info!("Data exchange successful with {}", endpoint_str);
255            }
256            Err(e) => {
257                issues.push(format!("Data exchange failed: {e}"));
258            }
259        }
260
261        // TODO: Check compliance issues when we have transport parameters
262
263        // Close connection gracefully
264        connection.close(VarInt::from_u32(0), b"test complete");
265
266        Ok(EndpointResult {
267            endpoint: endpoint_str.to_string(),
268            connected: true,
269            quic_versions,
270            extensions,
271            issues,
272        })
273    }
274
275    /// Test basic data exchange
276    async fn test_data_exchange(&self, connection: &Connection) -> Result<(), ValidationError> {
277        // Open a bidirectional stream
278        let (mut send, mut recv) = connection
279            .open_bi()
280            .await
281            .map_err(|e| ValidationError::ValidationError(format!("Failed to open stream: {e}")))?;
282
283        // Send test data
284        let test_data = b"QUIC compliance test";
285        send.write_all(&test_data[..])
286            .await
287            .map_err(|e| ValidationError::ValidationError(format!("Failed to send data: {e}")))?;
288
289        send.finish().map_err(|e| {
290            ValidationError::ValidationError(format!("Failed to finish stream: {e}"))
291        })?;
292
293        // Read response (if any)
294        let mut buf = vec![0u8; 1024];
295        let _ = timeout(Duration::from_secs(2), recv.read(&mut buf)).await;
296
297        Ok(())
298    }
299
300    /// Check compliance issues in transport parameters
301    fn check_compliance(&self, params: &TransportParameters) -> Option<Vec<String>> {
302        let mut issues = Vec::new();
303
304        // Check max_udp_payload_size
305        if params.max_udp_payload_size.0 < 1200 {
306            issues.push("max_udp_payload_size < 1200 (RFC 9000 violation)".to_string());
307        }
308
309        // Check ack_delay_exponent
310        if params.ack_delay_exponent.0 > 20 {
311            issues.push("ack_delay_exponent > 20 (RFC 9000 violation)".to_string());
312        }
313
314        // Check max_ack_delay
315        if params.max_ack_delay.0 >= (1 << 14) {
316            issues.push("max_ack_delay >= 2^14 (RFC 9000 violation)".to_string());
317        }
318
319        // Check active_connection_id_limit
320        if params.active_connection_id_limit.0 < 2 {
321            issues.push("active_connection_id_limit < 2 (RFC 9000 violation)".to_string());
322        }
323
324        if issues.is_empty() {
325            None
326        } else {
327            Some(issues)
328        }
329    }
330}
331
332/// Create a test client configuration
333fn create_test_client_config(_server_name: &str) -> Result<ClientConfig, ValidationError> {
334    // Use the platform verifier if available
335    #[cfg(feature = "platform-verifier")]
336    {
337        ClientConfig::try_with_platform_verifier().map_err(|e| {
338            ValidationError::ValidationError(format!("Failed to create client config: {e}"))
339        })
340    }
341
342    #[cfg(not(feature = "platform-verifier"))]
343    {
344        // Fall back to accepting any certificate for testing
345        use crate::crypto::rustls::QuicClientConfig;
346        use std::sync::Arc;
347
348        let mut roots = rustls::RootCertStore::empty();
349
350        // Add system roots
351        let cert_result = rustls_native_certs::load_native_certs();
352        for cert in cert_result.certs {
353            roots.add(cert.into()).ok();
354        }
355        if !cert_result.errors.is_empty() {
356            warn!("Failed to load some native certs: {:?}", cert_result.errors);
357        }
358
359        // Create rustls config
360        let crypto = rustls::ClientConfig::builder()
361            .with_root_certificates(roots)
362            .with_no_client_auth();
363
364        // Convert to QUIC client config
365        let quic_crypto = QuicClientConfig::try_from(Arc::new(crypto)).map_err(|e| {
366            ValidationError::ValidationError(format!(
367                "Failed to create QUIC crypto config: {:?}",
368                e
369            ))
370        })?;
371
372        Ok(ClientConfig::new(Arc::new(quic_crypto)))
373    }
374}
375
376/// Get recommended test endpoints based on requirements
377pub fn get_recommended_endpoints(requirements: &[&str]) -> Vec<String> {
378    let mut endpoints = Vec::new();
379
380    for req in requirements {
381        match *req {
382            "address_discovery" => {
383                // Endpoints known to support address discovery
384                endpoints.push("quic.tech:4433".to_string());
385            }
386            "nat_traversal" => {
387                // Endpoints that might support NAT traversal
388                endpoints.push("test.privateoctopus.com:4433".to_string());
389            }
390            "h3" => {
391                // HTTP/3 endpoints
392                endpoints.push("cloudflare.com:443".to_string());
393                endpoints.push("www.google.com:443".to_string());
394            }
395            _ => {}
396        }
397    }
398
399    // Remove duplicates
400    endpoints.sort();
401    endpoints.dedup();
402
403    endpoints
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409
410    #[test]
411    fn test_endpoint_tester_creation() {
412        let tester = EndpointTester::new();
413        assert_eq!(tester.timeout_duration, Duration::from_secs(10));
414        assert!(tester.custom_endpoints.is_empty());
415    }
416
417    #[test]
418    fn test_add_endpoint() {
419        let mut tester = EndpointTester::new();
420        tester.add_endpoint("example.com:443".to_string());
421        assert_eq!(tester.custom_endpoints.len(), 1);
422        assert_eq!(tester.custom_endpoints[0], "example.com:443");
423    }
424
425    #[test]
426    fn test_with_timeout() {
427        let tester = EndpointTester::new().with_timeout(Duration::from_secs(30));
428        assert_eq!(tester.timeout_duration, Duration::from_secs(30));
429    }
430
431    #[test]
432    fn test_recommended_endpoints() {
433        let endpoints = get_recommended_endpoints(&["h3"]);
434        assert!(!endpoints.is_empty());
435        assert!(endpoints.contains(&"cloudflare.com:443".to_string()));
436
437        let endpoints = get_recommended_endpoints(&["address_discovery"]);
438        assert!(endpoints.contains(&"quic.tech:4433".to_string()));
439    }
440
441    #[test]
442    fn test_compliance_check() {
443        let tester = EndpointTester::new();
444
445        // Valid parameters
446        let mut params = TransportParameters::default();
447        params.max_udp_payload_size = VarInt::from_u32(1500);
448        params.ack_delay_exponent = VarInt::from_u32(3);
449        params.max_ack_delay = VarInt::from_u32(25);
450        params.active_connection_id_limit = VarInt::from_u32(4);
451
452        assert!(tester.check_compliance(&params).is_none());
453
454        // Invalid parameters
455        params.max_udp_payload_size = VarInt::from_u32(1000);
456        params.ack_delay_exponent = VarInt::from_u32(21);
457
458        let issues = tester.check_compliance(&params).unwrap();
459        assert_eq!(issues.len(), 2);
460    }
461}