Skip to main content

nntp_proxy/
tls.rs

1//! TLS configuration and handshake management for NNTP connections
2//!
3//! This module provides high-performance TLS support using rustls with optimizations:
4//! - Ring crypto provider for pure Rust crypto operations
5//! - TLS 1.3 early data (0-RTT) enabled for faster reconnections
6//! - Session resumption enabled to avoid full handshakes
7//! - Pure Rust implementation (memory safe, no C dependencies)
8//! - System certificate loading with Mozilla CA bundle fallback
9
10use crate::connection_error::ConnectionError;
11use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
12use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
13use rustls::{
14    ClientConfig, DigitallySignedStruct, Error as RustlsError, RootCertStore, SignatureScheme,
15};
16use std::sync::Arc;
17use tokio::net::TcpStream;
18use tokio_rustls::TlsConnector;
19use tracing::{debug, warn};
20
21// Re-export TlsStream for use in other modules
22pub use tokio_rustls::client::TlsStream;
23
24/// Configuration for TLS connections
25#[derive(Debug, Clone)]
26pub struct TlsConfig {
27    /// Enable TLS for this connection
28    pub use_tls: bool,
29    /// Verify server certificates (recommended: true)  
30    pub tls_verify_cert: bool,
31    /// Path to custom CA certificate file (optional)
32    pub tls_cert_path: Option<String>,
33}
34
35impl Default for TlsConfig {
36    fn default() -> Self {
37        Self {
38            use_tls: false,
39            tls_verify_cert: true,
40            tls_cert_path: None,
41        }
42    }
43}
44
45impl TlsConfig {
46    /// Create a builder for TlsConfig
47    ///
48    /// # Example
49    /// ```
50    /// use nntp_proxy::tls::TlsConfig;
51    ///
52    /// let config = TlsConfig::builder()
53    ///     .enabled(true)
54    ///     .verify_cert(true)
55    ///     .build();
56    /// ```
57    pub fn builder() -> TlsConfigBuilder {
58        TlsConfigBuilder::default()
59    }
60}
61
62/// Builder for type-safe TLS configuration
63///
64/// Provides a fluent API for constructing TLS configurations with sensible defaults.
65///
66/// # Examples
67///
68/// Basic TLS with verification:
69/// ```
70/// use nntp_proxy::tls::TlsConfig;
71///
72/// let config = TlsConfig::builder()
73///     .enabled(true)
74///     .verify_cert(true)
75///     .build();
76/// ```
77///
78/// TLS with custom CA certificate:
79/// ```
80/// use nntp_proxy::tls::TlsConfig;
81///
82/// let config = TlsConfig::builder()
83///     .enabled(true)
84///     .verify_cert(true)
85///     .cert_path("/path/to/ca.pem")
86///     .build();
87/// ```
88///
89/// Insecure TLS (for testing only):
90/// ```
91/// use nntp_proxy::tls::TlsConfig;
92///
93/// let config = TlsConfig::builder()
94///     .enabled(true)
95///     .verify_cert(false)
96///     .build();
97/// ```
98#[derive(Debug, Clone)]
99pub struct TlsConfigBuilder {
100    use_tls: bool,
101    tls_verify_cert: bool,
102    tls_cert_path: Option<String>,
103}
104
105impl Default for TlsConfigBuilder {
106    fn default() -> Self {
107        Self {
108            use_tls: false,
109            tls_verify_cert: true, // Secure by default
110            tls_cert_path: None,
111        }
112    }
113}
114
115impl TlsConfigBuilder {
116    /// Enable or disable TLS
117    ///
118    /// Default: `false`
119    pub fn enabled(mut self, use_tls: bool) -> Self {
120        self.use_tls = use_tls;
121        self
122    }
123
124    /// Enable or disable certificate verification
125    ///
126    /// **WARNING**: Disabling certificate verification is insecure and should only
127    /// be used for testing or with trusted private networks.
128    ///
129    /// Default: `true`
130    pub fn verify_cert(mut self, verify: bool) -> Self {
131        self.tls_verify_cert = verify;
132        self
133    }
134
135    /// Set path to custom CA certificate file
136    ///
137    /// The certificate should be in PEM format.
138    pub fn cert_path<S: Into<String>>(mut self, path: S) -> Self {
139        self.tls_cert_path = Some(path.into());
140        self
141    }
142
143    /// Build the TlsConfig
144    pub fn build(self) -> TlsConfig {
145        TlsConfig {
146            use_tls: self.use_tls,
147            tls_verify_cert: self.tls_verify_cert,
148            tls_cert_path: self.tls_cert_path,
149        }
150    }
151}
152
153// Certificate handling and custom verifiers
154
155mod rustls_backend {
156    use super::*;
157
158    /// Certificate loading results
159    #[derive(Debug)]
160    pub struct CertificateLoadResult {
161        pub root_store: RootCertStore,
162        pub sources: Vec<String>,
163    }
164
165    /// Custom certificate verifier that accepts all certificates (INSECURE!)
166    ///
167    /// This is used when `tls_verify_cert = false` for NNTP servers without valid certificates.
168    /// **WARNING**: This disables all certificate validation and should only be used for testing
169    /// or with trusted private networks.
170    #[derive(Debug)]
171    pub struct NoVerifier;
172
173    impl ServerCertVerifier for NoVerifier {
174        fn verify_server_cert(
175            &self,
176            _end_entity: &CertificateDer<'_>,
177            _intermediates: &[CertificateDer<'_>],
178            _server_name: &ServerName<'_>,
179            _ocsp_response: &[u8],
180            _now: UnixTime,
181        ) -> Result<ServerCertVerified, RustlsError> {
182            // Accept all certificates without verification
183            Ok(ServerCertVerified::assertion())
184        }
185
186        fn verify_tls12_signature(
187            &self,
188            _message: &[u8],
189            _cert: &CertificateDer<'_>,
190            _dss: &DigitallySignedStruct,
191        ) -> Result<HandshakeSignatureValid, RustlsError> {
192            // Accept all signatures without verification
193            Ok(HandshakeSignatureValid::assertion())
194        }
195
196        fn verify_tls13_signature(
197            &self,
198            _message: &[u8],
199            _cert: &CertificateDer<'_>,
200            _dss: &DigitallySignedStruct,
201        ) -> Result<HandshakeSignatureValid, RustlsError> {
202            // Accept all signatures without verification
203            Ok(HandshakeSignatureValid::assertion())
204        }
205
206        fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
207            // Support all signature schemes
208            vec![
209                SignatureScheme::RSA_PKCS1_SHA1,
210                SignatureScheme::ECDSA_SHA1_Legacy,
211                SignatureScheme::RSA_PKCS1_SHA256,
212                SignatureScheme::ECDSA_NISTP256_SHA256,
213                SignatureScheme::RSA_PKCS1_SHA384,
214                SignatureScheme::ECDSA_NISTP384_SHA384,
215                SignatureScheme::RSA_PKCS1_SHA512,
216                SignatureScheme::ECDSA_NISTP521_SHA512,
217                SignatureScheme::RSA_PSS_SHA256,
218                SignatureScheme::RSA_PSS_SHA384,
219                SignatureScheme::RSA_PSS_SHA512,
220                SignatureScheme::ED25519,
221                SignatureScheme::ED448,
222            ]
223        }
224    }
225}
226
227/// High-performance TLS connector with cached configuration
228///
229/// Caches the parsed TLS configuration including certificates to avoid
230/// expensive re-parsing on every connection. Certificates are loaded once
231/// during initialization and reused for all connections.
232pub struct TlsManager {
233    config: TlsConfig,
234    /// Cached TLS connector with pre-loaded certificates
235    ///
236    /// Avoids expensive certificate parsing overhead (DER parsing, X.509 validation,
237    /// signature verification) on every connection by loading certificates once at init.
238    cached_connector: Arc<TlsConnector>,
239}
240
241impl std::fmt::Debug for TlsManager {
242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        f.debug_struct("TlsManager")
244            .field("config", &self.config)
245            .field("cached_connector", &"<TlsConnector>")
246            .finish()
247    }
248}
249
250impl Clone for TlsManager {
251    fn clone(&self) -> Self {
252        Self {
253            config: self.config.clone(),
254            cached_connector: Arc::clone(&self.cached_connector),
255        }
256    }
257}
258
259impl TlsManager {
260    /// Create a new TLS manager with the given configuration
261    ///
262    /// **Performance**: Loads and parses certificates once during initialization
263    /// instead of on every connection, eliminating certificate parsing overhead
264    /// (DER parsing, X.509 validation, signature verification).
265    pub fn new(config: TlsConfig) -> Result<Self, anyhow::Error> {
266        // Load certificates once during initialization
267        let cert_result = Self::load_certificates_sync(&config)?;
268        let client_config = Self::create_optimized_config_inner(cert_result.root_store, &config)?;
269
270        debug!(
271            "TLS: Initialized with certificate sources: {}",
272            cert_result.sources.join(", ")
273        );
274
275        let cached_connector = Arc::new(TlsConnector::from(Arc::new(client_config)));
276
277        Ok(Self {
278            config,
279            cached_connector,
280        })
281    }
282
283    /// Perform TLS handshake
284    pub async fn handshake(
285        &self,
286        stream: TcpStream,
287        hostname: &str,
288        backend_name: &str,
289    ) -> Result<TlsStream<TcpStream>, anyhow::Error> {
290        use anyhow::Context;
291
292        debug!("TLS: Connecting to {} with cached config", hostname);
293
294        let domain = rustls_pki_types::ServerName::try_from(hostname)
295            .context("Invalid hostname for TLS")?
296            .to_owned();
297
298        self.cached_connector
299            .connect(domain, stream)
300            .await
301            .map_err(|e| {
302                ConnectionError::TlsHandshake {
303                    backend: backend_name.to_string(),
304                    source: Box::new(e),
305                }
306                .into()
307            })
308    }
309
310    /// Load certificates from various sources with fallback chain (synchronous for init)
311    fn load_certificates_sync(
312        config: &TlsConfig,
313    ) -> Result<rustls_backend::CertificateLoadResult, anyhow::Error> {
314        let mut root_store = RootCertStore::empty();
315        let mut sources = Vec::new();
316
317        // 1. Load custom CA certificate if provided
318        if let Some(cert_path) = &config.tls_cert_path {
319            debug!("TLS: Loading custom CA certificate from: {}", cert_path);
320            Self::load_custom_certificate_sync(&mut root_store, cert_path)?;
321            sources.push("custom certificate".to_string());
322        }
323
324        // 2. Try to load system certificates
325        let system_count = Self::load_system_certificates_sync(&mut root_store)?;
326        if system_count > 0 {
327            debug!(
328                "TLS: Loaded {} certificates from system store",
329                system_count
330            );
331            sources.push("system certificates".to_string());
332        }
333
334        // 3. Fallback to Mozilla CA bundle if no certificates loaded
335        if root_store.is_empty() {
336            debug!("TLS: No system certificates available, using Mozilla CA bundle fallback");
337            root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
338            sources.push("Mozilla CA bundle".to_string());
339        }
340
341        Ok(rustls_backend::CertificateLoadResult {
342            root_store,
343            sources,
344        })
345    }
346
347    /// Load custom certificate from file
348    fn load_custom_certificate_sync(
349        root_store: &mut RootCertStore,
350        cert_path: &str,
351    ) -> Result<(), anyhow::Error> {
352        use anyhow::Context;
353
354        let cert_data = std::fs::read(cert_path)
355            .with_context(|| format!("Failed to read TLS certificate from {}", cert_path))?;
356
357        let certs = rustls_pemfile::certs(&mut cert_data.as_slice())
358            .collect::<Result<Vec<_>, _>>()
359            .context("Failed to parse TLS certificate")?;
360
361        for cert in certs {
362            root_store
363                .add(cert)
364                .context("Failed to add custom certificate to store")?;
365        }
366
367        Ok(())
368    }
369
370    /// Load system certificates, returning count of successfully loaded certificates
371    fn load_system_certificates_sync(
372        root_store: &mut RootCertStore,
373    ) -> Result<usize, anyhow::Error> {
374        let cert_result = rustls_native_certs::load_native_certs();
375        let mut added_count = 0;
376
377        for cert in cert_result.certs {
378            if root_store.add(cert).is_ok() {
379                added_count += 1;
380            }
381        }
382
383        // Log any errors but don't fail - we have fallback
384        for error in cert_result.errors {
385            warn!("TLS: Certificate loading error: {}", error);
386        }
387
388        Ok(added_count)
389    }
390
391    /// Create optimized client configuration using ring crypto provider
392    fn create_optimized_config_inner(
393        root_store: RootCertStore,
394        config: &TlsConfig,
395    ) -> Result<ClientConfig, anyhow::Error> {
396        use anyhow::Context;
397        use rustls_backend::NoVerifier;
398
399        let mut client_config = if config.tls_verify_cert {
400            debug!("TLS: Certificate verification enabled with ring crypto provider");
401            ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
402                .with_safe_default_protocol_versions()
403                .context("Failed to create TLS config with ring provider")?
404                .with_root_certificates(root_store)
405                .with_no_client_auth()
406        } else {
407            warn!(
408                "TLS: Certificate verification DISABLED - this is insecure and should only be used for testing!"
409            );
410            // Use custom verifier that accepts all certificates
411            ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
412                .with_safe_default_protocol_versions()
413                .context("Failed to create TLS config with ring provider")?
414                .dangerous()
415                .with_custom_certificate_verifier(Arc::new(NoVerifier))
416                .with_no_client_auth()
417        };
418
419        // Performance optimizations
420        client_config.enable_early_data = true; // Enable TLS 1.3 0-RTT for faster reconnections
421        client_config.resumption = rustls::client::Resumption::default(); // Enable session resumption
422
423        // Note: max_fragment_size is for outgoing records only (sending data to server)
424        // For incoming data (server->client), rustls uses internal buffering
425        // TLS 1.3 spec max is 16KB per record, larger values are not standard compliant
426        // and can cause connection failures with some servers
427        // client_config.max_fragment_size = Some(16384); // Keep default 16KB
428
429        Ok(client_config)
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436
437    #[test]
438    fn test_tls_config_default() {
439        let config = TlsConfig::default();
440        assert!(!config.use_tls);
441        assert!(config.tls_verify_cert);
442        assert!(config.tls_cert_path.is_none());
443    }
444
445    #[test]
446    fn test_tls_config_builder_default() {
447        let config = TlsConfig::builder().build();
448        assert!(!config.use_tls);
449        assert!(config.tls_verify_cert); // Secure by default
450        assert!(config.tls_cert_path.is_none());
451    }
452
453    #[test]
454    fn test_tls_config_builder_enabled() {
455        let config = TlsConfig::builder().enabled(true).verify_cert(true).build();
456        assert!(config.use_tls);
457        assert!(config.tls_verify_cert);
458        assert!(config.tls_cert_path.is_none());
459    }
460
461    #[test]
462    fn test_tls_config_builder_with_cert_path() {
463        let config = TlsConfig::builder()
464            .enabled(true)
465            .verify_cert(true)
466            .cert_path("/path/to/cert.pem")
467            .build();
468        assert!(config.use_tls);
469        assert!(config.tls_verify_cert);
470        assert_eq!(config.tls_cert_path, Some("/path/to/cert.pem".to_string()));
471    }
472
473    #[test]
474    fn test_tls_config_builder_insecure() {
475        let config = TlsConfig::builder()
476            .enabled(true)
477            .verify_cert(false)
478            .build();
479        assert!(config.use_tls);
480        assert!(!config.tls_verify_cert);
481        assert!(config.tls_cert_path.is_none());
482    }
483
484    #[test]
485    fn test_tls_config_builder_fluent_api() {
486        let config = TlsConfig::builder()
487            .enabled(true)
488            .verify_cert(true)
489            .cert_path("/custom/ca.pem".to_string())
490            .build();
491        assert!(config.use_tls);
492        assert!(config.tls_verify_cert);
493        assert_eq!(config.tls_cert_path, Some("/custom/ca.pem".to_string()));
494    }
495
496    #[test]
497    fn test_tls_manager_creation() {
498        let config = TlsConfig::default();
499        let manager = TlsManager::new(config).unwrap();
500        // Manager should successfully initialize with cached config
501        assert!(Arc::strong_count(&manager.cached_connector) >= 1);
502    }
503
504    #[test]
505    fn test_certificate_loading() {
506        let config = TlsConfig::default();
507
508        let result = TlsManager::load_certificates_sync(&config).unwrap();
509        assert!(!result.root_store.is_empty());
510        // Should have at least one source (system certificates or Mozilla CA bundle)
511        assert!(!result.sources.is_empty());
512        // Common sources include "system certificates" or "Mozilla CA bundle"
513        assert!(
514            result
515                .sources
516                .iter()
517                .any(|s| s.contains("Mozilla") || s.contains("system"))
518        );
519    }
520
521    #[test]
522    fn test_tls_config_builder_chaining() {
523        let config = TlsConfig::builder()
524            .enabled(false)
525            .verify_cert(true)
526            .enabled(true) // Override
527            .build();
528
529        assert!(config.use_tls);
530        assert!(config.tls_verify_cert);
531    }
532
533    #[test]
534    fn test_tls_config_clone() {
535        let config1 = TlsConfig::builder()
536            .enabled(true)
537            .cert_path("/test/path")
538            .build();
539
540        let config2 = config1.clone();
541        assert_eq!(config1.use_tls, config2.use_tls);
542        assert_eq!(config1.tls_verify_cert, config2.tls_verify_cert);
543        assert_eq!(config1.tls_cert_path, config2.tls_cert_path);
544    }
545
546    #[test]
547    fn test_tls_manager_clone() {
548        let config = TlsConfig::default();
549        let manager1 = TlsManager::new(config).unwrap();
550        let manager2 = manager1.clone();
551
552        // Both should share the same Arc<TlsConnector>
553        assert!(Arc::ptr_eq(
554            &manager1.cached_connector,
555            &manager2.cached_connector
556        ));
557    }
558
559    #[test]
560    fn test_tls_manager_debug() {
561        let config = TlsConfig::default();
562        let manager = TlsManager::new(config).unwrap();
563        let debug_str = format!("{:?}", manager);
564
565        assert!(debug_str.contains("TlsManager"));
566        assert!(debug_str.contains("<TlsConnector>"));
567    }
568
569    #[test]
570    fn test_tls_config_builder_cert_path_string_types() {
571        // Test with &str
572        let config1 = TlsConfig::builder().cert_path("/path/to/cert.pem").build();
573        assert_eq!(config1.tls_cert_path, Some("/path/to/cert.pem".to_string()));
574
575        // Test with String
576        let config2 = TlsConfig::builder()
577            .cert_path("/another/path.pem".to_string())
578            .build();
579        assert_eq!(config2.tls_cert_path, Some("/another/path.pem".to_string()));
580    }
581
582    #[test]
583    fn test_no_verifier_supported_schemes() {
584        use rustls_backend::NoVerifier;
585
586        let verifier = NoVerifier;
587        let schemes = verifier.supported_verify_schemes();
588
589        // Should support all major signature schemes
590        assert!(schemes.contains(&SignatureScheme::RSA_PKCS1_SHA256));
591        assert!(schemes.contains(&SignatureScheme::ECDSA_NISTP256_SHA256));
592        assert!(schemes.contains(&SignatureScheme::ED25519));
593        assert!(schemes.len() >= 10); // Should have many schemes
594    }
595
596    #[test]
597    fn test_certificate_load_result_sources() {
598        let config = TlsConfig::default();
599        let result = TlsManager::load_certificates_sync(&config).unwrap();
600
601        // Should have at least one source
602        assert!(!result.sources.is_empty());
603
604        // Sources should be descriptive strings
605        for source in &result.sources {
606            assert!(!source.is_empty());
607        }
608    }
609
610    #[test]
611    fn test_tls_config_builder_defaults_are_secure() {
612        // Builder should default to secure settings
613        let config = TlsConfig::builder().enabled(true).build();
614
615        assert!(config.use_tls);
616        assert!(config.tls_verify_cert); // Verification enabled by default - SECURE
617        assert!(config.tls_cert_path.is_none());
618    }
619
620    #[test]
621    fn test_tls_manager_with_verify_disabled() {
622        let config = TlsConfig::builder()
623            .enabled(true)
624            .verify_cert(false)
625            .build();
626        let manager = TlsManager::new(config);
627
628        // Should successfully create manager even with verification disabled
629        assert!(manager.is_ok());
630    }
631
632    #[test]
633    fn test_tls_manager_with_verify_enabled() {
634        let config = TlsConfig::builder().enabled(true).verify_cert(true).build();
635        let manager = TlsManager::new(config);
636
637        // Should successfully create manager with verification enabled
638        assert!(manager.is_ok());
639    }
640
641    #[test]
642    fn test_certificate_loading_fallback_to_mozilla_bundle() {
643        // Even with empty config, should fall back to Mozilla CA bundle
644        let config = TlsConfig::default();
645        let result = TlsManager::load_certificates_sync(&config).unwrap();
646
647        // Should have loaded certificates from some source
648        assert!(!result.root_store.is_empty());
649        assert!(!result.sources.is_empty());
650    }
651
652    #[test]
653    fn test_tls_config_debug_format() {
654        let config = TlsConfig::builder()
655            .enabled(true)
656            .verify_cert(false)
657            .cert_path("/test")
658            .build();
659
660        let debug_str = format!("{:?}", config);
661
662        assert!(debug_str.contains("TlsConfig"));
663        assert!(debug_str.contains("use_tls"));
664        assert!(debug_str.contains("tls_verify_cert"));
665    }
666
667    #[test]
668    fn test_tls_config_builder_debug_format() {
669        let builder = TlsConfig::builder().enabled(true).verify_cert(false);
670
671        let debug_str = format!("{:?}", builder);
672
673        assert!(debug_str.contains("TlsConfigBuilder"));
674    }
675
676    #[test]
677    fn test_no_verifier_debug_format() {
678        use rustls_backend::NoVerifier;
679
680        let verifier = NoVerifier;
681        let debug_str = format!("{:?}", verifier);
682
683        assert!(debug_str.contains("NoVerifier"));
684    }
685
686    #[test]
687    fn test_certificate_load_result_debug_format() {
688        let config = TlsConfig::default();
689        let result = TlsManager::load_certificates_sync(&config).unwrap();
690
691        let debug_str = format!("{:?}", result);
692
693        assert!(debug_str.contains("CertificateLoadResult"));
694        assert!(debug_str.contains("root_store"));
695        assert!(debug_str.contains("sources"));
696    }
697
698    #[test]
699    fn test_tls_config_builder_cert_path_empty_string() {
700        // Empty string is technically a valid path (current directory)
701        let config = TlsConfig::builder().cert_path("").build();
702
703        assert_eq!(config.tls_cert_path, Some(String::new()));
704    }
705
706    #[test]
707    fn test_tls_config_builder_cert_path_with_spaces() {
708        let config = TlsConfig::builder()
709            .cert_path("  /path/with spaces.pem  ")
710            .build();
711
712        // Should preserve exact string including spaces
713        assert_eq!(
714            config.tls_cert_path,
715            Some("  /path/with spaces.pem  ".to_string())
716        );
717    }
718
719    #[test]
720    fn test_multiple_tls_managers_from_same_config() {
721        let config = TlsConfig::builder()
722            .enabled(true)
723            .verify_cert(false)
724            .build();
725
726        let manager1 = TlsManager::new(config.clone()).unwrap();
727        let manager2 = TlsManager::new(config.clone()).unwrap();
728
729        // Both should successfully initialize
730        let debug1 = format!("{:?}", manager1);
731        let debug2 = format!("{:?}", manager2);
732
733        assert!(debug1.contains("TlsManager"));
734        assert!(debug2.contains("TlsManager"));
735    }
736
737    #[test]
738    fn test_tls_config_all_combinations() {
739        // Test all boolean combinations
740        for use_tls in [true, false] {
741            for verify_cert in [true, false] {
742                let config = TlsConfig::builder()
743                    .enabled(use_tls)
744                    .verify_cert(verify_cert)
745                    .build();
746
747                assert_eq!(config.use_tls, use_tls);
748                assert_eq!(config.tls_verify_cert, verify_cert);
749
750                // All configurations should create valid managers if TLS is enabled
751                if use_tls {
752                    let manager = TlsManager::new(config);
753                    assert!(manager.is_ok());
754                }
755            }
756        }
757    }
758
759    #[test]
760    fn test_tls_config_builder_method_chaining_order() {
761        // Test that builder methods can be called in any order
762        let config1 = TlsConfig::builder()
763            .enabled(true)
764            .verify_cert(false)
765            .cert_path("/test")
766            .build();
767
768        let config2 = TlsConfig::builder()
769            .cert_path("/test")
770            .verify_cert(false)
771            .enabled(true)
772            .build();
773
774        assert_eq!(config1.use_tls, config2.use_tls);
775        assert_eq!(config1.tls_verify_cert, config2.tls_verify_cert);
776        assert_eq!(config1.tls_cert_path, config2.tls_cert_path);
777    }
778
779    #[test]
780    fn test_tls_config_default_matches_builder_default() {
781        let default_config = TlsConfig::default();
782        let builder_config = TlsConfig::builder().build();
783
784        assert_eq!(default_config.use_tls, builder_config.use_tls);
785        assert_eq!(
786            default_config.tls_verify_cert,
787            builder_config.tls_verify_cert
788        );
789        assert_eq!(default_config.tls_cert_path, builder_config.tls_cert_path);
790    }
791}