atap 0.1.0

Threadsafe futureless async runtime for macOS
//! # TLS
//! The constructors every TLS task is started from

use crate::futures::{
    net::address::NetAddress,
    tcp::Connection,
    tls::{
        config::Keys,
        tls_task::{TlsConnectTask, TlsListenTask, TlsRequestTask},
    },
};
use std::{path::Path, sync::Arc};

/// Talks to other programs over TLS, on top of TCP
///
/// Needs the `tls` feature. It doesn't implement `Task`, so a
/// method has to be called on it to get something that does
///
/// ## Behaviour
/// Everything [`Tcp`] does, encrypted, with the other side's
/// certificate checked. A [`TlsConnection`] hands out the same send
/// and receive tasks as a TCP one
///
/// ```no_run
/// # use atap::{Runtime, tls::Tls};
/// # use std::time::Duration;
/// # fn main() -> Result<(), atap::RuntimeError> {
/// let reply = Runtime::task(Tls::request(
///     "www.example.com:443",
///     b"GET / HTTP/1.0\r\nHost: www.example.com\r\n\r\n".as_slice(),
/// ))
/// .timeout(Duration::from_secs(10))
/// .spawn()
/// .join()??;
/// # Ok(())
/// # }
/// ```
///
/// ## Certificates
/// A server's certificate is checked by macOS against the
/// system's trust store, the same one Safari uses, revocation
/// included. `.trust(pem)` adds roots of your own on top, for a
/// private certificate authority
///
/// A certificate that doesn't check out gives
/// [`RuntimeError::BadCertificate`], and anything else that goes
/// wrong in the session [`RuntimeError::TlsFailed`]
///
/// ## Waiting
/// A spawned task waiting on the network holds no thread, the
/// same as a TCP one. A connect still asks for a sleep thread for
/// its steps
///
/// [`Tcp`]: crate::tcp::Tcp
/// [`TlsConnection`]: crate::tls::TlsConnection
/// [`RuntimeError::BadCertificate`]: crate::RuntimeError::BadCertificate
/// [`RuntimeError::TlsFailed`]: crate::RuntimeError::TlsFailed
pub struct Tls;

impl Tls {
    /// Opens a TLS connection to `addr`
    ///
    /// ## Behaviour
    /// Connects over TCP, then runs the handshake. The server's
    /// certificate has to be for the host in `addr`, or for the
    /// name given to `.server_name()`
    ///
    /// ## Returns
    /// The connection, ready to send and receive on
    pub fn connect(addr: impl NetAddress) -> TlsConnectTask {
        TlsConnectTask::new(addr.target())
    }

    /// Connects to `addr` over TLS, sends `data`, and reads
    /// everything that comes back
    ///
    /// ## Behaviour
    /// The answer is read until the server closes the session,
    /// which is how HTTP/1.0 marks its end
    ///
    /// ## Returns
    /// Everything the server sent. [`RuntimeError::Closed`] instead
    /// if it dropped the connection without closing the session,
    /// since the answer may have been cut short
    ///
    /// [`RuntimeError::Closed`]: crate::RuntimeError::Closed
    pub fn request(addr: impl NetAddress, data: impl Into<Arc<[u8]>>) -> TlsRequestTask {
        TlsRequestTask::new(addr.target(), data.into())
    }

    /// Opens a socket that waits for TLS connections on `addr`
    ///
    /// ## Behaviour
    /// `cert` is the certificate chain and `key` its private key,
    /// both PEM files, read when the task runs. Port 0 picks any
    /// free port
    ///
    /// ## Returns
    /// The listener. A file that can't be read keeps the kernel's
    /// reason, and one that doesn't parse, or a key that doesn't
    /// match, gives [`RuntimeError::BadCertificate`]
    ///
    /// [`RuntimeError::BadCertificate`]: crate::RuntimeError::BadCertificate
    pub fn listen(
        addr: impl NetAddress,
        cert: impl AsRef<Path>,
        key: impl AsRef<Path>,
    ) -> TlsListenTask {
        TlsListenTask::new(
            addr.target(),
            Keys::Files(cert.as_ref().to_path_buf(), key.as_ref().to_path_buf()),
        )
    }

    /// Opens a socket that waits for TLS connections on `addr`,
    /// with a certificate and key already in memory
    ///
    /// ## Behaviour
    /// The same as [`Tls::listen`], with `cert` and `key` given as
    /// PEM rather than read from files
    ///
    /// ## Returns
    /// The listener. A chain or key that doesn't parse, or a key
    /// that doesn't match, gives [`RuntimeError::BadCertificate`]
    ///
    /// [`RuntimeError::BadCertificate`]: crate::RuntimeError::BadCertificate
    pub fn listen_pem(
        addr: impl NetAddress,
        cert: impl AsRef<[u8]>,
        key: impl AsRef<[u8]>,
    ) -> TlsListenTask {
        TlsListenTask::new(
            addr.target(),
            Keys::Pem(Arc::from(cert.as_ref()), Arc::from(key.as_ref())),
        )
    }

    /// Starts TLS on a TCP connection that is already open, as the
    /// client
    ///
    /// ## Behaviour
    /// For a protocol that begins in the clear and switches, such
    /// as SMTP's `STARTTLS`. The certificate is checked against the
    /// peer's address unless `.server_name()` says otherwise. The
    /// other side runs [`TlsListener::upgrade`] or its own
    /// equivalent
    ///
    /// ## Returns
    /// The TLS connection. The TCP one should not be used for
    /// anything else afterwards
    ///
    /// [`TlsListener::upgrade`]: crate::tls::TlsListener::upgrade
    pub fn upgrade(conn: Connection) -> TlsConnectTask {
        TlsConnectTask::over(conn)
    }
}