//!  TLS options configuration
//!
//! By default, a `Client` will make use of BoringSSL for TLS.
//!
//! - Various parts of TLS can also be configured or even disabled on the `ClientBuilder`.

pub(crate) mod conn;
mod keylog;
mod options;
mod x509;

#[cfg(feature = "boring")]
pub(crate) mod boring;
#[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
pub(crate) mod rustls;

#[cfg(feature = "boring")]
pub use ::boring::ssl::{CertificateCompressionAlgorithm, ExtensionType};
use bytes::Bytes;

pub use self::{
    keylog::KeyLog,
    options::{TlsOptions, TlsOptionsBuilder},
    x509::{CertStore, CertStoreBuilder, Certificate, Identity},
};

/// Http extension carrying extra TLS layer information.
/// Made available to clients on responses when `tls_info` is set.
#[derive(Debug, Clone)]
pub struct TlsInfo {
    pub(crate) peer_certificate: Option<Bytes>,
    pub(crate) peer_certificate_chain: Option<Vec<Bytes>>,
}

impl TlsInfo {
    /// Get the DER encoded leaf certificate of the peer.
    pub fn peer_certificate(&self) -> Option<&[u8]> {
        self.peer_certificate.as_deref()
    }

    /// Get the DER encoded certificate chain of the peer.
    ///
    /// This includes the leaf certificate on the client side.
    pub fn peer_certificate_chain(&self) -> Option<impl Iterator<Item = &[u8]>> {
        self.peer_certificate_chain
            .as_ref()
            .map(|v| v.iter().map(|b| b.as_ref()))
    }
}

#[allow(unused_imports)]
use std::hash::{Hash, Hasher};

#[cfg(feature = "boring")]
use ::boring::ssl;
use bytes::{BufMut, BytesMut};

/// A TLS protocol version.
#[cfg(feature = "boring")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TlsVersion(ssl::SslVersion);

#[cfg(feature = "boring")]
impl Hash for TlsVersion {
    fn hash<H: Hasher>(&self, state: &mut H) {
        format!("{:?}", self.0).hash(state);
    }
}

#[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
/// TLS protocol version.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct TlsVersion(u16);

#[cfg(not(any(feature = "boring", feature = "rustls-tls")))]
/// TLS protocol version.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct TlsVersion(u16);

impl TlsVersion {
    /// Version 1.0 of the TLS protocol.
    #[cfg(feature = "boring")]
    pub const TLS_1_0: TlsVersion = TlsVersion(ssl::SslVersion::TLS1);
    /// Version 1.0 of the TLS protocol.
    #[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
    pub const TLS_1_0: TlsVersion = TlsVersion(0x0301);
    /// Version 1.0 of the TLS protocol.
    #[cfg(not(any(feature = "boring", feature = "rustls-tls")))]
    pub const TLS_1_0: TlsVersion = TlsVersion(0x0301);

    /// Version 1.1 of the TLS protocol.
    #[cfg(feature = "boring")]
    pub const TLS_1_1: TlsVersion = TlsVersion(ssl::SslVersion::TLS1_1);
    /// Version 1.1 of the TLS protocol.
    #[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
    pub const TLS_1_1: TlsVersion = TlsVersion(0x0302);
    /// Version 1.1 of the TLS protocol.
    #[cfg(not(any(feature = "boring", feature = "rustls-tls")))]
    pub const TLS_1_1: TlsVersion = TlsVersion(0x0302);

    /// Version 1.2 of the TLS protocol.
    #[cfg(feature = "boring")]
    pub const TLS_1_2: TlsVersion = TlsVersion(ssl::SslVersion::TLS1_2);
    /// Version 1.2 of the TLS protocol.
    #[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
    pub const TLS_1_2: TlsVersion = TlsVersion(0x0303);
    /// Version 1.2 of the TLS protocol.
    #[cfg(not(any(feature = "boring", feature = "rustls-tls")))]
    pub const TLS_1_2: TlsVersion = TlsVersion(0x0303);

    /// Version 1.3 of the TLS protocol.
    #[cfg(feature = "boring")]
    pub const TLS_1_3: TlsVersion = TlsVersion(ssl::SslVersion::TLS1_3);
    /// Version 1.3 of the TLS protocol.
    #[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
    pub const TLS_1_3: TlsVersion = TlsVersion(0x0304);
    /// Version 1.3 of the TLS protocol.
    #[cfg(not(any(feature = "boring", feature = "rustls-tls")))]
    pub const TLS_1_3: TlsVersion = TlsVersion(0x0304);
}

/// A TLS ALPN protocol.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct AlpnProtocol(&'static [u8]);

impl AlpnProtocol {
    /// Prefer HTTP/1.1
    pub const HTTP1: AlpnProtocol = AlpnProtocol(b"http/1.1");

    /// Prefer HTTP/2
    pub const HTTP2: AlpnProtocol = AlpnProtocol(b"h2");

    /// Prefer HTTP/3
    pub const HTTP3: AlpnProtocol = AlpnProtocol(b"h3");

    /// Create a new [`AlpnProtocol`] from a static byte slice.
    #[inline]
    pub const fn new(value: &'static [u8]) -> Self {
        AlpnProtocol(value)
    }

    /// Returns the raw protocol name bytes (e.g. `b"h2"`, `b"http/1.1"`).
    ///
    /// This is the format expected by rustls's `ClientConfig::alpn_protocols`.
    #[inline]
    pub fn as_wire_bytes(&self) -> &'static [u8] {
        self.0
    }

    /// Encode a single protocol in TLS wire format (length-prefixed).
    ///
    /// This is the format expected by BoringSSL's `set_alpn_protos()`.
    #[inline]
    fn encode(self) -> Bytes {
        Self::encode_sequence(std::iter::once(&self))
    }

    /// Encode a sequence of protocols in TLS wire format (each length-prefixed).
    ///
    /// This is the format expected by BoringSSL's `set_alpn_protos()`.
    fn encode_sequence<'a, I>(items: I) -> Bytes
    where
        I: IntoIterator<Item = &'a AlpnProtocol>,
    {
        let mut buf = BytesMut::new();
        for item in items {
            buf.put_u8(item.0.len() as u8);
            buf.extend_from_slice(item.0);
        }
        buf.freeze()
    }
}

/// A TLS ALPS protocol.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct AlpsProtocol(&'static [u8]);

impl AlpsProtocol {
    /// Prefer HTTP/1.1
    pub const HTTP1: AlpsProtocol = AlpsProtocol(b"http/1.1");

    /// Prefer HTTP/2
    pub const HTTP2: AlpsProtocol = AlpsProtocol(b"h2");

    /// Prefer HTTP/3
    pub const HTTP3: AlpsProtocol = AlpsProtocol(b"h3");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn alpn_protocol_encode() {
        let alpn = AlpnProtocol::encode_sequence(&[AlpnProtocol::HTTP1, AlpnProtocol::HTTP2]);
        assert_eq!(alpn, Bytes::from_static(b"\x08http/1.1\x02h2"));

        let alpn = AlpnProtocol::encode_sequence(&[AlpnProtocol::HTTP3]);
        assert_eq!(alpn, Bytes::from_static(b"\x02h3"));

        let alpn = AlpnProtocol::encode_sequence(&[AlpnProtocol::HTTP1, AlpnProtocol::HTTP3]);
        assert_eq!(alpn, Bytes::from_static(b"\x08http/1.1\x02h3"));

        let alpn = AlpnProtocol::encode_sequence(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP3]);
        assert_eq!(alpn, Bytes::from_static(b"\x02h2\x02h3"));

        let alpn = AlpnProtocol::encode_sequence(&[
            AlpnProtocol::HTTP1,
            AlpnProtocol::HTTP2,
            AlpnProtocol::HTTP3,
        ]);
        assert_eq!(alpn, Bytes::from_static(b"\x08http/1.1\x02h2\x02h3"));
    }

    #[test]
    fn alpn_protocol_encode_single() {
        let alpn = AlpnProtocol::HTTP1.encode();
        assert_eq!(alpn, b"\x08http/1.1".as_ref());

        let alpn = AlpnProtocol::HTTP2.encode();
        assert_eq!(alpn, b"\x02h2".as_ref());

        let alpn = AlpnProtocol::HTTP3.encode();
        assert_eq!(alpn, b"\x02h3".as_ref());
    }
}
