added iOS source code
[wl-app.git] / iOS / Pods / Realm / include / core / realm / util / network_ssl.hpp
diff --git a/iOS/Pods/Realm/include/core/realm/util/network_ssl.hpp b/iOS/Pods/Realm/include/core/realm/util/network_ssl.hpp
new file mode 100644 (file)
index 0000000..408087b
--- /dev/null
@@ -0,0 +1,1304 @@
+/*************************************************************************
+ *
+ * REALM CONFIDENTIAL
+ * __________________
+ *
+ *  [2011] - [2015] Realm Inc
+ *  All Rights Reserved.
+ *
+ * NOTICE:  All information contained herein is, and remains
+ * the property of Realm Incorporated and its suppliers,
+ * if any.  The intellectual and technical concepts contained
+ * herein are proprietary to Realm Incorporated
+ * and its suppliers and may be covered by U.S. and Foreign Patents,
+ * patents in process, and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * from Realm Incorporated.
+ *
+ **************************************************************************/
+#ifndef REALM_UTIL_NETWORK_SSL_HPP
+#define REALM_UTIL_NETWORK_SSL_HPP
+
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <string>
+#include <exception>
+#include <system_error>
+
+#include <realm/util/features.h>
+#include <realm/util/assert.hpp>
+#include <realm/util/misc_errors.hpp>
+#include <realm/util/network.hpp>
+#include <realm/util/optional.hpp>
+
+#if REALM_HAVE_OPENSSL
+#  include <openssl/ssl.h>
+#  include <openssl/err.h>
+#elif REALM_HAVE_SECURE_TRANSPORT
+#  include <realm/util/cf_ptr.hpp>
+#  include <Security/Security.h>
+#  include <Security/SecureTransport.h>
+
+#define REALM_HAVE_KEYCHAIN_APIS (TARGET_OS_MAC && !TARGET_OS_IPHONE)
+
+#endif
+
+// FIXME: Add necessary support for customizing the SSL server and client
+// configurations.
+
+// FIXME: Currently, the synchronous SSL operations (handshake, read, write,
+// shutdown) do not automatically retry if the underlying SSL function returns
+// with SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. This normally never
+// happens, but it can happen according to the man pages, but in the case of
+// SSL_write(), only when a renegotiation has to take place. It is likely that
+// the solution is to to wrap the SSL calls inside a loop, such that they keep
+// retrying until they succeed, however, such a simple scheme will fail if the
+// synchronous operations were to be used with an underlying TCP socket in
+// nonblocking mode. Currently, the underlying TCP socket is always in blocking
+// mode when performing synchronous operations, but that may continue to be the
+// case in teh future.
+
+
+namespace realm {
+namespace util {
+namespace network {
+namespace ssl {
+
+enum class Error {
+    certificate_rejected = 1,
+};
+
+const std::error_category& ssl_error_category();
+
+std::error_condition make_error_condition(Error value);
+
+} // namespace ssl
+} // namespace network
+} // namespace util
+} // namespace realm
+
+namespace std {
+
+template <>
+struct is_error_condition_enum<realm::util::network::ssl::Error>: public true_type {};
+
+} // namespace std
+
+namespace realm {
+namespace util {
+namespace network {
+namespace ssl {
+
+#if REALM_HAVE_OPENSSL
+
+const std::error_category& openssl_error_category();
+
+#elif REALM_HAVE_SECURE_TRANSPORT
+
+const std::error_category& secure_transport_error_category();
+
+#endif
+
+
+class ProtocolNotSupported;
+
+
+/// `VerifyMode::none` corresponds to OpenSSL's `SSL_VERIFY_NONE`, and
+/// `VerifyMode::peer` to `SSL_VERIFY_PEER`.
+enum class VerifyMode { none, peer };
+
+
+class Context {
+public:
+    Context();
+    ~Context() noexcept;
+
+    /// File must be in PEM format. Corresponds to OpenSSL's
+    /// `SSL_CTX_use_certificate_chain_file()`.
+    void use_certificate_chain_file(const std::string& path);
+
+    /// File must be in PEM format. Corresponds to OpenSSL's
+    /// `SSL_CTX_use_PrivateKey_file()`.
+    void use_private_key_file(const std::string& path);
+
+    /// Calling use_default_verify() will make a client use the
+    /// device default certificates for server verification.
+    /// For OpenSSL, use_default_verify() corresponds to
+    /// SSL_CTX_set_default_verify_paths(SSL_CTX*);
+    void use_default_verify();
+
+    /// The verify file is a PEM file containing trust
+    /// certificates that the client will use to
+    /// verify the server crtificate. If use_verify_file()
+    /// is not called, the default device trust store will
+    /// be used.
+    /// Corresponds roughly to OpenSSL's
+    /// SSL_CTX_load_verify_locations().
+    void use_verify_file(const std::string& path);
+
+private:
+    void ssl_init();
+    void ssl_destroy() noexcept;
+    void ssl_use_certificate_chain_file(const std::string& path, std::error_code&);
+    void ssl_use_private_key_file(const std::string& path, std::error_code&);
+    void ssl_use_default_verify(std::error_code&);
+    void ssl_use_verify_file(const std::string& path, std::error_code&);
+
+#if REALM_HAVE_OPENSSL
+    SSL_CTX* m_ssl_ctx = nullptr;
+
+#elif REALM_HAVE_SECURE_TRANSPORT
+
+#if REALM_HAVE_KEYCHAIN_APIS
+    static util::CFPtr<CFArrayRef> load_pem_file(const std::string& path, SecKeychainRef, std::error_code&);
+
+    std::error_code open_temporary_keychain_if_needed();
+    std::error_code update_identity_if_needed();
+
+    util::CFPtr<SecKeychainRef> m_keychain;
+    std::string m_keychain_path;
+
+    util::CFPtr<SecCertificateRef> m_certificate;
+    util::CFPtr<SecKeyRef> m_private_key;
+    util::CFPtr<SecIdentityRef> m_identity;
+
+    util::CFPtr<CFArrayRef> m_certificate_chain;
+
+    util::CFPtr<CFArrayRef> m_trust_anchors;
+#endif // REALM_HAVE_KEYCHAIN_APIS
+
+#endif
+
+    friend class Stream;
+};
+
+
+/// Switching between synchronous and asynchronous operations is allowed, but
+/// only in a nonoverlapping fashion. That is, a synchronous operation is not
+/// allowed to run concurrently with an asynchronous one on the same
+/// stream. Note that an asynchronous operation is considered to be running
+/// until its completion handler starts executing.
+class Stream {
+public:
+    using port_type = util::network::Endpoint::port_type;
+    using SSLVerifyCallback = bool(const std::string& server_address,
+                                   port_type server_port,
+                                   const char* pem_data,
+                                   size_t pem_size,
+                                   int preverify_ok,
+                                   int depth);
+
+    enum HandshakeType { client, server };
+
+    Stream(Socket&, Context&, HandshakeType);
+    ~Stream() noexcept;
+
+    /// \brief Set the certificate verification mode for this SSL stream.
+    ///
+    /// Corresponds to OpenSSL's `SSL_set_verify()` with null passed as
+    /// `verify_callback`.
+    ///
+    /// Clients should always set it to `VerifyMode::peer`, such that the client
+    /// verifies the servers certificate. Servers should only set it to
+    /// `VerifyMode::peer` if they want to request a certificate from the
+    /// client. When testing with self-signed certificates, it is necessary to
+    /// set it to `VerifyMode::none` for clients too.
+    ///
+    /// It is an error if this function is called after the handshake operation
+    /// is initiated.
+    ///
+    /// The default verify mode is `VerifyMode::none`.
+    void set_verify_mode(VerifyMode);
+
+    /// \brief Check the certificate against a host_name.
+    ///
+    /// set_check_host() includes a host name check in the
+    /// certificate verification. It is typically used by clients
+    /// to secure that the received certificate has a common name
+    /// or subject alternative name that matches \param host_name.
+    ///
+    /// set_check_host() is only useful if verify_mode is
+    /// set to VerifyMode::peer.
+    void set_check_host(std::string host_name);
+    const std::string& get_host_name();
+
+    /// get_server_port() and set_server_port() are getter and setter for
+    /// the server port. They are only used by the verify callback function
+    /// below.
+    port_type get_server_port();
+    void set_server_port(port_type server_port);
+
+    /// If use_verify_callback() is called, the SSL certificate chain of
+    /// the server is presented to callback, one certificate at a time.
+    /// The SSL connection is accepted if and only if callback returns true
+    /// for all certificates.
+    /// The signature of \param callback is
+    ///
+    /// bool(const std::string& server_address,
+    ///      port_type server_port,
+    ///      const char* pem_data,
+    ///      size_t pem_size,
+    ///      int preverify_ok,
+    ///      int depth);
+    //
+    /// server address and server_port is the address and port of the server
+    /// that a SSL connection is being established to.
+    /// pem_data is the certificate of length pem_size in
+    /// the PEM format. preverify_ok is OpenSSL's preverification of the
+    /// certificate. preverify_ok is either 0, or 1. If preverify_ok is 1,
+    /// OpenSSL has accepted the certificate and it will generally be safe
+    /// to trust that certificate. depth represents the position of the
+    /// certificate in the certificate chain sent by the server. depth = 0
+    /// represents the actual server certificate that should contain the
+    /// host name(server address) of the server. The highest depth is the
+    /// root certificate.
+    /// The callback function will receive the certificates starting from
+    /// the root certificate and moving down the chain until it reaches the
+    /// server's own certificate with a host name. The depth of the last
+    /// certificate is 0. The depth of the first certificate is chain
+    /// length - 1.
+    ///
+    /// The return value of the callback function decides whether the
+    /// client accepts the certificate. If the return value is false, the
+    /// processing of the certificate chain is interrupted and the SSL
+    /// connection is rejected. If the return value is true, the verification
+    /// process continues. If the callback function returns true for all
+    /// presented certificates including the depth == 0 certificate, the
+    /// SSL connection is accepted.
+    ///
+    /// A recommended way of using the callback function is to return true
+    /// if preverify_ok = 1 and depth > 0,
+    /// always check the host name if depth = 0,
+    /// and use an independent verification step if preverify_ok = 0.
+    ///
+    /// Another possible way of using the callback is to collect all the
+    /// certificates until depth = 0, and present the entire chain for
+    /// independent verification.
+    void use_verify_callback(const std::function<SSLVerifyCallback>& callback);
+
+    /// @{
+    ///
+    /// Read and write operations behave the same way as they do on \ref
+    /// network::Socket, except that after cancellation of asynchronous
+    /// operations (`lowest_layer().cancel()`), the stream may be left in a bad
+    /// state (see below).
+    ///
+    /// The handshake operation must complete successfully before any read,
+    /// write, or shutdown operations are performed.
+    ///
+    /// The shutdown operation sends the shutdown alert to the peer, and
+    /// returns/completes as soon as the alert message has been written to the
+    /// underlying socket. It is an error if the shutdown operation is initiated
+    /// while there are read or write operations in progress. No read or write
+    /// operations are allowed to be initiated after the shutdown operation has
+    /// been initiated. When the shutdown operation has completed, it is safe to
+    /// close the underlying socket (`lowest_layer().close()`).
+    ///
+    /// If a write operation is executing while, or is initiated after a close
+    /// notify alert is received from the remote peer, the write operation will
+    /// fail with error::broken_pipe.
+    ///
+    /// Callback functions for async read and write operations must take two
+    /// arguments, an std::error_code(), and an integer of a type std::size_t
+    /// indicating the number of transferred bytes (other types are allowed as
+    /// long as implicit conversion can take place).
+    ///
+    /// Callback functions for async handshake and shutdown operations must take
+    /// a single argument of type std::error_code() (other types are allowed as
+    /// long as implicit conversion can take place).
+    ///
+    /// Resumption of stream operation after cancellation of asynchronous
+    /// operations is not supported (does not work). Since the shutdown
+    /// operation involves network communication, that operation is also not
+    /// allowed after cancellation. The only thing that is allowed, is to
+    /// destroy the stream object. Other stream objects are not affected.
+
+    void handshake();
+    std::error_code handshake(std::error_code&);
+
+    std::size_t read(char* buffer, std::size_t size);
+    std::size_t read(char* buffer, std::size_t size, std::error_code& ec);
+    std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&);
+    std::size_t read(char* buffer, std::size_t size, ReadAheadBuffer&, std::error_code& ec);
+    std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&);
+    std::size_t read_until(char* buffer, std::size_t size, char delim, ReadAheadBuffer&,
+                           std::error_code& ec);
+
+    std::size_t write(const char* data, std::size_t size);
+    std::size_t write(const char* data, std::size_t size, std::error_code& ec);
+
+    std::size_t read_some(char* buffer, std::size_t size);
+    std::size_t read_some(char* buffer, std::size_t size, std::error_code&);
+
+    std::size_t write_some(const char* data, std::size_t size);
+    std::size_t write_some(const char* data, std::size_t size, std::error_code&);
+
+    void shutdown();
+    std::error_code shutdown(std::error_code&);
+
+    template<class H> void async_handshake(H handler);
+
+    template<class H> void async_read(char* buffer, std::size_t size, H handler);
+    template<class H> void async_read(char* buffer, std::size_t size, ReadAheadBuffer&, H handler);
+    template<class H> void async_read_until(char* buffer, std::size_t size, char delim,
+                                            ReadAheadBuffer&, H handler);
+
+    template<class H> void async_write(const char* data, std::size_t size, H handler);
+
+    template<class H> void async_read_some(char* buffer, std::size_t size, H handler);
+
+    template<class H> void async_write_some(const char* data, std::size_t size, H handler);
+
+    template<class H> void async_shutdown(H handler);
+
+    /// @}
+
+    /// Returns a reference to the underlying socket.
+    Socket& lowest_layer() noexcept;
+
+private:
+    using Want = Service::Want;
+    using StreamOps = Service::BasicStreamOps<Stream>;
+
+    class HandshakeOperBase;
+    template<class H> class HandshakeOper;
+    class ShutdownOperBase;
+    template<class H> class ShutdownOper;
+
+    using LendersHandshakeOperPtr = std::unique_ptr<HandshakeOperBase, Service::LendersOperDeleter>;
+    using LendersShutdownOperPtr  = std::unique_ptr<ShutdownOperBase,  Service::LendersOperDeleter>;
+
+    Socket& m_tcp_socket;
+    Context& m_ssl_context;
+    const HandshakeType m_handshake_type;
+
+    // The host name that the certificate should be checked against.
+    // The host name is called server address in the certificate verify
+    // callback function.
+    std::string m_host_name;
+
+    // The port of the server which is used in the certificate verify
+    // callback function.
+    port_type m_server_port;
+
+    // The callback for certificate verification and an
+    // opaque argument that will be supplied to the callback.
+    const std::function<SSLVerifyCallback>* m_ssl_verify_callback = nullptr;
+
+    // See Service::BasicStreamOps for details on these these 6 functions.
+    void do_init_read_async(std::error_code&, Want&) noexcept;
+    void do_init_write_async(std::error_code&, Want&) noexcept;
+    std::size_t do_read_some_sync(char* buffer, std::size_t size,
+                                  std::error_code&) noexcept;
+    std::size_t do_write_some_sync(const char* data, std::size_t size,
+                                   std::error_code&) noexcept;
+    std::size_t do_read_some_async(char* buffer, std::size_t size,
+                                   std::error_code&, Want&) noexcept;
+    std::size_t do_write_some_async(const char* data, std::size_t size,
+                                    std::error_code&, Want&) noexcept;
+
+    // The meaning of the arguments and return values of ssl_read() and
+    // ssl_write() are identical to do_read_some_async() and
+    // do_write_some_async() respectively, except that when the return value is
+    // nonzero, `want` is always `Want::nothing`, meaning that after bytes have
+    // been transferred, ssl_read() and ssl_write() must be called again to
+    // figure out whether it is necessary to wait for read or write readiness.
+    //
+    // The first invocation of ssl_shutdown() must send the shutdown alert to
+    // the peer. In blocking mode it must wait until the alert has been sent. In
+    // nonblocking mode, it must keep setting `want` to something other than
+    // `Want::nothing` until the alert has been sent. When the shutdown alert
+    // has been sent, it is safe to shut down the sending side of the underlying
+    // socket. On failure, ssl_shutdown() must set `ec` to something different
+    // than `std::error_code()` and return false. On success, it must set `ec`
+    // to `std::error_code()`, and return true if a shutdown alert from the peer
+    // has already been received, otherwise it must return false. When it sets
+    // `want` to something other than `Want::nothing`, it must set `ec` to
+    // `std::error_code()` and return false.
+    //
+    // The second invocation of ssl_shutdown() (after the first invocation
+    // completed) must wait for reception on the peers shutdown alert.
+    //
+    // Note: The semantics around the second invocation of shutdown is currently
+    // unused by the higher level API, because of a requirement of compatibility
+    // with Apple's Secure Transport API.
+    void ssl_init();
+    void ssl_destroy() noexcept;
+    void ssl_set_verify_mode(VerifyMode, std::error_code&);
+    void ssl_set_check_host(std::string, std::error_code&);
+    void ssl_use_verify_callback(const std::function<SSLVerifyCallback>&, std::error_code&);
+
+    void ssl_handshake(std::error_code&, Want& want) noexcept;
+    bool ssl_shutdown(std::error_code& ec, Want& want) noexcept;
+    std::size_t ssl_read(char* buffer, std::size_t size,
+                         std::error_code&, Want& want) noexcept;
+    std::size_t ssl_write(const char* data, std::size_t size,
+                          std::error_code&, Want& want) noexcept;
+
+#if REALM_HAVE_OPENSSL
+    class BioMethod;
+    static BioMethod s_bio_method;
+    SSL* m_ssl = nullptr;
+    std::error_code m_bio_error_code;
+
+    int m_ssl_index = -1;
+
+    template<class Oper>
+    std::size_t ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept;
+
+    int do_ssl_accept() noexcept;
+    int do_ssl_connect() noexcept;
+    int do_ssl_shutdown() noexcept;
+    int do_ssl_read(char* buffer, std::size_t size) noexcept;
+    int do_ssl_write(const char* data, std::size_t size) noexcept;
+
+    static int bio_write(BIO*, const char*, int) noexcept;
+    static int bio_read(BIO*, char*, int) noexcept;
+    static int bio_puts(BIO*, const char*) noexcept;
+    static long bio_ctrl(BIO*, int, long, void*) noexcept;
+    static int bio_create(BIO*) noexcept;
+    static int bio_destroy(BIO*) noexcept;
+
+    // verify_callback_using_hostname is used as an argument to OpenSSL's SSL_set_verify function.
+    // verify_callback_using_hostname verifies that the certificate is valid and contains
+    // m_host_name as a Common Name or Subject Alternative Name.
+    static int verify_callback_using_hostname(int preverify_ok, X509_STORE_CTX *ctx) noexcept;
+
+    // verify_callback_using_delegate() is also used as an argument to OpenSSL's set_verify_function.
+    // verify_callback_using_delegate() calls out to the user supplied verify callback.
+    static int verify_callback_using_delegate(int preverify_ok, X509_STORE_CTX *ctx) noexcept;
+#elif REALM_HAVE_SECURE_TRANSPORT
+    util::CFPtr<SSLContextRef> m_ssl;
+    VerifyMode m_verify_mode = VerifyMode::none;
+
+    enum class BlockingOperation {
+        read,
+        write,
+    };
+    util::Optional<BlockingOperation> m_last_operation;
+
+    // Details of the underlying I/O error that lead to errSecIO being returned
+    // from a SecureTransport function.
+    std::error_code m_last_error;
+
+    // The number of bytes accepted by SSWrite() but not yet confirmed to be
+    // written to the underlying socket.
+    std::size_t m_num_partially_written_bytes = 0;
+
+    template<class Oper>
+    std::size_t ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept;
+
+    std::pair<OSStatus, std::size_t> do_ssl_handshake() noexcept;
+    std::pair<OSStatus, std::size_t> do_ssl_shutdown() noexcept;
+    std::pair<OSStatus, std::size_t> do_ssl_read(char* buffer, std::size_t size) noexcept;
+    std::pair<OSStatus, std::size_t> do_ssl_write(const char* data, std::size_t size) noexcept;
+
+    static OSStatus tcp_read(SSLConnectionRef, void*, std::size_t* length) noexcept;
+    static OSStatus tcp_write(SSLConnectionRef, const void*, std::size_t* length) noexcept;
+
+    OSStatus tcp_read(void*, std::size_t* length) noexcept;
+    OSStatus tcp_write(const void*, std::size_t* length) noexcept;
+
+    OSStatus verify_peer() noexcept;
+#endif
+
+    friend class Service::BasicStreamOps<Stream>;
+    friend class network::ReadAheadBuffer;
+};
+
+
+// Implementation
+
+class ProtocolNotSupported: public std::exception {
+public:
+    const char* what() const noexcept override final;
+};
+
+inline Context::Context()
+{
+    ssl_init(); // Throws
+}
+
+inline Context::~Context() noexcept
+{
+    ssl_destroy();
+}
+
+inline void Context::use_certificate_chain_file(const std::string& path)
+{
+    std::error_code ec;
+    ssl_use_certificate_chain_file(path, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+}
+
+inline void Context::use_private_key_file(const std::string& path)
+{
+    std::error_code ec;
+    ssl_use_private_key_file(path, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+}
+
+inline void Context::use_default_verify()
+{
+    std::error_code ec;
+    ssl_use_default_verify(ec);
+    if (ec)
+        throw std::system_error(ec);
+}
+
+inline void Context::use_verify_file(const std::string& path)
+{
+    std::error_code ec;
+    ssl_use_verify_file(path, ec);
+    if (ec)
+        throw std::system_error(ec);
+}
+
+class Stream::HandshakeOperBase: public Service::IoOper {
+public:
+    HandshakeOperBase(std::size_t size, Stream& stream):
+        IoOper{size},
+        m_stream{&stream}
+    {
+    }
+    Want initiate()
+    {
+        REALM_ASSERT(this == m_stream->m_tcp_socket.m_read_oper.get());
+        REALM_ASSERT(!is_complete());
+        m_stream->m_tcp_socket.m_desc.ensure_nonblocking_mode(); // Throws
+        return advance();
+    }
+    Want advance() noexcept override final
+    {
+        REALM_ASSERT(!is_complete());
+        REALM_ASSERT(!is_canceled());
+        REALM_ASSERT(!m_error_code);
+        Want want = Want::nothing;
+        m_stream->ssl_handshake(m_error_code, want);
+        set_is_complete(want == Want::nothing);
+        return want;
+    }
+    void recycle() noexcept override final
+    {
+        bool orphaned = !m_stream;
+        REALM_ASSERT(orphaned);
+        // Note: do_recycle() commits suicide.
+        do_recycle(orphaned);
+    }
+    void orphan() noexcept override final
+    {
+        m_stream = nullptr;
+    }
+    Service::Descriptor& descriptor() noexcept override final
+    {
+        return m_stream->lowest_layer().m_desc;
+    }
+protected:
+    Stream* m_stream;
+    std::error_code m_error_code;
+};
+
+template<class H> class Stream::HandshakeOper: public HandshakeOperBase {
+public:
+    HandshakeOper(std::size_t size, Stream& stream, H handler):
+        HandshakeOperBase{size, stream},
+        m_handler{std::move(handler)}
+    {
+    }
+    void recycle_and_execute() override final
+    {
+        REALM_ASSERT(is_complete() || is_canceled());
+        bool orphaned = !m_stream;
+        std::error_code ec = m_error_code;
+        if (is_canceled())
+            ec = error::operation_aborted;
+        // Note: do_recycle_and_execute() commits suicide.
+        do_recycle_and_execute<H>(orphaned, m_handler, ec); // Throws
+    }
+private:
+    H m_handler;
+};
+
+class Stream::ShutdownOperBase: public Service::IoOper {
+public:
+    ShutdownOperBase(std::size_t size, Stream& stream):
+        IoOper{size},
+        m_stream{&stream}
+    {
+    }
+    Want initiate()
+    {
+        REALM_ASSERT(this == m_stream->m_tcp_socket.m_write_oper.get());
+        REALM_ASSERT(!is_complete());
+        m_stream->m_tcp_socket.m_desc.ensure_nonblocking_mode(); // Throws
+        return advance();
+    }
+    Want advance() noexcept override final
+    {
+        REALM_ASSERT(!is_complete());
+        REALM_ASSERT(!is_canceled());
+        REALM_ASSERT(!m_error_code);
+        Want want = Want::nothing;
+        m_stream->ssl_shutdown(m_error_code, want);
+        if (want == Want::nothing)
+            set_is_complete(true);
+        return want;
+    }
+    void recycle() noexcept override final
+    {
+        bool orphaned = !m_stream;
+        REALM_ASSERT(orphaned);
+        // Note: do_recycle() commits suicide.
+        do_recycle(orphaned);
+    }
+    void orphan() noexcept override final
+    {
+        m_stream = nullptr;
+    }
+    Service::Descriptor& descriptor() noexcept override final
+    {
+        return m_stream->lowest_layer().m_desc;
+    }
+protected:
+    Stream* m_stream;
+    std::error_code m_error_code;
+};
+
+template<class H> class Stream::ShutdownOper: public ShutdownOperBase {
+public:
+    ShutdownOper(std::size_t size, Stream& stream, H handler):
+        ShutdownOperBase{size, stream},
+        m_handler{std::move(handler)}
+    {
+    }
+    void recycle_and_execute() override final
+    {
+        REALM_ASSERT(is_complete() || is_canceled());
+        bool orphaned = !m_stream;
+        std::error_code ec = m_error_code;
+        if (is_canceled())
+            ec = error::operation_aborted;
+        // Note: do_recycle_and_execute() commits suicide.
+        do_recycle_and_execute<H>(orphaned, m_handler, ec); // Throws
+    }
+private:
+    H m_handler;
+};
+
+inline Stream::Stream(Socket& socket, Context& context, HandshakeType type):
+    m_tcp_socket{socket},
+    m_ssl_context{context},
+    m_handshake_type{type}
+{
+    ssl_init(); // Throws
+}
+
+inline Stream::~Stream() noexcept
+{
+    m_tcp_socket.cancel();
+    ssl_destroy();
+}
+
+inline void Stream::set_verify_mode(VerifyMode mode)
+{
+    std::error_code ec;
+    ssl_set_verify_mode(mode, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+}
+
+inline void Stream::set_check_host(std::string host_name)
+{
+    m_host_name = host_name;
+    std::error_code ec;
+    ssl_set_check_host(host_name, ec);
+    if (ec)
+        throw std::system_error(ec);
+}
+
+inline const std::string& Stream::get_host_name()
+{
+    return m_host_name;
+}
+
+inline Stream::port_type Stream::get_server_port()
+{
+    return m_server_port;
+}
+
+inline void Stream::set_server_port(port_type server_port)
+{
+    m_server_port = server_port;
+}
+
+inline void Stream::use_verify_callback(const std::function<SSLVerifyCallback>& callback)
+{
+    std::error_code ec;
+    ssl_use_verify_callback(callback, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+}
+inline void Stream::handshake()
+{
+    std::error_code ec;
+    if (handshake(ec)) // Throws
+        throw std::system_error(ec);
+}
+
+inline std::size_t Stream::read(char* buffer, std::size_t size)
+{
+    std::error_code ec;
+    read(buffer, size, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+    return size;
+}
+
+inline std::size_t Stream::read(char* buffer, std::size_t size, std::error_code& ec)
+{
+    return StreamOps::read(*this, buffer, size, ec); // Throws
+}
+
+inline std::size_t Stream::read(char* buffer, std::size_t size, ReadAheadBuffer& rab)
+{
+    std::error_code ec;
+    read(buffer, size, rab, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+    return size;
+}
+
+inline std::size_t Stream::read(char* buffer, std::size_t size, ReadAheadBuffer& rab,
+                                std::error_code& ec)
+{
+    int delim = std::char_traits<char>::eof();
+    return StreamOps::buffered_read(*this, buffer, size, delim, rab, ec); // Throws
+}
+
+inline std::size_t Stream::read_until(char* buffer, std::size_t size, char delim,
+                                      ReadAheadBuffer& rab)
+{
+    std::error_code ec;
+    std::size_t n = read_until(buffer, size, delim, rab, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+    return n;
+}
+
+inline std::size_t Stream::read_until(char* buffer, std::size_t size, char delim,
+                                      ReadAheadBuffer& rab, std::error_code& ec)
+{
+    int delim_2 = std::char_traits<char>::to_int_type(delim);
+    return StreamOps::buffered_read(*this, buffer, size, delim_2, rab, ec); // Throws
+}
+
+inline std::size_t Stream::write(const char* data, std::size_t size)
+{
+    std::error_code ec;
+    write(data, size, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+    return size;
+}
+
+inline std::size_t Stream::write(const char* data, std::size_t size, std::error_code& ec)
+{
+    return StreamOps::write(*this, data, size, ec); // Throws
+}
+
+inline std::size_t Stream::read_some(char* buffer, std::size_t size)
+{
+    std::error_code ec;
+    std::size_t n = read_some(buffer, size, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+    return n;
+}
+
+inline std::size_t Stream::read_some(char* buffer, std::size_t size, std::error_code& ec)
+{
+    return StreamOps::read_some(*this, buffer, size, ec); // Throws
+}
+
+inline std::size_t Stream::write_some(const char* data, std::size_t size)
+{
+    std::error_code ec;
+    std::size_t n = write_some(data, size, ec); // Throws
+    if (ec)
+        throw std::system_error(ec);
+    return n;
+}
+
+inline std::size_t Stream::write_some(const char* data, std::size_t size, std::error_code& ec)
+{
+    return StreamOps::write_some(*this, data, size, ec); // Throws
+}
+
+inline void Stream::shutdown()
+{
+    std::error_code ec;
+    if (shutdown(ec)) // Throws
+        throw std::system_error(ec);
+}
+
+template<class H> inline void Stream::async_handshake(H handler)
+{
+    LendersHandshakeOperPtr op =
+        Service::alloc<HandshakeOper<H>>(m_tcp_socket.m_read_oper, *this,
+                                         std::move(handler)); // Throws
+    m_tcp_socket.m_desc.initiate_oper(std::move(op)); // Throws
+}
+
+template<class H> inline void Stream::async_read(char* buffer, std::size_t size, H handler)
+{
+    bool is_read_some = false;
+    StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws
+}
+
+template<class H>
+inline void Stream::async_read(char* buffer, std::size_t size, ReadAheadBuffer& rab, H handler)
+{
+    int delim = std::char_traits<char>::eof();
+    StreamOps::async_buffered_read(*this, buffer, size, delim, rab, std::move(handler)); // Throws
+}
+
+template<class H>
+inline void Stream::async_read_until(char* buffer, std::size_t size, char delim,
+                                     ReadAheadBuffer& rab, H handler)
+{
+    int delim_2 = std::char_traits<char>::to_int_type(delim);
+    StreamOps::async_buffered_read(*this, buffer, size, delim_2, rab, std::move(handler)); // Throws
+}
+
+template<class H> inline void Stream::async_write(const char* data, std::size_t size, H handler)
+{
+    bool is_write_some = false;
+    StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws
+}
+
+template<class H> inline void Stream::async_read_some(char* buffer, std::size_t size, H handler)
+{
+    bool is_read_some = true;
+    StreamOps::async_read(*this, buffer, size, is_read_some, std::move(handler)); // Throws
+}
+
+template<class H> inline void Stream::async_write_some(const char* data, std::size_t size, H handler)
+{
+    bool is_write_some = true;
+    StreamOps::async_write(*this, data, size, is_write_some, std::move(handler)); // Throws
+}
+
+template<class H> inline void Stream::async_shutdown(H handler)
+{
+    LendersShutdownOperPtr op =
+        Service::alloc<ShutdownOper<H>>(m_tcp_socket.m_write_oper, *this,
+                                        std::move(handler)); // Throws
+    m_tcp_socket.m_desc.initiate_oper(std::move(op)); // Throws
+}
+
+inline void Stream::do_init_read_async(std::error_code&, Want& want) noexcept
+{
+    want = Want::nothing; // Proceed immediately unless there is an error
+}
+
+inline void Stream::do_init_write_async(std::error_code&, Want& want) noexcept
+{
+    want = Want::nothing; // Proceed immediately unless there is an error
+}
+
+inline std::size_t Stream::do_read_some_sync(char* buffer, std::size_t size,
+                                             std::error_code& ec) noexcept
+{
+    Want want = Want::nothing;
+    std::size_t n = do_read_some_async(buffer, size, ec, want);
+    if (n == 0 && want != Want::nothing)
+        ec = error::resource_unavailable_try_again;
+    return n;
+}
+
+inline std::size_t Stream::do_write_some_sync(const char* data, std::size_t size,
+                                              std::error_code& ec) noexcept
+{
+    Want want = Want::nothing;
+    std::size_t n = do_write_some_async(data, size, ec, want);
+    if (n == 0 && want != Want::nothing)
+        ec = error::resource_unavailable_try_again;
+    return n;
+}
+
+inline std::size_t Stream::do_read_some_async(char* buffer, std::size_t size,
+                                              std::error_code& ec, Want& want) noexcept
+{
+    return ssl_read(buffer, size, ec, want);
+}
+
+inline std::size_t Stream::do_write_some_async(const char* data, std::size_t size,
+                                               std::error_code& ec, Want& want) noexcept
+{
+    return ssl_write(data, size, ec, want);
+}
+
+inline Socket& Stream::lowest_layer() noexcept
+{
+    return m_tcp_socket;
+}
+
+
+#if REALM_HAVE_OPENSSL
+
+inline void Stream::ssl_handshake(std::error_code& ec, Want& want) noexcept
+{
+    auto perform = [this]() noexcept {
+        switch (m_handshake_type) {
+            case client:
+                return do_ssl_connect();
+            case server:
+                return do_ssl_accept();
+        }
+        REALM_ASSERT(false);
+        return 0;
+    };
+    std::size_t n = ssl_perform(std::move(perform), ec, want);
+    REALM_ASSERT(n == 0 || n == 1);
+    if (want == Want::nothing && n == 0 && !ec) {
+        // End of input on TCP socket
+        ec = network::premature_end_of_input;
+    }
+}
+
+inline std::size_t Stream::ssl_read(char* buffer, std::size_t size,
+                                    std::error_code& ec, Want& want) noexcept
+{
+    auto perform = [this, buffer, size]() noexcept {
+        return do_ssl_read(buffer, size);
+    };
+    std::size_t n = ssl_perform(std::move(perform), ec, want);
+    if (want == Want::nothing && n == 0 && !ec) {
+        // End of input on TCP socket
+        if (SSL_get_shutdown(m_ssl) & SSL_RECEIVED_SHUTDOWN) {
+            ec = network::end_of_input;
+        }
+        else {
+            ec = network::premature_end_of_input;
+        }
+    }
+    return n;
+}
+
+inline std::size_t Stream::ssl_write(const char* data, std::size_t size,
+                                     std::error_code& ec, Want& want) noexcept
+{
+    // While OpenSSL is able to continue writing after we have received the
+    // close notify alert fro the remote peer, Apple's Secure Transport API is
+    // not, so to achieve common behaviour, we make sure that any such attempt
+    // will result in an `error::broken_pipe` error.
+    if ((SSL_get_shutdown(m_ssl) & SSL_RECEIVED_SHUTDOWN) != 0) {
+        ec = error::broken_pipe;
+        want = Want::nothing;
+        return 0;
+    }
+    auto perform = [this, data, size]() noexcept {
+        return do_ssl_write(data, size);
+    };
+    std::size_t n = ssl_perform(std::move(perform), ec, want);
+    if (want == Want::nothing && n == 0 && !ec) {
+        // End of input on TCP socket
+        ec = network::premature_end_of_input;
+    }
+    return n;
+}
+
+inline bool Stream::ssl_shutdown(std::error_code& ec, Want& want) noexcept
+{
+    auto perform = [this]() noexcept {
+        return do_ssl_shutdown();
+    };
+    std::size_t n = ssl_perform(std::move(perform), ec, want);
+    REALM_ASSERT(n == 0 || n == 1);
+    if (want == Want::nothing && n == 0 && !ec) {
+        // The first invocation of SSL_shutdown() does not signal completion
+        // until the shutdown alert has been sent to the peer, or an error
+        // occurred (does not wait for acknowledgment).
+        //
+        // The second invocation (after a completed first invocation) does not
+        // signal completion until the peers shutdown alert has been received,
+        // or an error occurred.
+        //
+        // It is believed that:
+        //
+        // If this is the first time SSL_shutdown() is called, and
+        // `SSL_get_shutdown() & SSL_SENT_SHUTDOWN` evaluates to nonzero, then a
+        // zero return value means "partial success" (shutdown alert was sent,
+        // but the peers shutdown alert was not yet received), and 1 means "full
+        // success" (peers shutdown alert has already been received).
+        //
+        // If this is the first time SSL_shutdown() is called, and
+        // `SSL_get_shutdown() & SSL_SENT_SHUTDOWN` valuates to zero, then a
+        // zero return value means "premature end of input", and 1 is supposedly
+        // not a possibility.
+        //
+        // If this is the second time SSL_shutdown() is called (after the first
+        // call has returned zero), then a zero return value means "premature
+        // end of input", and 1 means "full success" (peers shutdown alert has
+        // now been received).
+        if ((SSL_get_shutdown(m_ssl) & SSL_SENT_SHUTDOWN) == 0)
+            ec = network::premature_end_of_input;
+    }
+    return (n > 0);
+}
+
+// Provides a homogeneous, and mostly quirks-free interface across the OpenSSL
+// operations (handshake, read, write, shutdown).
+//
+// First of all, if the operation remains incomplete (neither successfully
+// completed, nor failed), ssl_perform() will set `ec` to `std::system_error()`,
+// `want` to something other than `Want::nothing`, and return zero. Note that
+// read and write operations are partial in the sense that they do not need to
+// read or write everything before completing successfully. They only need to
+// read or write at least one byte to complete successfully.
+//
+// Such a situation will normally only happen when the underlying TCP socket is
+// in nonblocking mode, and the read/write requirements of the operation could
+// not be immediately accommodated. However, as is noted in the SSL_write() man
+// page, it can also happen in blocking mode (at least while writing).
+//
+// If an error occurred, ssl_perform() will set `ec` to something other than
+// `std::system_error()`, `want` to `Want::nothing`, and return 0.
+//
+// If no error occurred, and the operation completed (`!ec && want ==
+// Want::nothing`), then the return value indicates the outcome of the
+// operation.
+//
+// In general, a nonzero value means "full" success, and a zero value means
+// "partial" success, however, a zero result can also generally mean "premature
+// end of input" / "unclean protocol termination".
+//
+// Assuming there is no premature end of input, then for reads and writes, the
+// returned value is the number of transferred bytes. Zero for read on end of
+// input. Never zero for write. For handshake it is always 1. For shutdown it is
+// 1 if the peer shutdown alert was already received, otherwise it is zero.
+//
+// ssl_read() should use `SSL_get_shutdown() & SSL_RECEIVED_SHUTDOWN` to
+// distinguish between the two possible meanings of zero.
+//
+// ssl_shutdown() should use `SSL_get_shutdown() & SSL_SENT_SHUTDOWN` to
+// distinguish between the two possible meanings of zero.
+template<class Oper>
+std::size_t Stream::ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept
+{
+    ERR_clear_error();
+    m_bio_error_code = std::error_code(); // Success
+    int ret = oper();
+    int ssl_error = SSL_get_error(m_ssl, ret);
+    int sys_error = int(ERR_get_error());
+
+    // Guaranteed by the documentation of SSL_get_error()
+    REALM_ASSERT((ret > 0) == (ssl_error == SSL_ERROR_NONE));
+
+    REALM_ASSERT(!m_bio_error_code || ssl_error == SSL_ERROR_SYSCALL);
+
+    // Judging from various comments in the man pages, and from experience with
+    // the API, it seems that,
+    //
+    //   ret=0, ssl_error=SSL_ERROR_SYSCALL, sys_error=0
+    //
+    // is supposed to be an indicator of "premature end of input" / "unclean
+    // protocol termination", while
+    //
+    //   ret=0, ssl_error=SSL_ERROR_ZERO_RETURN
+    //
+    // is supposed to be an indicator of the following success conditions:
+    //
+    //   - Mature end of input / clean protocol termination.
+    //
+    //   - Successful transmission of the shutdown alert, but no prior reception
+    //     of shutdown alert from peer.
+    //
+    // Unfortunately, as is also remarked in various places in the man pages,
+    // those two success conditions may actually result in `ret=0,
+    // ssl_error=SSL_ERROR_SYSCALL, sys_error=0` too, and it seems that they
+    // almost always do.
+    //
+    // This means that we cannot properly discriminate between these conditions
+    // in ssl_perform(), and will have to defer to the caller to interpret the
+    // situation. Since thay cannot be properly told apart, we report all
+    // `ret=0, ssl_error=SSL_ERROR_SYSCALL, sys_error=0` and `ret=0,
+    // ssl_error=SSL_ERROR_ZERO_RETURN` cases as the latter.
+    switch (ssl_error) {
+        case SSL_ERROR_NONE:
+            ec = std::error_code(); // Success
+            want = Want::nothing;
+            return std::size_t(ret); // ret > 0
+        case SSL_ERROR_ZERO_RETURN:
+            ec = std::error_code(); // Success
+            want = Want::nothing;
+            return 0;
+        case SSL_ERROR_WANT_READ:
+            ec = std::error_code(); // Success
+            want = Want::read;
+            return 0;
+        case SSL_ERROR_WANT_WRITE:
+            ec = std::error_code(); // Success
+            want = Want::write;
+            return 0;
+        case SSL_ERROR_SYSCALL:
+            if (REALM_UNLIKELY(sys_error != 0)) {
+                ec = make_basic_system_error_code(sys_error);
+            }
+            else if (REALM_UNLIKELY(m_bio_error_code)) {
+                ec = m_bio_error_code;
+            }
+            else if (ret == 0) {
+                // ret = 0, ssl_eror = SSL_ERROR_SYSCALL, sys_error = 0
+                //
+                // See remarks above!
+                ec = std::error_code(); // Success
+            }
+            else {
+                // ret = -1, ssl_eror = SSL_ERROR_SYSCALL, sys_error = 0
+                //
+                // This situation arises in OpenSSL version >= 1.1.
+                // It has been observed in the SSL_connect call if the
+                // other endpoint terminates the connection during
+                // SSL_connect. The OpenSSL documentation states
+                // that ret = -1 implies an underlying BIO error and
+                // that errno should be consulted. However,
+                // errno = 0(Undefined error) in the observed case.
+                // At the moment. we will report
+                // premature_end_of_input.
+                // If we see this error case occurring in other situations in
+                // the future, we will have to update this case.
+                ec = network::premature_end_of_input;
+            }
+            want = Want::nothing;
+            return 0;
+        case SSL_ERROR_SSL:
+            ec = std::error_code(sys_error, openssl_error_category());
+            want = Want::nothing;
+            return 0;
+        default:
+            break;
+    }
+    // We are not supposed to ever get here
+    REALM_ASSERT(false);
+    return 0;
+}
+
+inline int Stream::do_ssl_accept() noexcept
+{
+    int ret = SSL_accept(m_ssl);
+    return ret;
+}
+
+inline int Stream::do_ssl_connect() noexcept
+{
+    int ret = SSL_connect(m_ssl);
+    return ret;
+}
+
+inline int Stream::do_ssl_read(char* buffer, std::size_t size) noexcept
+{
+    int size_2 = int(size);
+    if (size > unsigned(std::numeric_limits<int>::max()))
+        size_2 = std::size_t(std::numeric_limits<int>::max());
+    int ret = SSL_read(m_ssl, buffer, size_2);
+    return ret;
+}
+
+inline int Stream::do_ssl_write(const char* data, std::size_t size) noexcept
+{
+    int size_2 = int(size);
+    if (size > unsigned(std::numeric_limits<int>::max()))
+        size_2 = std::size_t(std::numeric_limits<int>::max());
+    int ret = SSL_write(m_ssl, data, size_2);
+    return ret;
+}
+
+inline int Stream::do_ssl_shutdown() noexcept
+{
+    int ret = SSL_shutdown(m_ssl);
+    return ret;
+}
+
+#elif REALM_HAVE_SECURE_TRANSPORT
+
+// Provides a homogeneous, and mostly quirks-free interface across the SecureTransport
+// operations (handshake, read, write, shutdown).
+//
+// First of all, if the operation remains incomplete (neither successfully
+// completed, nor failed), ssl_perform() will set `ec` to `std::system_error()`,
+// `want` to something other than `Want::nothing`, and return zero.
+//
+// If an error occurred, ssl_perform() will set `ec` to something other than
+// `std::system_error()`, `want` to `Want::nothing`, and return 0.
+//
+// If no error occurred, and the operation completed (`!ec && want ==
+// Want::nothing`), then the return value indicates the outcome of the
+// operation.
+//
+// In general, a nonzero value means "full" success, and a zero value means
+// "partial" success, however, a zero result can also generally mean "premature
+// end of input" / "unclean protocol termination".
+//
+// Assuming there is no premature end of input, then for reads and writes, the
+// returned value is the number of transferred bytes. Zero for read on end of
+// input. Never zero for write. For handshake it is always 1. For shutdown it is
+// 1 if the peer shutdown alert was already received, otherwise it is zero.
+template<class Oper>
+std::size_t Stream::ssl_perform(Oper oper, std::error_code& ec, Want& want) noexcept
+{
+    OSStatus result;
+    std::size_t n;
+    std::tie(result, n) = oper();
+
+    if (result == noErr) {
+        ec = std::error_code();
+        want = Want::nothing;
+        return n;
+    }
+
+    if (result == errSSLWouldBlock) {
+        REALM_ASSERT(m_last_operation);
+        ec = std::error_code();
+        want = m_last_operation == BlockingOperation::read ? Want::read : Want::write;
+        m_last_operation = {};
+        return n;
+    }
+
+    if (result == errSSLClosedGraceful) {
+        ec = network::end_of_input;
+        want = Want::nothing;
+        return n;
+    }
+
+    if (result == errSSLClosedAbort || result == errSSLClosedNoNotify) {
+        ec = network::premature_end_of_input;
+        want = Want::nothing;
+        return n;
+    }
+
+    if (result == errSecIO) {
+        // A generic I/O error means something went wrong at a lower level. Use the error
+        // code we smuggled out of our lower-level functions to provide a more specific error.
+        REALM_ASSERT(m_last_error);
+        ec = m_last_error;
+        want = Want::nothing;
+        return n;
+    }
+
+    ec = std::error_code(result, secure_transport_error_category());
+    want = Want::nothing;
+    return 0;
+}
+#endif // REALM_HAVE_OPENSSL / REALM_HAVE_SECURE_TRANSPORT
+
+} // namespace ssl
+} // namespace network
+} // namespace util
+} // namespace realm
+
+#endif // REALM_UTIL_NETWORK_SSL_HPP