AWS SDK

AWS SDK

rev. 7b929c2a147df61bb7fa8bb8ba54ebd6829e5397 (ignoring whitespace)

Files changed:

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/src/client/tls.rs

@@ -113,113 +354,149 @@
  133    133   
    fn default() -> Self {
  134    134   
        Self {
  135    135   
            enable_native_roots: true,
  136    136   
            custom_certs: Vec::new(),
  137    137   
        }
  138    138   
    }
  139    139   
}
  140    140   
  141    141   
cfg_rustls! {
  142    142   
    /// rustls based support and adapters
  143         -
    pub mod rustls_provider {
  144         -
        use crate::client::tls::Provider;
  145         -
        use rustls::crypto::CryptoProvider;
  146         -
  147         -
        /// Choice of underlying cryptography library (this only applies to rustls)
  148         -
        #[derive(Debug, Eq, PartialEq, Clone)]
  149         -
        #[non_exhaustive]
  150         -
        pub enum CryptoMode {
  151         -
            /// Crypto based on [ring](https://github.com/briansmith/ring)
  152         -
            #[cfg(feature = "rustls-ring")]
  153         -
            Ring,
  154         -
            /// Crypto based on [aws-lc](https://github.com/aws/aws-lc-rs)
  155         -
            #[cfg(feature = "rustls-aws-lc")]
  156         -
            AwsLc,
  157         -
            /// FIPS compliant variant of [aws-lc](https://github.com/aws/aws-lc-rs)
  158         -
            #[cfg(feature = "rustls-aws-lc-fips")]
  159         -
            AwsLcFips,
  160         -
        }
  161         -
  162         -
        impl CryptoMode {
  163         -
            fn provider(self) -> CryptoProvider {
  164         -
                match self {
  165         -
                    #[cfg(feature = "rustls-aws-lc")]
  166         -
                    CryptoMode::AwsLc => rustls::crypto::aws_lc_rs::default_provider(),
  167         -
  168         -
                    #[cfg(feature = "rustls-ring")]
  169         -
                    CryptoMode::Ring => rustls::crypto::ring::default_provider(),
  170         -
  171         -
                    #[cfg(feature = "rustls-aws-lc-fips")]
  172         -
                    CryptoMode::AwsLcFips => {
  173         -
                        let provider = rustls::crypto::default_fips_provider();
  174         -
                        assert!(
  175         -
                            provider.fips(),
  176         -
                            "FIPS was requested but the provider did not support FIPS"
  177         -
                        );
  178         -
                        provider
  179         -
                    }
  180         -
                }
  181         -
            }
  182         -
        }
  183         -
  184         -
        impl Provider {
  185         -
            /// Create a TLS provider based on [rustls](https://github.com/rustls/rustls)
  186         -
            /// and the given [`CryptoMode`]
  187         -
            pub fn rustls(mode: CryptoMode) -> Provider {
  188         -
                Provider::Rustls(mode)
  189         -
            }
  190         -
        }
  191         -
  192         -
        pub(crate) mod build_connector {
  193         -
            use crate::client::tls::rustls_provider::CryptoMode;
  194         -
            use crate::tls::TlsContext;
  195         -
            use hyper_util::client::legacy as client;
  196         -
            use client::connect::HttpConnector;
  197         -
            use rustls::crypto::CryptoProvider;
  198         -
            use std::sync::Arc;
  199         -
            use rustls_pki_types::CertificateDer;
  200         -
            use rustls_pki_types::pem::PemObject;
  201         -
            use rustls_native_certs::CertificateResult;
  202         -
            use std::sync::LazyLock;
  203         -
  204         -
            /// Cached native certificates
  205         -
            ///
  206         -
            /// Creating a `with_native_roots()` hyper_rustls client re-loads system certs
  207         -
            /// each invocation (which can take 300ms on OSx). Cache the loaded certs
  208         -
            /// to avoid repeatedly incurring that cost.
  209         -
            pub(crate) static NATIVE_ROOTS: LazyLock<Vec<CertificateDer<'static>>> = LazyLock::new(|| {
  210         -
                let CertificateResult { certs, errors, .. } = rustls_native_certs::load_native_certs();
  211         -
                if !errors.is_empty() {
  212         -
                    tracing::warn!("native root CA certificate loading errors: {errors:?}")
  213         -
                }
  214         -
  215         -
                if certs.is_empty() {
  216         -
                    tracing::warn!("no native root CA certificates found!");
  217         -
                }
  218         -
  219         -
                // NOTE: unlike hyper-rustls::with_native_roots we don't validate here, we'll do that later
  220         -
                // for now we have a collection of certs that may or may not be valid.
  221         -
                certs
  222         -
            });
  223         -
  224         -
            fn restrict_ciphers(base: CryptoProvider) -> CryptoProvider {
  225         -
                let suites = &[
  226         -
                    rustls::CipherSuite::TLS13_AES_256_GCM_SHA384,
  227         -
                    rustls::CipherSuite::TLS13_AES_128_GCM_SHA256,
  228         -
                    // TLS1.2 suites
  229         -
                    rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
  230         -
                    rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
  231         -
                    rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
  232         -
                    rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
  233         -
                    rustls::CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
  234         -
                ];
  235         -
                let supported_suites = suites
  236         -
                    .iter()
  237         -
                    .flat_map(|suite| {
  238         -
                        base.cipher_suites
  239         -
                            .iter()
  240         -
                            .find(|s| &s.suite() == suite)
  241         -
                            .cloned()
  242         -
                    })
  243         -
                    .collect::<Vec<_>>();
  244         -
                CryptoProvider {
  245         -
                    cipher_suites: supported_suites,
  246         -
                    ..base
  247         -
                }
  248         -
            }
  249         -
  250         -
            impl TlsContext {
  251         -
                fn rustls_root_certs(&self) -> rustls::RootCertStore {
  252         -
                    let mut roots = rustls::RootCertStore::empty();
  253         -
                    if self.trust_store.enable_native_roots {
  254         -
                        let (valid, _invalid) = roots.add_parsable_certificates(
  255         -
                           NATIVE_ROOTS.clone()
  256         -
                        );
  257         -
                        debug_assert!(valid > 0, "TrustStore configured to enable native roots but no valid root certificates parsed!");
  258         -
                    }
  259         -
  260         -
                    for pem_cert in &self.trust_store.custom_certs {
  261         -
                        let ders = CertificateDer::pem_slice_iter(&pem_cert.0).collect::<Result<Vec<_>, _> >().expect("valid PEM certificate");
  262         -
                        for cert in ders {
  263         -
                            roots.add(cert).expect("cert parsable")
  264         -
                        }
  265         -
                    }
  266         -
  267         -
                    roots
  268         -
                }
  269         -
            }
  270         -
  271         -
            pub(crate) fn wrap_connector<R>(
  272         -
                mut conn: HttpConnector<R>,
  273         -
                crypto_mode: CryptoMode,
  274         -
                tls_context: &TlsContext,
  275         -
            ) -> hyper_rustls::HttpsConnector<HttpConnector<R>> {
  276         -
                let root_certs = tls_context.rustls_root_certs();
  277         -
                conn.enforce_http(false);
  278         -
                hyper_rustls::HttpsConnectorBuilder::new()
  279         -
                    .with_tls_config(
  280         -
                        rustls::ClientConfig::builder_with_provider(Arc::new(restrict_ciphers(crypto_mode.provider())))
  281         -
                            .with_safe_default_protocol_versions()
  282         -
                            .expect("Error with the TLS configuration. Please file a bug report under https://github.com/smithy-lang/smithy-rs/issues.")
  283         -
                            .with_root_certificates(root_certs)
  284         -
                            .with_no_client_auth()
  285         -
                    )
  286         -
                    .https_or_http()
  287         -
                    .enable_http1()
  288         -
                    .enable_http2()
  289         -
                    .wrap_connector(conn)
  290         -
            }
  291         -
        }
  292         -
    }
         143  +
    pub mod rustls_provider;
  293    144   
}
  294    145   
  295    146   
cfg_s2n_tls! {
  296    147   
    /// s2n-tls based support and adapters
  297         -
    pub(crate) mod s2n_tls_provider {
  298         -
        pub(crate) mod build_connector {
  299         -
            use hyper_util::client::legacy as client;
  300         -
            use client::connect::HttpConnector;
  301         -
            use s2n_tls::security::Policy;
  302         -
            use crate::tls::TlsContext;
  303         -
            use std::sync::LazyLock;
  304         -
  305         -
            // Default S2N security policy which sets protocol versions and cipher suites
  306         -
            //  See https://aws.github.io/s2n-tls/usage-guide/ch06-security-policies.html
  307         -
            const S2N_POLICY_VERSION: &str = "20230317";
  308         -
  309         -
            fn base_config() -> s2n_tls::config::Builder {
  310         -
                let mut builder = s2n_tls::config::Config::builder();
  311         -
                let policy = Policy::from_version(S2N_POLICY_VERSION).unwrap();
  312         -
                builder.set_security_policy(&policy).expect("valid s2n security policy");
  313         -
                // default is true
  314         -
                builder.with_system_certs(false).unwrap();
  315         -
                builder
  316         -
            }
  317         -
  318         -
            static CACHED_CONFIG: LazyLock<s2n_tls::config::Config> = LazyLock::new(|| {
  319         -
                let mut config = base_config();
  320         -
                config.with_system_certs(true).unwrap();
  321         -
                // actually loads the system certs
  322         -
                config.build().expect("valid s2n config")
  323         -
            });
  324         -
  325         -
            impl TlsContext {
  326         -
                fn s2n_config(&self) -> s2n_tls::config::Config {
  327         -
                    // TODO(s2n-tls): s2n does not support turning a config back into a builder or a way to load a trust store and re-use it
  328         -
                    // instead if we are only using the defaults then use a cached config, otherwise pay the cost to build a new one
  329         -
                    if self.trust_store.enable_native_roots && self.trust_store.custom_certs.is_empty() {
  330         -
                        CACHED_CONFIG.clone()
  331         -
                    } else {
  332         -
                        let mut config = base_config();
  333         -
                        config.with_system_certs(self.trust_store.enable_native_roots).unwrap();
  334         -
                        for pem_cert in &self.trust_store.custom_certs {
  335         -
                            config.trust_pem(pem_cert.0.as_slice()).expect("valid certificate");
  336         -
                        }
  337         -
                        config.build().expect("valid s2n config")
  338         -
                    }
  339         -
                }
  340         -
            }
  341         -
  342         -
            pub(crate) fn wrap_connector<R>(
  343         -
                mut http_connector: HttpConnector<R>,
  344         -
                tls_context: &TlsContext,
  345         -
            ) -> s2n_tls_hyper::connector::HttpsConnector<HttpConnector<R>> {
  346         -
                let config = tls_context.s2n_config();
  347         -
                http_connector.enforce_http(false);
  348         -
                let mut builder = s2n_tls_hyper::connector::HttpsConnector::builder_with_http(http_connector, config);
  349         -
                builder.with_plaintext_http(true);
  350         -
                builder.build()
  351         -
            }
  352         -
        }
  353         -
    }
         148  +
    pub(crate) mod s2n_tls_provider;
  354    149   
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/src/client/tls/rustls_provider.rs

@@ -0,1 +0,437 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
use crate::client::tls::Provider;
           6  +
use rustls::crypto::CryptoProvider;
           7  +
           8  +
/// Choice of underlying cryptography library (this only applies to rustls)
           9  +
#[derive(Debug, Eq, PartialEq, Clone)]
          10  +
#[non_exhaustive]
          11  +
pub enum CryptoMode {
          12  +
    /// Crypto based on [ring](https://github.com/briansmith/ring)
          13  +
    #[cfg(feature = "rustls-ring")]
          14  +
    Ring,
          15  +
    /// Crypto based on [aws-lc](https://github.com/aws/aws-lc-rs)
          16  +
    #[cfg(feature = "rustls-aws-lc")]
          17  +
    AwsLc,
          18  +
    /// FIPS compliant variant of [aws-lc](https://github.com/aws/aws-lc-rs)
          19  +
    #[cfg(feature = "rustls-aws-lc-fips")]
          20  +
    AwsLcFips,
          21  +
}
          22  +
          23  +
impl CryptoMode {
          24  +
    fn provider(self) -> CryptoProvider {
          25  +
        match self {
          26  +
            #[cfg(feature = "rustls-aws-lc")]
          27  +
            CryptoMode::AwsLc => rustls::crypto::aws_lc_rs::default_provider(),
          28  +
          29  +
            #[cfg(feature = "rustls-ring")]
          30  +
            CryptoMode::Ring => rustls::crypto::ring::default_provider(),
          31  +
          32  +
            #[cfg(feature = "rustls-aws-lc-fips")]
          33  +
            CryptoMode::AwsLcFips => {
          34  +
                let provider = rustls::crypto::default_fips_provider();
          35  +
                assert!(
          36  +
                    provider.fips(),
          37  +
                    "FIPS was requested but the provider did not support FIPS"
          38  +
                );
          39  +
                provider
          40  +
            }
          41  +
        }
          42  +
    }
          43  +
}
          44  +
          45  +
impl Provider {
          46  +
    /// Create a TLS provider based on [rustls](https://github.com/rustls/rustls)
          47  +
    /// and the given [`CryptoMode`]
          48  +
    pub fn rustls(mode: CryptoMode) -> Provider {
          49  +
        Provider::Rustls(mode)
          50  +
    }
          51  +
}
          52  +
          53  +
pub(crate) mod build_connector {
          54  +
    use crate::client::tls::rustls_provider::CryptoMode;
          55  +
    use crate::tls::TlsContext;
          56  +
    use client::connect::HttpConnector;
          57  +
    use hyper_util::client::legacy as client;
          58  +
    use rustls::crypto::CryptoProvider;
          59  +
    use rustls_native_certs::CertificateResult;
          60  +
    use rustls_pki_types::pem::PemObject;
          61  +
    use rustls_pki_types::CertificateDer;
          62  +
    use std::sync::Arc;
          63  +
    use std::sync::LazyLock;
          64  +
          65  +
    /// Cached native certificates
          66  +
    ///
          67  +
    /// Creating a `with_native_roots()` hyper_rustls client re-loads system certs
          68  +
    /// each invocation (which can take 300ms on OSx). Cache the loaded certs
          69  +
    /// to avoid repeatedly incurring that cost.
          70  +
    pub(crate) static NATIVE_ROOTS: LazyLock<Vec<CertificateDer<'static>>> = LazyLock::new(|| {
          71  +
        let CertificateResult { certs, errors, .. } = rustls_native_certs::load_native_certs();
          72  +
        if !errors.is_empty() {
          73  +
            tracing::warn!("native root CA certificate loading errors: {errors:?}")
          74  +
        }
          75  +
          76  +
        if certs.is_empty() {
          77  +
            tracing::warn!("no native root CA certificates found!");
          78  +
        }
          79  +
          80  +
        // NOTE: unlike hyper-rustls::with_native_roots we don't validate here, we'll do that later
          81  +
        // for now we have a collection of certs that may or may not be valid.
          82  +
        certs
          83  +
    });
          84  +
          85  +
    pub(crate) fn restrict_ciphers(base: CryptoProvider) -> CryptoProvider {
          86  +
        let suites = &[
          87  +
            rustls::CipherSuite::TLS13_AES_256_GCM_SHA384,
          88  +
            rustls::CipherSuite::TLS13_AES_128_GCM_SHA256,
          89  +
            // TLS1.2 suites
          90  +
            rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
          91  +
            rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
          92  +
            rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
          93  +
            rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
          94  +
            rustls::CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
          95  +
        ];
          96  +
        let supported_suites = suites
          97  +
            .iter()
          98  +
            .flat_map(|suite| {
          99  +
                base.cipher_suites
         100  +
                    .iter()
         101  +
                    .find(|s| &s.suite() == suite)
         102  +
                    .cloned()
         103  +
            })
         104  +
            .collect::<Vec<_>>();
         105  +
        CryptoProvider {
         106  +
            cipher_suites: supported_suites,
         107  +
            ..base
         108  +
        }
         109  +
    }
         110  +
         111  +
    impl TlsContext {
         112  +
        pub(crate) fn rustls_root_certs(&self) -> rustls::RootCertStore {
         113  +
            let mut roots = rustls::RootCertStore::empty();
         114  +
            if self.trust_store.enable_native_roots {
         115  +
                let (valid, _invalid) = roots.add_parsable_certificates(NATIVE_ROOTS.clone());
         116  +
                debug_assert!(valid > 0, "TrustStore configured to enable native roots but no valid root certificates parsed!");
         117  +
            }
         118  +
         119  +
            for pem_cert in &self.trust_store.custom_certs {
         120  +
                let ders = CertificateDer::pem_slice_iter(&pem_cert.0)
         121  +
                    .collect::<Result<Vec<_>, _>>()
         122  +
                    .expect("valid PEM certificate");
         123  +
                for cert in ders {
         124  +
                    roots.add(cert).expect("cert parsable")
         125  +
                }
         126  +
            }
         127  +
         128  +
            roots
         129  +
        }
         130  +
    }
         131  +
         132  +
    /// Create a rustls ClientConfig with smithy-rs defaults
         133  +
    ///
         134  +
    /// This centralizes the rustls ClientConfig creation logic to ensure
         135  +
    /// consistency between the main HTTPS connector and tunnel handlers.
         136  +
    pub(crate) fn create_rustls_client_config(
         137  +
        crypto_mode: CryptoMode,
         138  +
        tls_context: &TlsContext,
         139  +
    ) -> rustls::ClientConfig {
         140  +
        let root_certs = tls_context.rustls_root_certs();
         141  +
        rustls::ClientConfig::builder_with_provider(Arc::new(restrict_ciphers(crypto_mode.provider())))
         142  +
            .with_safe_default_protocol_versions()
         143  +
            .expect("Error with the TLS configuration. Please file a bug report under https://github.com/smithy-lang/smithy-rs/issues.")
         144  +
            .with_root_certificates(root_certs)
         145  +
            .with_no_client_auth()
         146  +
    }
         147  +
         148  +
    pub(crate) fn wrap_connector<R>(
         149  +
        mut conn: HttpConnector<R>,
         150  +
        crypto_mode: CryptoMode,
         151  +
        tls_context: &TlsContext,
         152  +
        proxy_config: crate::client::proxy::ProxyConfig,
         153  +
    ) -> super::connect::RustTlsConnector<R> {
         154  +
        let client_config = create_rustls_client_config(crypto_mode, tls_context);
         155  +
        conn.enforce_http(false);
         156  +
        let https_connector = hyper_rustls::HttpsConnectorBuilder::new()
         157  +
            .with_tls_config(client_config.clone())
         158  +
            .https_or_http()
         159  +
            .enable_http1()
         160  +
            .enable_http2()
         161  +
            .wrap_connector(conn);
         162  +
         163  +
        super::connect::RustTlsConnector::new(https_connector, client_config, proxy_config)
         164  +
    }
         165  +
}
         166  +
         167  +
pub(crate) mod connect {
         168  +
    use crate::client::connect::{Conn, Connecting};
         169  +
    use crate::client::proxy::ProxyConfig;
         170  +
    use aws_smithy_runtime_api::box_error::BoxError;
         171  +
    use http_1x::uri::Scheme;
         172  +
    use http_1x::Uri;
         173  +
    use hyper::rt::{Read, ReadBufCursor, Write};
         174  +
    use hyper_rustls::MaybeHttpsStream;
         175  +
    use hyper_util::client::legacy::connect::{Connected, Connection, HttpConnector};
         176  +
    use hyper_util::client::proxy::matcher::Matcher;
         177  +
    use hyper_util::rt::TokioIo;
         178  +
    use pin_project_lite::pin_project;
         179  +
    use std::error::Error;
         180  +
    use std::sync::Arc;
         181  +
    use std::{
         182  +
        io::{self, IoSlice},
         183  +
        pin::Pin,
         184  +
        task::{Context, Poll},
         185  +
    };
         186  +
    use tokio::io::{AsyncRead, AsyncWrite};
         187  +
    use tokio::net::TcpStream;
         188  +
    use tokio_rustls::client::TlsStream;
         189  +
    use tower::Service;
         190  +
         191  +
    #[derive(Debug, Clone)]
         192  +
    pub(crate) struct RustTlsConnector<R> {
         193  +
        https: hyper_rustls::HttpsConnector<HttpConnector<R>>,
         194  +
        tls_config: Arc<rustls::ClientConfig>,
         195  +
        proxy_matcher: Option<Arc<Matcher>>, // Pre-computed for performance
         196  +
    }
         197  +
         198  +
    impl<R> RustTlsConnector<R> {
         199  +
        pub(super) fn new(
         200  +
            https: hyper_rustls::HttpsConnector<HttpConnector<R>>,
         201  +
            tls_config: rustls::ClientConfig,
         202  +
            proxy_config: ProxyConfig,
         203  +
        ) -> Self {
         204  +
            // Pre-compute the proxy matcher once during construction
         205  +
            let proxy_matcher = if proxy_config.is_disabled() {
         206  +
                None
         207  +
            } else {
         208  +
                Some(Arc::new(proxy_config.into_hyper_util_matcher()))
         209  +
            };
         210  +
         211  +
            Self {
         212  +
                https,
         213  +
                tls_config: Arc::new(tls_config),
         214  +
                proxy_matcher,
         215  +
            }
         216  +
        }
         217  +
    }
         218  +
         219  +
    impl<R> Service<Uri> for RustTlsConnector<R>
         220  +
    where
         221  +
        R: Clone + Send + Sync + 'static,
         222  +
        R: Service<hyper_util::client::legacy::connect::dns::Name>,
         223  +
        R::Response: Iterator<Item = std::net::SocketAddr>,
         224  +
        R::Future: Send,
         225  +
        R::Error: Into<Box<dyn Error + Send + Sync>>,
         226  +
    {
         227  +
        type Response = Conn;
         228  +
        type Error = BoxError;
         229  +
        type Future = Connecting;
         230  +
         231  +
        fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
         232  +
            self.https.poll_ready(cx).map_err(Into::into)
         233  +
        }
         234  +
         235  +
        fn call(&mut self, dst: Uri) -> Self::Future {
         236  +
            // Check if this request should be proxied using pre-computed matcher
         237  +
            let proxy_intercept = if let Some(ref matcher) = self.proxy_matcher {
         238  +
                matcher.intercept(&dst)
         239  +
            } else {
         240  +
                None
         241  +
            };
         242  +
         243  +
            if let Some(intercept) = proxy_intercept {
         244  +
                if dst.scheme() == Some(&Scheme::HTTPS) {
         245  +
                    // HTTPS through HTTP proxy: Use CONNECT tunneling + manual TLS
         246  +
                    self.handle_https_through_proxy(dst, intercept)
         247  +
                } else {
         248  +
                    // HTTP through proxy: Direct connection to proxy
         249  +
                    self.handle_http_through_proxy(dst, intercept)
         250  +
                }
         251  +
            } else {
         252  +
                // Direct connection: Use the existing HTTPS connector
         253  +
                self.handle_direct_connection(dst)
         254  +
            }
         255  +
        }
         256  +
    }
         257  +
         258  +
    impl<R> RustTlsConnector<R>
         259  +
    where
         260  +
        R: Clone + Send + Sync + 'static,
         261  +
        R: Service<hyper_util::client::legacy::connect::dns::Name>,
         262  +
        R::Response: Iterator<Item = std::net::SocketAddr>,
         263  +
        R::Future: Send,
         264  +
        R::Error: Into<Box<dyn Error + Send + Sync>>,
         265  +
    {
         266  +
        fn handle_direct_connection(&mut self, dst: Uri) -> Connecting {
         267  +
            let fut = self.https.call(dst);
         268  +
            Box::pin(async move {
         269  +
                let conn = fut.await?;
         270  +
                Ok(Conn {
         271  +
                    inner: Box::new(conn),
         272  +
                    is_proxy: false,
         273  +
                })
         274  +
            })
         275  +
        }
         276  +
         277  +
        fn handle_http_through_proxy(
         278  +
            &mut self,
         279  +
            _dst: Uri,
         280  +
            intercept: hyper_util::client::proxy::matcher::Intercept,
         281  +
        ) -> Connecting {
         282  +
            // For HTTP through proxy, connect to the proxy and let it handle the request
         283  +
            let proxy_uri = intercept.uri().clone();
         284  +
            let fut = self.https.call(proxy_uri);
         285  +
            Box::pin(async move {
         286  +
                let conn = fut.await?;
         287  +
                Ok(Conn {
         288  +
                    inner: Box::new(conn),
         289  +
                    is_proxy: true,
         290  +
                })
         291  +
            })
         292  +
        }
         293  +
         294  +
        fn handle_https_through_proxy(
         295  +
            &mut self,
         296  +
            dst: Uri,
         297  +
            intercept: hyper_util::client::proxy::matcher::Intercept,
         298  +
        ) -> Connecting {
         299  +
            use rustls_pki_types::ServerName;
         300  +
            // For HTTPS through HTTP proxy, we need to:
         301  +
            // 1. Establish CONNECT tunnel using the HTTPS connector
         302  +
            // 2. Perform manual TLS handshake over the tunneled stream
         303  +
         304  +
            let tunnel = hyper_util::client::legacy::connect::proxy::Tunnel::new(
         305  +
                intercept.uri().clone(),
         306  +
                self.https.clone(),
         307  +
            );
         308  +
         309  +
            // Configure tunnel with authentication if present
         310  +
            let mut tunnel = if let Some(auth) = intercept.basic_auth() {
         311  +
                tunnel.with_auth(auth.clone())
         312  +
            } else {
         313  +
                tunnel
         314  +
            };
         315  +
         316  +
            let tls_config = self.tls_config.clone();
         317  +
            let dst_clone = dst.clone();
         318  +
         319  +
            Box::pin(async move {
         320  +
                // Establish CONNECT tunnel
         321  +
                tracing::trace!("tunneling HTTPS over proxy");
         322  +
                let tunneled = tunnel
         323  +
                    .call(dst_clone.clone())
         324  +
                    .await
         325  +
                    .map_err(|e| BoxError::from(format!("CONNECT tunnel failed: {}", e)))?;
         326  +
         327  +
                // Stage 2: Manual TLS handshake over tunneled stream
         328  +
                let host = dst_clone
         329  +
                    .host()
         330  +
                    .ok_or("missing host in URI for TLS handshake")?;
         331  +
         332  +
                let server_name = ServerName::try_from(host.to_owned()).map_err(|e| {
         333  +
                    BoxError::from(format!("invalid server name for TLS handshake: {}", e))
         334  +
                })?;
         335  +
         336  +
                let tls_connector = tokio_rustls::TlsConnector::from(tls_config)
         337  +
                    .connect(server_name, TokioIo::new(tunneled))
         338  +
                    .await?;
         339  +
         340  +
                Ok(Conn {
         341  +
                    inner: Box::new(RustTlsConn {
         342  +
                        inner: TokioIo::new(tls_connector),
         343  +
                    }),
         344  +
                    is_proxy: true,
         345  +
                })
         346  +
            })
         347  +
        }
         348  +
    }
         349  +
         350  +
    pin_project! {
         351  +
        pub(crate) struct RustTlsConn<T> {
         352  +
            #[pin] pub(super) inner: TokioIo<TlsStream<T>>
         353  +
        }
         354  +
    }
         355  +
         356  +
    impl Connection for RustTlsConn<TokioIo<TokioIo<TcpStream>>> {
         357  +
        fn connected(&self) -> Connected {
         358  +
            if self.inner.inner().get_ref().1.alpn_protocol() == Some(b"h2") {
         359  +
                self.inner
         360  +
                    .inner()
         361  +
                    .get_ref()
         362  +
                    .0
         363  +
                    .inner()
         364  +
                    .connected()
         365  +
                    .negotiated_h2()
         366  +
            } else {
         367  +
                self.inner.inner().get_ref().0.inner().connected()
         368  +
            }
         369  +
        }
         370  +
    }
         371  +
         372  +
    impl Connection for RustTlsConn<TokioIo<MaybeHttpsStream<TokioIo<TcpStream>>>> {
         373  +
        fn connected(&self) -> Connected {
         374  +
            if self.inner.inner().get_ref().1.alpn_protocol() == Some(b"h2") {
         375  +
                self.inner
         376  +
                    .inner()
         377  +
                    .get_ref()
         378  +
                    .0
         379  +
                    .inner()
         380  +
                    .connected()
         381  +
                    .negotiated_h2()
         382  +
            } else {
         383  +
                self.inner.inner().get_ref().0.inner().connected()
         384  +
            }
         385  +
        }
         386  +
    }
         387  +
    impl<T: AsyncRead + AsyncWrite + Unpin> Read for RustTlsConn<T> {
         388  +
        fn poll_read(
         389  +
            self: Pin<&mut Self>,
         390  +
            cx: &mut Context<'_>,
         391  +
            buf: ReadBufCursor<'_>,
         392  +
        ) -> Poll<tokio::io::Result<()>> {
         393  +
            let this = self.project();
         394  +
            Read::poll_read(this.inner, cx, buf)
         395  +
        }
         396  +
    }
         397  +
         398  +
    impl<T: AsyncRead + AsyncWrite + Unpin> Write for RustTlsConn<T> {
         399  +
        fn poll_write(
         400  +
            self: Pin<&mut Self>,
         401  +
            cx: &mut Context<'_>,
         402  +
            buf: &[u8],
         403  +
        ) -> Poll<Result<usize, tokio::io::Error>> {
         404  +
            let this = self.project();
         405  +
            Write::poll_write(this.inner, cx, buf)
         406  +
        }
         407  +
         408  +
        fn poll_write_vectored(
         409  +
            self: Pin<&mut Self>,
         410  +
            cx: &mut Context<'_>,
         411  +
            bufs: &[IoSlice<'_>],
         412  +
        ) -> Poll<Result<usize, io::Error>> {
         413  +
            let this = self.project();
         414  +
            Write::poll_write_vectored(this.inner, cx, bufs)
         415  +
        }
         416  +
         417  +
        fn is_write_vectored(&self) -> bool {
         418  +
            self.inner.is_write_vectored()
         419  +
        }
         420  +
         421  +
        fn poll_flush(
         422  +
            self: Pin<&mut Self>,
         423  +
            cx: &mut Context<'_>,
         424  +
        ) -> Poll<Result<(), tokio::io::Error>> {
         425  +
            let this = self.project();
         426  +
            Write::poll_flush(this.inner, cx)
         427  +
        }
         428  +
         429  +
        fn poll_shutdown(
         430  +
            self: Pin<&mut Self>,
         431  +
            cx: &mut Context<'_>,
         432  +
        ) -> Poll<Result<(), tokio::io::Error>> {
         433  +
            let this = self.project();
         434  +
            Write::poll_shutdown(this.inner, cx)
         435  +
        }
         436  +
    }
         437  +
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/src/client/tls/s2n_tls_provider.rs

@@ -0,1 +0,319 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
           6  +
pub(crate) mod build_connector {
           7  +
    use crate::tls::TlsContext;
           8  +
    use client::connect::HttpConnector;
           9  +
    use hyper_util::client::legacy as client;
          10  +
    use s2n_tls::security::Policy;
          11  +
    use std::sync::LazyLock;
          12  +
          13  +
    // Default S2N security policy which sets protocol versions and cipher suites
          14  +
    //  See https://aws.github.io/s2n-tls/usage-guide/ch06-security-policies.html
          15  +
    const S2N_POLICY_VERSION: &str = "20230317";
          16  +
          17  +
    fn base_config() -> s2n_tls::config::Builder {
          18  +
        let mut builder = s2n_tls::config::Config::builder();
          19  +
        let policy = Policy::from_version(S2N_POLICY_VERSION).unwrap();
          20  +
        builder
          21  +
            .set_security_policy(&policy)
          22  +
            .expect("valid s2n security policy");
          23  +
        // default is true
          24  +
        builder.with_system_certs(false).unwrap();
          25  +
        builder
          26  +
    }
          27  +
          28  +
    static CACHED_CONFIG: LazyLock<s2n_tls::config::Config> = LazyLock::new(|| {
          29  +
        let mut config = base_config();
          30  +
        config.with_system_certs(true).unwrap();
          31  +
        // actually loads the system certs
          32  +
        config.build().expect("valid s2n config")
          33  +
    });
          34  +
          35  +
    impl TlsContext {
          36  +
        fn s2n_config(&self) -> s2n_tls::config::Config {
          37  +
            // TODO(s2n-tls): s2n does not support turning a config back into a builder or a way to load a trust store and re-use it
          38  +
            // instead if we are only using the defaults then use a cached config, otherwise pay the cost to build a new one
          39  +
            if self.trust_store.enable_native_roots && self.trust_store.custom_certs.is_empty() {
          40  +
                CACHED_CONFIG.clone()
          41  +
            } else {
          42  +
                let mut config = base_config();
          43  +
                config
          44  +
                    .with_system_certs(self.trust_store.enable_native_roots)
          45  +
                    .unwrap();
          46  +
                for pem_cert in &self.trust_store.custom_certs {
          47  +
                    config
          48  +
                        .trust_pem(pem_cert.0.as_slice())
          49  +
                        .expect("valid certificate");
          50  +
                }
          51  +
                config.build().expect("valid s2n config")
          52  +
            }
          53  +
        }
          54  +
    }
          55  +
          56  +
    pub(crate) fn wrap_connector<R>(
          57  +
        mut http_connector: HttpConnector<R>,
          58  +
        tls_context: &TlsContext,
          59  +
        proxy_config: crate::client::proxy::ProxyConfig,
          60  +
    ) -> super::connect::S2nTlsConnector<R> {
          61  +
        let config = tls_context.s2n_config();
          62  +
        http_connector.enforce_http(false);
          63  +
        let mut builder = s2n_tls_hyper::connector::HttpsConnector::builder_with_http(
          64  +
            http_connector,
          65  +
            config.clone(),
          66  +
        );
          67  +
        builder.with_plaintext_http(true);
          68  +
        let https_connector = builder.build();
          69  +
          70  +
        super::connect::S2nTlsConnector::new(https_connector, config, proxy_config)
          71  +
    }
          72  +
}
          73  +
          74  +
pub(crate) mod connect {
          75  +
    use crate::client::connect::{Conn, Connecting};
          76  +
    use crate::client::proxy::ProxyConfig;
          77  +
    use aws_smithy_runtime_api::box_error::BoxError;
          78  +
    use http_1x::uri::Scheme;
          79  +
    use http_1x::Uri;
          80  +
    use hyper_util::client::legacy::connect::{Connected, Connection, HttpConnector};
          81  +
    use hyper_util::client::proxy::matcher::Matcher;
          82  +
    use hyper_util::rt::TokioIo;
          83  +
    use std::error::Error;
          84  +
    use std::sync::Arc;
          85  +
    use std::{
          86  +
        io::IoSlice,
          87  +
        pin::Pin,
          88  +
        task::{Context, Poll},
          89  +
    };
          90  +
    use tower::Service;
          91  +
          92  +
    #[derive(Clone)]
          93  +
    pub(crate) struct S2nTlsConnector<R> {
          94  +
        https: s2n_tls_hyper::connector::HttpsConnector<HttpConnector<R>>,
          95  +
        tls_config: s2n_tls::config::Config,
          96  +
        proxy_matcher: Option<Arc<Matcher>>, // Pre-computed for performance
          97  +
    }
          98  +
          99  +
    impl<R> S2nTlsConnector<R> {
         100  +
        pub(super) fn new(
         101  +
            https: s2n_tls_hyper::connector::HttpsConnector<HttpConnector<R>>,
         102  +
            tls_config: s2n_tls::config::Config,
         103  +
            proxy_config: ProxyConfig,
         104  +
        ) -> Self {
         105  +
            // Pre-compute the proxy matcher once during construction
         106  +
            let proxy_matcher = if proxy_config.is_disabled() {
         107  +
                None
         108  +
            } else {
         109  +
                Some(Arc::new(proxy_config.into_hyper_util_matcher()))
         110  +
            };
         111  +
         112  +
            Self {
         113  +
                https,
         114  +
                tls_config,
         115  +
                proxy_matcher,
         116  +
            }
         117  +
        }
         118  +
    }
         119  +
         120  +
    impl<R> Service<Uri> for S2nTlsConnector<R>
         121  +
    where
         122  +
        R: Clone + Send + Sync + 'static,
         123  +
        R: Service<hyper_util::client::legacy::connect::dns::Name>,
         124  +
        R::Response: Iterator<Item = std::net::SocketAddr>,
         125  +
        R::Future: Send,
         126  +
        R::Error: Into<Box<dyn Error + Send + Sync>>,
         127  +
    {
         128  +
        type Response = Conn;
         129  +
        type Error = BoxError;
         130  +
        type Future = Connecting;
         131  +
         132  +
        fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
         133  +
            self.https.poll_ready(cx).map_err(Into::into)
         134  +
        }
         135  +
         136  +
        fn call(&mut self, dst: Uri) -> Self::Future {
         137  +
            // Check if this request should be proxied using pre-computed matcher
         138  +
            let proxy_intercept = if let Some(ref matcher) = self.proxy_matcher {
         139  +
                matcher.intercept(&dst)
         140  +
            } else {
         141  +
                None
         142  +
            };
         143  +
         144  +
            if let Some(intercept) = proxy_intercept {
         145  +
                if dst.scheme() == Some(&Scheme::HTTPS) {
         146  +
                    // HTTPS through HTTP proxy: Use CONNECT tunneling + manual TLS
         147  +
                    self.handle_https_through_proxy(dst, intercept)
         148  +
                } else {
         149  +
                    // HTTP through proxy: Direct connection to proxy
         150  +
                    self.handle_http_through_proxy(dst, intercept)
         151  +
                }
         152  +
            } else {
         153  +
                // Direct connection: Use the existing HTTPS connector
         154  +
                self.handle_direct_connection(dst)
         155  +
            }
         156  +
        }
         157  +
    }
         158  +
         159  +
    impl<R> S2nTlsConnector<R>
         160  +
    where
         161  +
        R: Clone + Send + Sync + 'static,
         162  +
        R: Service<hyper_util::client::legacy::connect::dns::Name>,
         163  +
        R::Response: Iterator<Item = std::net::SocketAddr>,
         164  +
        R::Future: Send,
         165  +
        R::Error: Into<Box<dyn Error + Send + Sync>>,
         166  +
    {
         167  +
        fn handle_direct_connection(&mut self, dst: Uri) -> Connecting {
         168  +
            let fut = self.https.call(dst);
         169  +
            Box::pin(async move {
         170  +
                let conn = fut.await?;
         171  +
                Ok(Conn {
         172  +
                    inner: Box::new(conn),
         173  +
                    is_proxy: false,
         174  +
                })
         175  +
            })
         176  +
        }
         177  +
         178  +
        fn handle_http_through_proxy(
         179  +
            &mut self,
         180  +
            _dst: Uri,
         181  +
            intercept: hyper_util::client::proxy::matcher::Intercept,
         182  +
        ) -> Connecting {
         183  +
            // For HTTP through proxy, connect to the proxy and let it handle the request
         184  +
            let proxy_uri = intercept.uri().clone();
         185  +
            let fut = self.https.call(proxy_uri);
         186  +
            Box::pin(async move {
         187  +
                let conn = fut.await?;
         188  +
                Ok(Conn {
         189  +
                    inner: Box::new(conn),
         190  +
                    is_proxy: true,
         191  +
                })
         192  +
            })
         193  +
        }
         194  +
         195  +
        fn handle_https_through_proxy(
         196  +
            &mut self,
         197  +
            dst: Uri,
         198  +
            intercept: hyper_util::client::proxy::matcher::Intercept,
         199  +
        ) -> Connecting {
         200  +
            // For HTTPS through HTTP proxy, we need to:
         201  +
            // 1. Establish CONNECT tunnel using the HTTPS connector
         202  +
            // 2. Perform manual TLS handshake over the tunneled stream
         203  +
         204  +
            let tunnel = hyper_util::client::legacy::connect::proxy::Tunnel::new(
         205  +
                intercept.uri().clone(),
         206  +
                self.https.clone(),
         207  +
            );
         208  +
         209  +
            // Configure tunnel with authentication if present
         210  +
            let mut tunnel = if let Some(auth) = intercept.basic_auth() {
         211  +
                tunnel.with_auth(auth.clone())
         212  +
            } else {
         213  +
                tunnel
         214  +
            };
         215  +
         216  +
            let tls_config = self.tls_config.clone();
         217  +
            let dst_clone = dst.clone();
         218  +
         219  +
            Box::pin(async move {
         220  +
                // Stage 1: Establish CONNECT tunnel
         221  +
                tracing::trace!("tunneling HTTPS over proxy using s2n-tls");
         222  +
                let tunneled = tunnel
         223  +
                    .call(dst_clone.clone())
         224  +
                    .await
         225  +
                    .map_err(|e| BoxError::from(format!("CONNECT tunnel failed: {}", e)))?;
         226  +
         227  +
                // Stage 2: Manual TLS handshake over tunneled stream
         228  +
                let host = dst_clone
         229  +
                    .host()
         230  +
                    .ok_or("missing host in URI for TLS handshake")?;
         231  +
         232  +
                // s2n-tls uses string server names (simpler than rustls ServerName)
         233  +
                let tls_connector = s2n_tls_tokio::TlsConnector::new(tls_config);
         234  +
                let tls_stream = tls_connector
         235  +
                    .connect(host, TokioIo::new(tunneled))
         236  +
                    .await
         237  +
                    .map_err(|e| BoxError::from(format!("s2n-tls handshake failed: {}", e)))?;
         238  +
         239  +
                Ok(Conn {
         240  +
                    inner: Box::new(S2nTlsConn {
         241  +
                        inner: TokioIo::new(tls_stream),
         242  +
                    }),
         243  +
                    is_proxy: true,
         244  +
                })
         245  +
            })
         246  +
        }
         247  +
    }
         248  +
         249  +
    // Simple wrapper that implements Connection for s2n-tls streams
         250  +
    struct S2nTlsConn<T>
         251  +
    where
         252  +
        T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
         253  +
    {
         254  +
        inner: TokioIo<s2n_tls_tokio::TlsStream<T>>,
         255  +
    }
         256  +
         257  +
    impl<T> Connection for S2nTlsConn<T>
         258  +
    where
         259  +
        T: Connection + tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
         260  +
    {
         261  +
        fn connected(&self) -> Connected {
         262  +
            // For tunneled connections, we can't easily access the underlying connection info
         263  +
            // from s2n-tls, so we'll return a basic Connected instance
         264  +
            Connected::new()
         265  +
        }
         266  +
    }
         267  +
         268  +
    impl<T> hyper::rt::Read for S2nTlsConn<T>
         269  +
    where
         270  +
        T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
         271  +
    {
         272  +
        fn poll_read(
         273  +
            self: Pin<&mut Self>,
         274  +
            cx: &mut Context<'_>,
         275  +
            buf: hyper::rt::ReadBufCursor<'_>,
         276  +
        ) -> Poll<tokio::io::Result<()>> {
         277  +
            Pin::new(&mut self.get_mut().inner).poll_read(cx, buf)
         278  +
        }
         279  +
    }
         280  +
         281  +
    impl<T> hyper::rt::Write for S2nTlsConn<T>
         282  +
    where
         283  +
        T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
         284  +
    {
         285  +
        fn poll_write(
         286  +
            self: Pin<&mut Self>,
         287  +
            cx: &mut Context<'_>,
         288  +
            buf: &[u8],
         289  +
        ) -> Poll<Result<usize, tokio::io::Error>> {
         290  +
            Pin::new(&mut self.get_mut().inner).poll_write(cx, buf)
         291  +
        }
         292  +
         293  +
        fn poll_flush(
         294  +
            self: Pin<&mut Self>,
         295  +
            cx: &mut Context<'_>,
         296  +
        ) -> Poll<Result<(), tokio::io::Error>> {
         297  +
            Pin::new(&mut self.get_mut().inner).poll_flush(cx)
         298  +
        }
         299  +
         300  +
        fn poll_shutdown(
         301  +
            self: Pin<&mut Self>,
         302  +
            cx: &mut Context<'_>,
         303  +
        ) -> Poll<Result<(), tokio::io::Error>> {
         304  +
            Pin::new(&mut self.get_mut().inner).poll_shutdown(cx)
         305  +
        }
         306  +
         307  +
        fn poll_write_vectored(
         308  +
            self: Pin<&mut Self>,
         309  +
            cx: &mut Context<'_>,
         310  +
            bufs: &[IoSlice<'_>],
         311  +
        ) -> Poll<Result<usize, tokio::io::Error>> {
         312  +
            Pin::new(&mut self.get_mut().inner).poll_write_vectored(cx, bufs)
         313  +
        }
         314  +
         315  +
        fn is_write_vectored(&self) -> bool {
         316  +
            self.inner.is_write_vectored()
         317  +
        }
         318  +
    }
         319  +
}

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/src/lib.rs

@@ -16,16 +76,76 @@
   36     36   
#[cfg(feature = "hyper-014")]
   37     37   
#[deprecated = "hyper 0.14.x support is deprecated, please migrate to 1.x client"]
   38     38   
pub mod hyper_014 {
   39     39   
    pub use crate::hyper_legacy::*;
   40     40   
}
   41     41   
   42     42   
/// Default HTTP and TLS connectors
   43     43   
#[cfg(feature = "default-client")]
   44     44   
pub(crate) mod client;
   45     45   
#[cfg(feature = "default-client")]
   46         -
pub use client::{default_connector, tls, Builder, Connector, ConnectorBuilder};
          46  +
pub use client::{default_connector, proxy, tls, Builder, Connector, ConnectorBuilder};
   47     47   
   48     48   
#[cfg(feature = "test-util")]
   49     49   
pub mod test_util;
   50     50   
   51     51   
mod error;
   52     52   
pub use error::HttpClientError;
   53     53   
   54     54   
#[allow(unused_macros, unused_imports)]
   55     55   
#[macro_use]
   56     56   
pub(crate) mod cfg {

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/tests/proxy_tests.rs

@@ -0,1 +0,1105 @@
           1  +
/*
           2  +
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
           3  +
 * SPDX-License-Identifier: Apache-2.0
           4  +
 */
           5  +
           6  +
//! Integration tests for proxy functionality
           7  +
//!
           8  +
//! These tests verify that proxy configuration works end-to-end with real HTTP requests
           9  +
//! using mock proxy servers.
          10  +
#![cfg(feature = "default-client")]
          11  +
          12  +
use aws_smithy_async::time::SystemTimeSource;
          13  +
use aws_smithy_http_client::{proxy::ProxyConfig, tls, Connector};
          14  +
use aws_smithy_runtime_api::client::http::{
          15  +
    http_client_fn, HttpClient, HttpConnector, HttpConnectorSettings,
          16  +
};
          17  +
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
          18  +
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
          19  +
use base64::Engine;
          20  +
use http_1x::{Request, Response, StatusCode};
          21  +
use http_body_util::BodyExt;
          22  +
use hyper::body::Incoming;
          23  +
use hyper::service::service_fn;
          24  +
use hyper_util::rt::TokioIo;
          25  +
use std::collections::HashMap;
          26  +
use std::convert::Infallible;
          27  +
use std::net::SocketAddr;
          28  +
use std::sync::{Arc, Mutex};
          29  +
use tokio::net::TcpListener;
          30  +
use tokio::sync::oneshot;
          31  +
          32  +
// ================================================================================================
          33  +
// Test Utilities (Mock Proxy Server)
          34  +
// ================================================================================================
          35  +
          36  +
/// Mock HTTP server that acts as a proxy endpoint for testing
          37  +
#[derive(Debug)]
          38  +
struct MockProxyServer {
          39  +
    addr: SocketAddr,
          40  +
    shutdown_tx: Option<oneshot::Sender<()>>,
          41  +
    request_log: Arc<Mutex<Vec<RecordedRequest>>>,
          42  +
}
          43  +
          44  +
/// A recorded request received by the mock proxy server
          45  +
#[derive(Debug, Clone)]
          46  +
struct RecordedRequest {
          47  +
    method: String,
          48  +
    uri: String,
          49  +
    headers: HashMap<String, String>,
          50  +
}
          51  +
          52  +
impl MockProxyServer {
          53  +
    /// Create a new mock proxy server with a custom request handler
          54  +
    async fn new<F>(handler: F) -> Self
          55  +
    where
          56  +
        F: Fn(RecordedRequest) -> Response<String> + Send + Sync + 'static,
          57  +
    {
          58  +
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
          59  +
        let addr = listener.local_addr().unwrap();
          60  +
        let (shutdown_tx, shutdown_rx) = oneshot::channel();
          61  +
        let request_log = Arc::new(Mutex::new(Vec::new()));
          62  +
        let request_log_clone = request_log.clone();
          63  +
          64  +
        let handler = Arc::new(handler);
          65  +
          66  +
        tokio::spawn(async move {
          67  +
            let mut shutdown_rx = shutdown_rx;
          68  +
          69  +
            loop {
          70  +
                tokio::select! {
          71  +
                    result = listener.accept() => {
          72  +
                        match result {
          73  +
                            Ok((stream, _)) => {
          74  +
                                let io = TokioIo::new(stream);
          75  +
                                let handler = handler.clone();
          76  +
                                let request_log = request_log_clone.clone();
          77  +
          78  +
                                tokio::spawn(async move {
          79  +
                                    let service = service_fn(move |req: Request<Incoming>| {
          80  +
                                        let handler = handler.clone();
          81  +
                                        let request_log = request_log.clone();
          82  +
          83  +
                                        async move {
          84  +
                                            // Record the request
          85  +
                                            let recorded = RecordedRequest {
          86  +
                                                method: req.method().to_string(),
          87  +
                                                uri: req.uri().to_string(),
          88  +
                                                headers: req.headers().iter()
          89  +
                                                    .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
          90  +
                                                    .collect(),
          91  +
                                            };
          92  +
          93  +
                                            request_log.lock().unwrap().push(recorded.clone());
          94  +
          95  +
                                            // Call the handler
          96  +
                                            let response = handler(recorded);
          97  +
          98  +
                                            // Convert to hyper response
          99  +
                                            let (parts, body) = response.into_parts();
         100  +
                                            let hyper_response = Response::from_parts(parts, body);
         101  +
         102  +
                                            Ok::<_, Infallible>(hyper_response)
         103  +
                                        }
         104  +
                                    });
         105  +
         106  +
                                    if let Err(err) = hyper::server::conn::http1::Builder::new()
         107  +
                                        .serve_connection(io, service)
         108  +
                                        .await
         109  +
                                    {
         110  +
                                        eprintln!("Mock proxy server connection error: {}", err);
         111  +
                                    }
         112  +
                                });
         113  +
                            }
         114  +
                            Err(_) => break,
         115  +
                        }
         116  +
                    }
         117  +
                    _ = &mut shutdown_rx => {
         118  +
                        break;
         119  +
                    }
         120  +
                }
         121  +
            }
         122  +
        });
         123  +
         124  +
        Self {
         125  +
            addr,
         126  +
            shutdown_tx: Some(shutdown_tx),
         127  +
            request_log,
         128  +
        }
         129  +
    }
         130  +
         131  +
    /// Create a simple mock proxy that returns a fixed response
         132  +
    async fn with_response(status: StatusCode, body: &str) -> Self {
         133  +
        let body = body.to_string();
         134  +
        Self::new(move |_req| {
         135  +
            Response::builder()
         136  +
                .status(status)
         137  +
                .body(body.clone())
         138  +
                .unwrap()
         139  +
        })
         140  +
        .await
         141  +
    }
         142  +
         143  +
    /// Create a mock proxy that validates basic authentication
         144  +
    async fn with_auth_validation(expected_user: &str, expected_pass: &str) -> Self {
         145  +
        let expected_auth = format!(
         146  +
            "Basic {}",
         147  +
            base64::prelude::BASE64_STANDARD.encode(format!("{}:{}", expected_user, expected_pass))
         148  +
        );
         149  +
         150  +
        Self::new(move |req| {
         151  +
            if let Some(auth_header) = req.headers.get("proxy-authorization") {
         152  +
                if auth_header == &expected_auth {
         153  +
                    Response::builder()
         154  +
                        .status(StatusCode::OK)
         155  +
                        .body("authenticated".to_string())
         156  +
                        .unwrap()
         157  +
                } else {
         158  +
                    Response::builder()
         159  +
                        .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
         160  +
                        .body("invalid credentials".to_string())
         161  +
                        .unwrap()
         162  +
                }
         163  +
            } else {
         164  +
                Response::builder()
         165  +
                    .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
         166  +
                    .header("proxy-authenticate", "Basic realm=\"proxy\"")
         167  +
                    .body("authentication required".to_string())
         168  +
                    .unwrap()
         169  +
            }
         170  +
        })
         171  +
        .await
         172  +
    }
         173  +
         174  +
    /// Get the address this server is listening on
         175  +
    fn addr(&self) -> SocketAddr {
         176  +
        self.addr
         177  +
    }
         178  +
         179  +
    /// Get all requests received by this server
         180  +
    fn requests(&self) -> Vec<RecordedRequest> {
         181  +
        self.request_log.lock().unwrap().clone()
         182  +
    }
         183  +
}
         184  +
         185  +
impl Drop for MockProxyServer {
         186  +
    fn drop(&mut self) {
         187  +
        if let Some(tx) = self.shutdown_tx.take() {
         188  +
            let _ = tx.send(());
         189  +
        }
         190  +
    }
         191  +
}
         192  +
         193  +
/// Utility for running tests with specific environment variables
         194  +
#[allow(clippy::await_holding_lock)]
         195  +
async fn with_env_vars<F, Fut, R>(vars: &[(&str, &str)], test: F) -> R
         196  +
where
         197  +
    F: FnOnce() -> Fut,
         198  +
    Fut: std::future::Future<Output = R>,
         199  +
{
         200  +
    // Use a static mutex to serialize environment variable tests
         201  +
    static ENV_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
         202  +
    let _guard = ENV_MUTEX.lock().unwrap();
         203  +
         204  +
    // Save original environment
         205  +
    let original_vars: Vec<_> = vars
         206  +
        .iter()
         207  +
        .map(|(key, _)| (*key, std::env::var(key)))
         208  +
        .collect();
         209  +
         210  +
    // Set test environment variables
         211  +
    for (key, value) in vars {
         212  +
        std::env::set_var(key, value);
         213  +
    }
         214  +
         215  +
    // Run the test
         216  +
    let result = test().await;
         217  +
         218  +
    // Restore original environment
         219  +
    for (key, original_value) in original_vars {
         220  +
        match original_value {
         221  +
            Ok(val) => std::env::set_var(key, val),
         222  +
            Err(_) => std::env::remove_var(key),
         223  +
        }
         224  +
    }
         225  +
         226  +
    result
         227  +
}
         228  +
         229  +
/// Helper function to make HTTP requests through a proxy-configured connector
         230  +
async fn make_http_request_through_proxy(
         231  +
    proxy_config: ProxyConfig,
         232  +
    target_url: &str,
         233  +
) -> Result<(StatusCode, String), Box<dyn std::error::Error + Send + Sync>> {
         234  +
    // Create an HttpClient using http_client_fn with proxy-configured connector
         235  +
    let http_client = http_client_fn(move |settings, _components| {
         236  +
        let connector = Connector::builder()
         237  +
            .proxy_config(proxy_config.clone())
         238  +
            .connector_settings(settings.clone())
         239  +
            .build_http();
         240  +
         241  +
        aws_smithy_runtime_api::client::http::SharedHttpConnector::new(connector)
         242  +
    });
         243  +
         244  +
    // Set up runtime components (following smoke_test_client pattern)
         245  +
    let connector_settings = HttpConnectorSettings::builder().build();
         246  +
    let runtime_components = RuntimeComponentsBuilder::for_tests()
         247  +
        .with_time_source(Some(SystemTimeSource::new()))
         248  +
        .build()
         249  +
        .unwrap();
         250  +
         251  +
    // Get the HTTP connector from the client
         252  +
    let http_connector = http_client.http_connector(&connector_settings, &runtime_components);
         253  +
         254  +
    // Create and make the HTTP request
         255  +
    let request = HttpRequest::get(target_url)
         256  +
        .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
         257  +
         258  +
    let response = http_connector.call(request).await?;
         259  +
         260  +
    // Extract status and body
         261  +
    let status = response.status();
         262  +
    let body_bytes = response.into_body().collect().await?.to_bytes();
         263  +
    let body_string = String::from_utf8(body_bytes.to_vec())?;
         264  +
         265  +
    Ok((status.into(), body_string))
         266  +
}
         267  +
         268  +
#[tokio::test]
         269  +
async fn test_http_proxy_basic_request() {
         270  +
    // Create a mock proxy server that validates the request was routed through it
         271  +
    let mock_proxy = MockProxyServer::new(|req| {
         272  +
        // Validate that this looks like a proxy request
         273  +
        assert_eq!(req.method, "GET");
         274  +
        // For HTTP proxy, the URI should be the full target URL
         275  +
        assert_eq!(req.uri, "http://aws.amazon.com/api/data");
         276  +
         277  +
        // Return a successful response that we can identify
         278  +
        Response::builder()
         279  +
            .status(StatusCode::OK)
         280  +
            .body("proxied response from mock server".to_string())
         281  +
            .unwrap()
         282  +
    })
         283  +
    .await;
         284  +
         285  +
    // Configure connector with HTTP proxy
         286  +
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr())).unwrap();
         287  +
         288  +
    // Make an HTTP request through the proxy - use safe domain
         289  +
    let target_url = "http://aws.amazon.com/api/data";
         290  +
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
         291  +
         292  +
    let (status, body) = result.expect("HTTP request through proxy should succeed");
         293  +
         294  +
    assert_eq!(status, StatusCode::OK);
         295  +
    assert_eq!(body, "proxied response from mock server");
         296  +
         297  +
    // Verify the mock proxy received the expected request
         298  +
    let requests = mock_proxy.requests();
         299  +
    assert_eq!(requests.len(), 1);
         300  +
    assert_eq!(requests[0].method, "GET");
         301  +
    assert_eq!(requests[0].uri, target_url);
         302  +
}
         303  +
         304  +
#[tokio::test]
         305  +
async fn test_proxy_authentication() {
         306  +
    // Create a mock proxy that requires authentication
         307  +
    let mock_proxy = MockProxyServer::with_auth_validation("testuser", "testpass").await;
         308  +
         309  +
    // Configure connector with authenticated proxy
         310  +
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr()))
         311  +
        .unwrap()
         312  +
        .with_basic_auth("testuser", "testpass");
         313  +
         314  +
    // Make request through authenticated proxy - use safe domain
         315  +
    let target_url = "http://aws.amazon.com/protected/resource";
         316  +
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
         317  +
         318  +
    let (status, body) = result.expect("Authenticated proxy request should succeed");
         319  +
         320  +
    assert_eq!(status, StatusCode::OK);
         321  +
    assert_eq!(body, "authenticated");
         322  +
         323  +
    // Verify the proxy received the request with correct auth
         324  +
    let requests = mock_proxy.requests();
         325  +
    assert_eq!(requests.len(), 1);
         326  +
         327  +
    let expected_auth = format!(
         328  +
        "Basic {}",
         329  +
        base64::prelude::BASE64_STANDARD.encode("testuser:testpass")
         330  +
    );
         331  +
    assert_eq!(
         332  +
        requests[0].headers.get("proxy-authorization"),
         333  +
        Some(&expected_auth)
         334  +
    );
         335  +
}
         336  +
         337  +
/// Tests URL-embedded proxy authentication (http://user:pass@proxy.com format)
         338  +
/// Verifies that credentials in the proxy URL are properly extracted and used
         339  +
#[tokio::test]
         340  +
async fn test_proxy_url_embedded_auth() {
         341  +
    let mock_proxy = MockProxyServer::with_auth_validation("urluser", "urlpass").await;
         342  +
         343  +
    // Configure proxy with credentials embedded in URL
         344  +
    let proxy_url = format!("http://urluser:urlpass@{}", mock_proxy.addr());
         345  +
    let proxy_config = ProxyConfig::http(proxy_url).unwrap();
         346  +
         347  +
    // Make request through proxy with URL-embedded auth
         348  +
    let target_url = "http://aws.amazon.com/api/test";
         349  +
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
         350  +
         351  +
    let (status, body) = result.expect("URL-embedded auth proxy request should succeed");
         352  +
    assert_eq!(status, StatusCode::OK);
         353  +
    assert_eq!(body, "authenticated");
         354  +
         355  +
    // Verify the proxy received the request with correct auth
         356  +
    let requests = mock_proxy.requests();
         357  +
    assert_eq!(requests.len(), 1);
         358  +
         359  +
    let expected_auth = format!(
         360  +
        "Basic {}",
         361  +
        base64::prelude::BASE64_STANDARD.encode("urluser:urlpass")
         362  +
    );
         363  +
    assert_eq!(
         364  +
        requests[0].headers.get("proxy-authorization"),
         365  +
        Some(&expected_auth)
         366  +
    );
         367  +
}
         368  +
         369  +
/// Tests authentication precedence: URL-embedded credentials should take precedence over programmatic auth
         370  +
/// Verifies that when both URL auth and with_basic_auth() are provided, URL auth wins
         371  +
#[tokio::test]
         372  +
async fn test_proxy_auth_precedence() {
         373  +
    let mock_proxy = MockProxyServer::with_auth_validation("urluser", "urlpass").await;
         374  +
         375  +
    // Configure proxy with URL-embedded auth AND programmatic auth
         376  +
    // URL auth should take precedence
         377  +
    let proxy_url = format!("http://urluser:urlpass@{}", mock_proxy.addr());
         378  +
    let proxy_config = ProxyConfig::http(proxy_url)
         379  +
        .unwrap()
         380  +
        .with_basic_auth("programmatic", "auth"); // This should be ignored
         381  +
         382  +
    // Make request - should use URL-embedded auth, not programmatic auth
         383  +
    let target_url = "http://aws.amazon.com/precedence/test";
         384  +
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
         385  +
         386  +
    let (status, body) = result.expect("Auth precedence test should succeed");
         387  +
    assert_eq!(status, StatusCode::OK);
         388  +
    assert_eq!(body, "authenticated");
         389  +
         390  +
    // Verify the proxy received the request with URL-embedded auth (not programmatic)
         391  +
    let requests = mock_proxy.requests();
         392  +
    assert_eq!(requests.len(), 1);
         393  +
         394  +
    let expected_auth = format!(
         395  +
        "Basic {}",
         396  +
        base64::prelude::BASE64_STANDARD.encode("urluser:urlpass")
         397  +
    );
         398  +
    assert_eq!(
         399  +
        requests[0].headers.get("proxy-authorization"),
         400  +
        Some(&expected_auth)
         401  +
    );
         402  +
}
         403  +
         404  +
#[tokio::test]
         405  +
async fn test_proxy_from_environment_variables() {
         406  +
    let mock_proxy = MockProxyServer::with_response(StatusCode::OK, "env proxy response").await;
         407  +
         408  +
    with_env_vars(
         409  +
        &[
         410  +
            ("HTTP_PROXY", &format!("http://{}", mock_proxy.addr())),
         411  +
            ("NO_PROXY", "localhost,127.0.0.1"),
         412  +
        ],
         413  +
        || async {
         414  +
            // Create connector with environment-based proxy config
         415  +
            let proxy_config = ProxyConfig::from_env();
         416  +
         417  +
            // Make request through environment-configured proxy
         418  +
            let target_url = "http://aws.amazon.com/v1/data";
         419  +
            let result = make_http_request_through_proxy(proxy_config, target_url).await;
         420  +
         421  +
            let (status, body) = result.expect("Environment proxy request should succeed");
         422  +
         423  +
            assert_eq!(status, StatusCode::OK);
         424  +
            assert_eq!(body, "env proxy response");
         425  +
         426  +
            // Verify the proxy received the request
         427  +
            let requests = mock_proxy.requests();
         428  +
            assert_eq!(requests.len(), 1);
         429  +
            assert_eq!(requests[0].uri, target_url);
         430  +
        },
         431  +
    )
         432  +
    .await;
         433  +
}
         434  +
         435  +
/// Tests that NO_PROXY bypass rules work correctly
         436  +
/// Verifies that requests to bypassed hosts do not go through the proxy
         437  +
#[tokio::test]
         438  +
async fn test_no_proxy_bypass_rules() {
         439  +
    let mock_proxy = MockProxyServer::with_response(StatusCode::OK, "should not reach here").await;
         440  +
         441  +
    // Create a second mock server that will act as the "direct" target
         442  +
    let direct_server = MockProxyServer::with_response(StatusCode::OK, "direct connection").await;
         443  +
         444  +
    // Configure proxy with NO_PROXY rules that include the direct server's address
         445  +
    // Use just the IP address for the NO_PROXY rule
         446  +
    let direct_ip = "127.0.0.1";
         447  +
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr()))
         448  +
        .unwrap()
         449  +
        .no_proxy(direct_ip);
         450  +
         451  +
    // Make request to the direct server (should bypass proxy due to NO_PROXY rule)
         452  +
    let result = make_http_request_through_proxy(
         453  +
        proxy_config,
         454  +
        &format!("http://{}/test", direct_server.addr()),
         455  +
    )
         456  +
    .await;
         457  +
         458  +
    let (status, body) = result.expect("Direct connection should succeed");
         459  +
    assert_eq!(status, StatusCode::OK);
         460  +
    assert_eq!(body, "direct connection");
         461  +
         462  +
    // Verify the mock proxy received no requests (bypassed)
         463  +
    let proxy_requests = mock_proxy.requests();
         464  +
    assert_eq!(
         465  +
        proxy_requests.len(),
         466  +
        0,
         467  +
        "Proxy should not have received any requests due to NO_PROXY bypass"
         468  +
    );
         469  +
         470  +
    // Verify the direct server received the request
         471  +
    let direct_requests = direct_server.requests();
         472  +
    assert_eq!(
         473  +
        direct_requests.len(),
         474  +
        1,
         475  +
        "Direct server should have received the request"
         476  +
    );
         477  +
}
         478  +
         479  +
/// Tests that disabled proxy configuration results in direct connections
         480  +
/// Verifies that ProxyConfig::disabled() bypasses all proxy logic
         481  +
#[tokio::test]
         482  +
async fn test_proxy_disabled() {
         483  +
    // Create a direct target server
         484  +
    let direct_server = MockProxyServer::with_response(StatusCode::OK, "direct connection").await;
         485  +
         486  +
    // Create a disabled proxy configuration
         487  +
    let proxy_config = ProxyConfig::disabled();
         488  +
         489  +
    // Make request with disabled proxy (should go direct to our mock server)
         490  +
    let result = make_http_request_through_proxy(
         491  +
        proxy_config,
         492  +
        &format!("http://{}/get", direct_server.addr()),
         493  +
    )
         494  +
    .await;
         495  +
         496  +
    let (status, body) = result.expect("Direct connection should succeed");
         497  +
    assert_eq!(status, StatusCode::OK);
         498  +
    assert_eq!(body, "direct connection");
         499  +
         500  +
    // Verify the direct server received the request
         501  +
    let requests = direct_server.requests();
         502  +
    assert_eq!(
         503  +
        requests.len(),
         504  +
        1,
         505  +
        "Direct server should have received the request"
         506  +
    );
         507  +
    assert_eq!(requests[0].method, "GET");
         508  +
    // For direct connections, the URI might be just the path part
         509  +
    assert!(
         510  +
        requests[0].uri == format!("http://{}/get", direct_server.addr())
         511  +
            || requests[0].uri == "/get",
         512  +
        "URI should be either full URL or path, got: {}",
         513  +
        requests[0].uri
         514  +
    );
         515  +
}
         516  +
         517  +
/// Tests HTTPS-only proxy configuration
         518  +
/// Verifies that HTTP requests bypass HTTPS-only proxies
         519  +
#[tokio::test]
         520  +
async fn test_https_proxy_configuration() {
         521  +
    let mock_proxy = MockProxyServer::with_response(StatusCode::OK, "https proxy response").await;
         522  +
         523  +
    // Create a direct target server for HTTP requests
         524  +
    let direct_server =
         525  +
        MockProxyServer::with_response(StatusCode::OK, "direct http connection").await;
         526  +
         527  +
    // Configure HTTPS-only proxy
         528  +
    let proxy_config = ProxyConfig::https(format!("http://{}", mock_proxy.addr())).unwrap();
         529  +
         530  +
    // Test: HTTP request should NOT go through HTTPS-only proxy, should go direct
         531  +
    let target_url = format!("http://{}/api", direct_server.addr());
         532  +
    let result = make_http_request_through_proxy(proxy_config.clone(), &target_url).await;
         533  +
         534  +
    // The HTTP request should succeed by going directly to our mock server
         535  +
    let (status, body) = result.expect("HTTP request should succeed via direct connection");
         536  +
    assert_eq!(status, StatusCode::OK);
         537  +
    assert_eq!(body, "direct http connection");
         538  +
         539  +
    // Verify the HTTPS-only proxy received no requests
         540  +
    let proxy_requests = mock_proxy.requests();
         541  +
    assert_eq!(
         542  +
        proxy_requests.len(),
         543  +
        0,
         544  +
        "HTTP request should not go through HTTPS-only proxy"
         545  +
    );
         546  +
         547  +
    // Verify the direct server received the request
         548  +
    let direct_requests = direct_server.requests();
         549  +
    assert_eq!(
         550  +
        direct_requests.len(),
         551  +
        1,
         552  +
        "Direct server should have received the HTTP request"
         553  +
    );
         554  +
}
         555  +
         556  +
/// Tests all-traffic proxy configuration
         557  +
/// Verifies that both HTTP and HTTPS requests go through all-traffic proxies
         558  +
#[tokio::test]
         559  +
async fn test_all_traffic_proxy() {
         560  +
    let mock_proxy = MockProxyServer::with_response(StatusCode::OK, "all traffic proxy").await;
         561  +
         562  +
    // Configure proxy for all traffic
         563  +
    let proxy_config = ProxyConfig::all(format!("http://{}", mock_proxy.addr())).unwrap();
         564  +
         565  +
    // HTTP request should go through the proxy
         566  +
    let target_url = "http://aws.amazon.com/api/endpoint";
         567  +
    let result = make_http_request_through_proxy(proxy_config.clone(), target_url).await;
         568  +
         569  +
    let (status, body) = result.expect("HTTP request through all-traffic proxy should succeed");
         570  +
    assert_eq!(status, StatusCode::OK);
         571  +
    assert_eq!(body, "all traffic proxy");
         572  +
         573  +
    // Verify the proxy received the HTTP request
         574  +
    let requests = mock_proxy.requests();
         575  +
    assert_eq!(
         576  +
        requests.len(),
         577  +
        1,
         578  +
        "Proxy should have received exactly one request"
         579  +
    );
         580  +
    assert_eq!(requests[0].method, "GET");
         581  +
    assert_eq!(requests[0].uri, target_url);
         582  +
}
         583  +
         584  +
/// Tests proxy connection failure handling
         585  +
/// Verifies that unreachable proxy servers result in appropriate connection errors
         586  +
#[tokio::test]
         587  +
async fn test_proxy_connection_failure() {
         588  +
    // Configure proxy pointing to non-existent server
         589  +
    let proxy_config = ProxyConfig::http("http://127.0.0.1:1").unwrap(); // Port 1 should be unavailable
         590  +
         591  +
    // Make request through non-existent proxy - use a safe domain that won't cause issues
         592  +
    let target_url = "http://aws.amazon.com/api/test";
         593  +
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
         594  +
         595  +
    // The request should fail with a connection error
         596  +
    assert!(
         597  +
        result.is_err(),
         598  +
        "Request should fail when proxy is unreachable"
         599  +
    );
         600  +
         601  +
    let error = result.unwrap_err();
         602  +
    let error_msg = error.to_string().to_lowercase();
         603  +
         604  +
    // Verify it's a connection-related error (not a different kind of error)
         605  +
    assert!(
         606  +
        error_msg.contains("connection")
         607  +
            || error_msg.contains("refused")
         608  +
            || error_msg.contains("unreachable")
         609  +
            || error_msg.contains("timeout")
         610  +
            || error_msg.contains("connect")
         611  +
            || error_msg.contains("io error"), // Include generic IO errors
         612  +
        "Error should be connection-related, got: {}",
         613  +
        error
         614  +
    );
         615  +
}
         616  +
         617  +
/// Tests proxy authentication failure handling
         618  +
/// Verifies that incorrect proxy credentials result in 407 Proxy Authentication Required
         619  +
#[tokio::test]
         620  +
async fn test_proxy_authentication_failure() {
         621  +
    let mock_proxy = MockProxyServer::with_auth_validation("correct", "password").await;
         622  +
         623  +
    // Configure proxy with wrong credentials
         624  +
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr()))
         625  +
        .unwrap()
         626  +
        .with_basic_auth("wrong", "credentials");
         627  +
         628  +
    // Make request with wrong credentials - use safe domain
         629  +
    let target_url = "http://aws.amazon.com/secure/api";
         630  +
    let result = make_http_request_through_proxy(proxy_config, target_url).await;
         631  +
         632  +
    // The request should return 407 Proxy Authentication Required
         633  +
    let (status, _body) = result.expect("Request should complete (even with auth failure)");
         634  +
    assert_eq!(status, StatusCode::PROXY_AUTHENTICATION_REQUIRED);
         635  +
         636  +
    // Verify the proxy received the request (even though auth failed)
         637  +
    let requests = mock_proxy.requests();
         638  +
    assert_eq!(requests.len(), 1, "Proxy should have received the request");
         639  +
         640  +
    // Verify the wrong credentials were sent
         641  +
    let expected_wrong_auth = format!(
         642  +
        "Basic {}",
         643  +
        base64::prelude::BASE64_STANDARD.encode("wrong:credentials")
         644  +
    );
         645  +
    assert_eq!(
         646  +
        requests[0].headers.get("proxy-authorization"),
         647  +
        Some(&expected_wrong_auth)
         648  +
    );
         649  +
}
         650  +
         651  +
/// Tests that ProxyConfig::disabled() overrides environment proxy settings
         652  +
/// Verifies that explicit proxy disabling takes precedence over environment variables
         653  +
#[tokio::test]
         654  +
async fn test_explicit_proxy_disable_overrides_environment() {
         655  +
    let mock_proxy = MockProxyServer::new(|_req| {
         656  +
        panic!("Request should not reach proxy when explicitly disabled");
         657  +
    })
         658  +
    .await;
         659  +
         660  +
    // Create a direct target server
         661  +
    let direct_server = MockProxyServer::with_response(StatusCode::OK, "direct connection").await;
         662  +
         663  +
    with_env_vars(
         664  +
        &[("HTTP_PROXY", &format!("http://{}", mock_proxy.addr()))],
         665  +
        || async {
         666  +
            // Create connector with explicitly disabled proxy (should override environment)
         667  +
            let proxy_config = ProxyConfig::disabled();
         668  +
         669  +
            // Make request - should go direct despite HTTP_PROXY environment variable
         670  +
            let target_url = format!("http://{}/test", direct_server.addr());
         671  +
            let result = make_http_request_through_proxy(proxy_config, &target_url).await;
         672  +
         673  +
            let (status, body) = result.expect("Direct connection should succeed");
         674  +
            assert_eq!(status, StatusCode::OK);
         675  +
            assert_eq!(body, "direct connection");
         676  +
         677  +
            // Verify the proxy received no requests (disabled)
         678  +
            let proxy_requests = mock_proxy.requests();
         679  +
            assert_eq!(
         680  +
                proxy_requests.len(),
         681  +
                0,
         682  +
                "Proxy should not receive requests when explicitly disabled"
         683  +
            );
         684  +
         685  +
            // Verify the direct server received the request
         686  +
            let direct_requests = direct_server.requests();
         687  +
            assert_eq!(
         688  +
                direct_requests.len(),
         689  +
                1,
         690  +
                "Direct server should have received the request"
         691  +
            );
         692  +
        },
         693  +
    )
         694  +
    .await;
         695  +
}
         696  +
         697  +
// ================================================================================================
         698  +
// HTTPS/CONNECT Tunneling Tests
         699  +
// ================================================================================================
         700  +
//
         701  +
// These tests are for HTTPS tunneling through HTTP proxies using the CONNECT method.
         702  +
         703  +
/// Helper function to make HTTPS requests through proxy using TLS providers
         704  +
/// This is similar to make_http_request_through_proxy but uses TLS-enabled connectors
         705  +
async fn make_https_request_through_proxy(
         706  +
    proxy_config: ProxyConfig,
         707  +
    target_url: &str,
         708  +
    tls_provider: tls::Provider,
         709  +
) -> Result<(StatusCode, String), Box<dyn std::error::Error + Send + Sync>> {
         710  +
    let http_client = http_client_fn(move |settings, _components| {
         711  +
        let connector = Connector::builder()
         712  +
            .proxy_config(proxy_config.clone())
         713  +
            .connector_settings(settings.clone())
         714  +
            .tls_provider(tls_provider.clone())
         715  +
            .build();
         716  +
         717  +
        aws_smithy_runtime_api::client::http::SharedHttpConnector::new(connector)
         718  +
    });
         719  +
         720  +
    let connector_settings = HttpConnectorSettings::builder().build();
         721  +
    let runtime_components = RuntimeComponentsBuilder::for_tests()
         722  +
        .with_time_source(Some(SystemTimeSource::new()))
         723  +
        .build()
         724  +
        .unwrap();
         725  +
         726  +
    let http_connector = http_client.http_connector(&connector_settings, &runtime_components);
         727  +
         728  +
    let request = HttpRequest::get(target_url)
         729  +
        .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
         730  +
         731  +
    let response = http_connector.call(request).await?;
         732  +
         733  +
    let status = response.status();
         734  +
    let body_bytes = response.into_body().collect().await?.to_bytes();
         735  +
    let body_string = String::from_utf8(body_bytes.to_vec())?;
         736  +
         737  +
    Ok((status.into(), body_string))
         738  +
}
         739  +
         740  +
/// Generic test function for HTTPS CONNECT with authentication
         741  +
/// Tests that HTTPS requests through HTTP proxy use CONNECT method with proper auth headers
         742  +
async fn run_https_connect_with_auth_test(tls_provider: tls::Provider, provider_name: &str) {
         743  +
    let mock_proxy = MockProxyServer::new(|req| {
         744  +
        // For HTTPS through HTTP proxy, we should see a CONNECT request
         745  +
        assert_eq!(req.method, "CONNECT");
         746  +
        assert_eq!(req.uri, "secure.aws.amazon.com:443");
         747  +
         748  +
        // Verify authentication header is present
         749  +
        let expected_auth = format!(
         750  +
            "Basic {}",
         751  +
            base64::prelude::BASE64_STANDARD.encode("connectuser:connectpass")
         752  +
        );
         753  +
        assert_eq!(req.headers.get("proxy-authorization"), Some(&expected_auth));
         754  +
         755  +
        // Return 400 to avoid dealing with actual TLS tunneling
         756  +
        // The important part is that we got the CONNECT request with correct auth
         757  +
        Response::builder()
         758  +
            .status(StatusCode::BAD_REQUEST)
         759  +
            .body("CONNECT tunnel setup failed".to_string())
         760  +
            .unwrap()
         761  +
    })
         762  +
    .await;
         763  +
         764  +
    // Configure proxy with authentication
         765  +
    let proxy_config = ProxyConfig::all(format!("http://{}", mock_proxy.addr()))
         766  +
        .unwrap()
         767  +
        .with_basic_auth("connectuser", "connectpass");
         768  +
         769  +
    // Make HTTPS request - should trigger CONNECT method
         770  +
    let target_url = "https://secure.aws.amazon.com/api/secure";
         771  +
    let result = make_https_request_through_proxy(proxy_config, target_url, tls_provider).await;
         772  +
         773  +
    // We expect this to fail with a connection error since we returned 400
         774  +
    // The important thing is that the CONNECT request was made correctly
         775  +
    assert!(
         776  +
        result.is_err(),
         777  +
        "CONNECT tunnel should fail with 400 response for {}",
         778  +
        provider_name
         779  +
    );
         780  +
         781  +
    // Verify the proxy received the CONNECT request
         782  +
    let requests = mock_proxy.requests();
         783  +
    assert_eq!(
         784  +
        requests.len(),
         785  +
        1,
         786  +
        "Proxy should have received exactly one CONNECT request for {}",
         787  +
        provider_name
         788  +
    );
         789  +
}
         790  +
         791  +
/// Generic test function for CONNECT without authentication (should get 407)
         792  +
/// Tests that HTTPS requests without auth get proper 407 response
         793  +
async fn run_https_connect_auth_required_test(tls_provider: tls::Provider, provider_name: &str) {
         794  +
    let mock_proxy = MockProxyServer::new(|req| {
         795  +
        // For HTTPS through HTTP proxy, we should see a CONNECT request
         796  +
        assert_eq!(req.method, "CONNECT");
         797  +
        assert_eq!(req.uri, "secure.aws.amazon.com:443");
         798  +
         799  +
        // No auth header should be present
         800  +
        assert!(!req.headers.contains_key("proxy-authorization"));
         801  +
         802  +
        // Return 407 Proxy Authentication Required
         803  +
        Response::builder()
         804  +
            .status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
         805  +
            .body("Proxy authentication required for CONNECT".to_string())
         806  +
            .unwrap()
         807  +
    })
         808  +
    .await;
         809  +
         810  +
    // Configure proxy without authentication
         811  +
    let proxy_config = ProxyConfig::all(format!("http://{}", mock_proxy.addr())).unwrap();
         812  +
         813  +
    // Make HTTPS request - should trigger CONNECT method and get 407
         814  +
    let target_url = "https://secure.aws.amazon.com/api/secure";
         815  +
    let result = make_https_request_through_proxy(proxy_config, target_url, tls_provider).await;
         816  +
         817  +
    // We expect this to fail with a connection error since we returned 407
         818  +
    assert!(
         819  +
        result.is_err(),
         820  +
        "CONNECT tunnel should fail with 407 response for {}",
         821  +
        provider_name
         822  +
    );
         823  +
         824  +
    let error_msg = result.unwrap_err().to_string();
         825  +
    let error_msg_lower = error_msg.to_lowercase();
         826  +
         827  +
    // The important thing is that the request failed (which means CONNECT was attempted)
         828  +
    // The specific error message format is less critical for this test
         829  +
    // We accept either specific proxy auth errors OR generic connection errors
         830  +
    // since both indicate the CONNECT tunnel attempt was made
         831  +
    assert!(
         832  +
        error_msg_lower.contains("407")
         833  +
            || error_msg_lower.contains("proxy")
         834  +
            || error_msg_lower.contains("auth")
         835  +
            || error_msg_lower.contains("io error")
         836  +
            || error_msg_lower.contains("connection"),
         837  +
        "Error should be connection-related (indicating CONNECT was attempted) for {}, got: {}",
         838  +
        provider_name,
         839  +
        error_msg
         840  +
    );
         841  +
         842  +
    // Verify the proxy received the CONNECT request
         843  +
    let requests = mock_proxy.requests();
         844  +
    assert_eq!(
         845  +
        requests.len(),
         846  +
        1,
         847  +
        "Proxy should have received exactly one CONNECT request for {}",
         848  +
        provider_name
         849  +
    );
         850  +
}
         851  +
         852  +
/// Tests HTTPS tunneling through HTTP proxy with CONNECT method (rustls provider)
         853  +
/// Verifies that HTTPS requests through HTTP proxy use CONNECT method with authentication
         854  +
#[cfg(feature = "rustls-ring")]
         855  +
#[tokio::test]
         856  +
async fn test_https_connect_with_auth_rustls() {
         857  +
    run_https_connect_with_auth_test(
         858  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
         859  +
        "rustls",
         860  +
    )
         861  +
    .await;
         862  +
}
         863  +
         864  +
/// Tests CONNECT method without authentication (should get 407) - rustls provider
         865  +
/// Verifies that HTTPS requests without auth get proper 407 response
         866  +
#[cfg(feature = "rustls-ring")]
         867  +
#[tokio::test]
         868  +
async fn test_https_connect_auth_required_rustls() {
         869  +
    run_https_connect_auth_required_test(
         870  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
         871  +
        "rustls",
         872  +
    )
         873  +
    .await;
         874  +
}
         875  +
         876  +
/// Tests HTTPS tunneling through HTTP proxy with CONNECT method (s2n-tls provider)
         877  +
/// Verifies that HTTPS requests through HTTP proxy use CONNECT method with authentication
         878  +
#[cfg(feature = "s2n-tls")]
         879  +
#[tokio::test]
         880  +
async fn test_https_connect_with_auth_s2n_tls() {
         881  +
    run_https_connect_with_auth_test(tls::Provider::S2nTls, "s2n-tls").await;
         882  +
}
         883  +
         884  +
/// Tests CONNECT method without authentication (should get 407) - s2n-tls provider
         885  +
/// Verifies that HTTPS requests without auth get proper 407 response
         886  +
#[cfg(feature = "s2n-tls")]
         887  +
#[tokio::test]
         888  +
async fn test_https_connect_auth_required_s2n_tls() {
         889  +
    run_https_connect_auth_required_test(tls::Provider::S2nTls, "s2n-tls").await;
         890  +
}
         891  +
         892  +
/// Tests that HTTP requests through proxy use absolute URI form
         893  +
/// Verifies that the full URL (including hostname) is sent to the proxy
         894  +
#[tokio::test]
         895  +
async fn test_http_proxy_absolute_uri_form() {
         896  +
    let target_host = "api.example.com";
         897  +
    let target_path = "/v1/data";
         898  +
    let expected_absolute_uri = format!("http://{}{}", target_host, target_path);
         899  +
         900  +
    // Clone for use in closure
         901  +
    let expected_uri_clone = expected_absolute_uri.clone();
         902  +
    let target_host_clone = target_host.to_string();
         903  +
         904  +
    let mock_proxy = MockProxyServer::new(move |req| {
         905  +
        // For HTTP through proxy, we should see the full absolute URI
         906  +
        assert_eq!(req.method, "GET");
         907  +
        assert_eq!(req.uri, expected_uri_clone);
         908  +
         909  +
        // Host header should still be present
         910  +
        assert_eq!(req.headers.get("host"), Some(&target_host_clone));
         911  +
         912  +
        Response::builder()
         913  +
            .status(StatusCode::OK)
         914  +
            .body("proxied response".to_string())
         915  +
            .unwrap()
         916  +
    })
         917  +
    .await;
         918  +
         919  +
    let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr())).unwrap();
         920  +
         921  +
    let result = make_http_request_through_proxy(proxy_config, &expected_absolute_uri).await;
         922  +
         923  +
    let (status, body) = result.expect("HTTP request through proxy should succeed");
         924  +
    assert_eq!(status, StatusCode::OK);
         925  +
    assert_eq!(body, "proxied response");
         926  +
         927  +
    // Verify the proxy received the request with absolute URI
         928  +
    let requests = mock_proxy.requests();
         929  +
    assert_eq!(requests.len(), 1);
         930  +
    assert_eq!(requests[0].uri, expected_absolute_uri);
         931  +
}
         932  +
         933  +
/// Tests that direct HTTP requests (no proxy) use origin form URI
         934  +
/// Verifies that only the path is sent when connecting directly
         935  +
#[tokio::test]
         936  +
async fn test_direct_http_origin_uri_form() {
         937  +
    let target_path = "/v1/data";
         938  +
         939  +
    // Create a direct target server (no proxy)
         940  +
    let direct_server = MockProxyServer::new(move |req| {
         941  +
        // For direct connections, we should see only the path (origin form)
         942  +
        assert_eq!(req.method, "GET");
         943  +
        // The URI should be just the path part, not the full URL
         944  +
        assert!(
         945  +
            req.uri == target_path || req.uri.ends_with(target_path),
         946  +
            "Expected origin form URI ending with '{}', got '{}'",
         947  +
            target_path,
         948  +
            req.uri
         949  +
        );
         950  +
         951  +
        Response::builder()
         952  +
            .status(StatusCode::OK)
         953  +
            .body("direct response".to_string())
         954  +
            .unwrap()
         955  +
    })
         956  +
    .await;
         957  +
         958  +
    // Use disabled proxy to ensure direct connection
         959  +
    let proxy_config = ProxyConfig::disabled();
         960  +
         961  +
    let target_url = format!("http://{}{}", direct_server.addr(), target_path);
         962  +
    let result = make_http_request_through_proxy(proxy_config, &target_url).await;
         963  +
         964  +
    let (status, body) = result.expect("Direct HTTP request should succeed");
         965  +
    assert_eq!(status, StatusCode::OK);
         966  +
    assert_eq!(body, "direct response");
         967  +
         968  +
    // Verify the server received the request
         969  +
    let requests = direct_server.requests();
         970  +
    assert_eq!(requests.len(), 1);
         971  +
}
         972  +
         973  +
/// Tests URI form handling with different proxy configurations
         974  +
/// Verifies that URI form changes based on proxy vs direct connection
         975  +
#[tokio::test]
         976  +
async fn test_uri_form_proxy_vs_direct() {
         977  +
    let target_host = "test.example.com";
         978  +
    let target_path = "/api/test";
         979  +
    let full_url = format!("http://{}{}", target_host, target_path);
         980  +
         981  +
    // Test 1: Through proxy - should use absolute form
         982  +
    {
         983  +
        // Clone for use in closure
         984  +
        let target_host_clone = target_host.to_string();
         985  +
        let target_path_clone = target_path.to_string();
         986  +
         987  +
        let mock_proxy = MockProxyServer::new(move |req| {
         988  +
            // Should receive absolute URI
         989  +
            assert!(req.uri.starts_with("http://"));
         990  +
            assert!(req.uri.contains(&target_host_clone));
         991  +
            assert!(req.uri.contains(&target_path_clone));
         992  +
         993  +
            Response::builder()
         994  +
                .status(StatusCode::OK)
         995  +
                .body("proxy response".to_string())
         996  +
                .unwrap()
         997  +
        })
         998  +
        .await;
         999  +
        1000  +
        let proxy_config = ProxyConfig::http(format!("http://{}", mock_proxy.addr())).unwrap();
        1001  +
        let result = make_http_request_through_proxy(proxy_config, &full_url).await;
        1002  +
        1003  +
        assert!(result.is_ok(), "Proxy request should succeed");
        1004  +
        let requests = mock_proxy.requests();
        1005  +
        assert_eq!(requests.len(), 1);
        1006  +
        assert_eq!(requests[0].uri, full_url);
        1007  +
    }
        1008  +
        1009  +
    // Test 2: Direct connection - should use origin form
        1010  +
    {
        1011  +
        let target_path_clone = target_path.to_string();
        1012  +
        1013  +
        let direct_server = MockProxyServer::new(move |req| {
        1014  +
            // Should receive only the path part
        1015  +
            assert!(!req.uri.starts_with("http://"));
        1016  +
            assert!(req.uri == target_path_clone || req.uri.ends_with(&target_path_clone));
        1017  +
        1018  +
            Response::builder()
        1019  +
                .status(StatusCode::OK)
        1020  +
                .body("direct response".to_string())
        1021  +
                .unwrap()
        1022  +
        })
        1023  +
        .await;
        1024  +
        1025  +
        let proxy_config = ProxyConfig::disabled();
        1026  +
        let direct_url = format!("http://{}{}", direct_server.addr(), target_path);
        1027  +
        let result = make_http_request_through_proxy(proxy_config, &direct_url).await;
        1028  +
        1029  +
        assert!(result.is_ok(), "Direct request should succeed");
        1030  +
        let requests = direct_server.requests();
        1031  +
        assert_eq!(requests.len(), 1);
        1032  +
    }
        1033  +
}
        1034  +
        1035  +
/// Generic test function for CONNECT URI form validation
        1036  +
/// Tests that CONNECT requests use the correct host:port format
        1037  +
async fn run_connect_uri_form_test(tls_provider: tls::Provider, provider_name: &str) {
        1038  +
    let target_host = "secure.example.com";
        1039  +
    let target_port = 443;
        1040  +
    let expected_connect_uri = format!("{}:{}", target_host, target_port);
        1041  +
        1042  +
    // Clone for use in closure
        1043  +
    let expected_uri_clone = expected_connect_uri.clone();
        1044  +
        1045  +
    let mock_proxy = MockProxyServer::new(move |req| {
        1046  +
        if req.method == "CONNECT" {
        1047  +
            // CONNECT should use host:port format
        1048  +
            assert_eq!(req.uri, expected_uri_clone);
        1049  +
        1050  +
            // CONNECT requests should not have a Host header in the CONNECT line
        1051  +
            // (the Host header is for the tunneled HTTP request, not the CONNECT)
        1052  +
        1053  +
            Response::builder()
        1054  +
                .status(StatusCode::OK)
        1055  +
                .body("Connection established".to_string())
        1056  +
                .unwrap()
        1057  +
        } else {
        1058  +
            // This shouldn't happen in our test, but handle it gracefully
        1059  +
            Response::builder()
        1060  +
                .status(StatusCode::BAD_REQUEST)
        1061  +
                .body("Unexpected non-CONNECT request".to_string())
        1062  +
                .unwrap()
        1063  +
        }
        1064  +
    })
        1065  +
    .await;
        1066  +
        1067  +
    let proxy_config = ProxyConfig::all(format!("http://{}", mock_proxy.addr())).unwrap();
        1068  +
        1069  +
    // Try to make an HTTPS request - this should trigger CONNECT
        1070  +
    let target_url = format!("https://{}/api/secure", target_host);
        1071  +
        1072  +
    let _result = make_https_request_through_proxy(proxy_config, &target_url, tls_provider).await;
        1073  +
        1074  +
    // The request will likely fail due to our mock setup, but that's OK
        1075  +
    // The important thing is that the CONNECT request was made with correct URI
        1076  +
    let requests = mock_proxy.requests();
        1077  +
    assert_eq!(
        1078  +
        requests.len(),
        1079  +
        1,
        1080  +
        "Should have received exactly one CONNECT request for {}",
        1081  +
        provider_name
        1082  +
    );
        1083  +
    assert_eq!(requests[0].method, "CONNECT");
        1084  +
    assert_eq!(requests[0].uri, expected_connect_uri);
        1085  +
}
        1086  +
        1087  +
/// Tests CONNECT method URI form for HTTPS tunneling - rustls provider
        1088  +
/// Verifies that CONNECT requests use the correct host:port format
        1089  +
#[cfg(feature = "rustls-ring")]
        1090  +
#[tokio::test]
        1091  +
async fn test_connect_uri_form_rustls() {
        1092  +
    run_connect_uri_form_test(
        1093  +
        tls::Provider::rustls(tls::rustls_provider::CryptoMode::Ring),
        1094  +
        "rustls",
        1095  +
    )
        1096  +
    .await;
        1097  +
}
        1098  +
        1099  +
/// Tests CONNECT method URI form for HTTPS tunneling - s2n-tls provider
        1100  +
/// Verifies that CONNECT requests use the correct host:port format
        1101  +
#[cfg(feature = "s2n-tls")]
        1102  +
#[tokio::test]
        1103  +
async fn test_connect_uri_form_s2n_tls() {
        1104  +
    run_connect_uri_form_test(tls::Provider::S2nTls, "s2n-tls").await;
        1105  +
}