Newer
Older
bremer-ios-app / Pods / Realm / core / realm-monorepo.xcframework / watchos-arm64_armv7k_arm64_32 / Headers / realm / sync / client_base.hpp
#ifndef REALM_SYNC_CLIENT_BASE_HPP
#define REALM_SYNC_CLIENT_BASE_HPP

#include <realm/db.hpp>
#include <realm/sync/config.hpp>
#include <realm/sync/protocol.hpp>
#include <realm/util/functional.hpp>

namespace realm::sync {
class ClientImpl;
class SessionWrapper;
class SyncSocketProvider;

/// The presence of the ClientReset config indicates an ongoing or requested client
/// reset operation. If client_reset is util::none or if the local Realm does not
/// exist, an ordinary sync session will take place.
///
/// A session will perform client reset by downloading a fresh copy of the Realm
/// from the server at a different file path location. After download, the fresh
/// Realm will be integrated into the local Realm in a write transaction. The
/// application is free to read or write to the local realm during the entire client
/// reset. Like a DOWNLOAD message, the application will not be able to perform a
/// write transaction at the same time as the sync client performs its own write
/// transaction. Client reset is not more disturbing for the application than any
/// DOWNLOAD message. The application can listen to change notifications from the
/// client reset exactly as in a DOWNLOAD message. If the application writes to the
/// local realm during client reset but before the client reset operation has
/// obtained a write lock, the changes made by the application may be lost or
/// overwritten depending on the recovery mode selected.
///
/// Client reset downloads its fresh Realm copy for a Realm at path "xyx.realm" to
/// "xyz.realm.fresh". It is assumed that this path is available for use and if
/// there are any problems the client reset will fail with
/// Client::Error::client_reset_failed.
///
/// The recommended usage of client reset is after a previous session encountered an
/// error that implies the need for a client reset. It is not recommended to persist
/// the need for a client reset. The application should just attempt to synchronize
/// in the usual fashion and only after hitting an error, start a new session with a
/// client reset. In other words, if the application crashes during a client reset,
/// the application should attempt to perform ordinary synchronization after restart
/// and switch to client reset if needed.
///
/// Error codes that imply the need for a client reset are the session level error
/// codes described by SyncError::is_client_reset_requested()
///
/// However, other errors such as bad changeset (UPLOAD) could also be resolved with
/// a client reset. Client reset can even be used without any prior error if so
/// desired.
///
/// After completion of a client reset, the sync client will continue synchronizing
/// with the server in the usual fashion.
///
/// The progress of client reset can be tracked with the standard progress handler.
///
/// Client reset is done when the progress handler arguments satisfy
/// "progress_version > 0". However, if the application wants to ensure that it has
/// all data present on the server, it should wait for download completion using
/// either void async_wait_for_download_completion(WaitOperCompletionHandler) or
/// bool wait_for_download_complete_or_client_stopped().
struct ClientReset {
    realm::ClientResyncMode mode;
    DBRef fresh_copy;
    bool recovery_is_allowed = true;
    util::UniqueFunction<VersionID()> notify_before_client_reset;
    util::UniqueFunction<void(VersionID before_version, bool did_recover)> notify_after_client_reset;
};

/// \brief Protocol errors discovered by the client.
///
/// These errors will terminate the network connection (disconnect all sessions
/// associated with the affected connection), and the error will be reported to
/// the application via the connection state change listeners of the affected
/// sessions.
enum class ClientError {
    // clang-format off
    connection_closed           = RLM_SYNC_ERR_CLIENT_CONNECTION_CLOSED          , ///< Connection closed (no error)
    unknown_message             = RLM_SYNC_ERR_CLIENT_UNKNOWN_MESSAGE            , ///< Unknown type of input message
    bad_syntax                  = RLM_SYNC_ERR_CLIENT_BAD_SYNTAX                 , ///< Bad syntax in input message head
    limits_exceeded             = RLM_SYNC_ERR_CLIENT_LIMITS_EXCEEDED            , ///< Limits exceeded in input message
    bad_session_ident           = RLM_SYNC_ERR_CLIENT_BAD_SESSION_IDENT          , ///< Bad session identifier in input message
    bad_message_order           = RLM_SYNC_ERR_CLIENT_BAD_MESSAGE_ORDER          , ///< Bad input message order
    bad_client_file_ident       = RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT      , ///< Bad client file identifier (IDENT)
    bad_progress                = RLM_SYNC_ERR_CLIENT_BAD_PROGRESS               , ///< Bad progress information (DOWNLOAD)
    bad_changeset_header_syntax = RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_HEADER_SYNTAX, ///< Bad syntax in changeset header (DOWNLOAD)
    bad_changeset_size          = RLM_SYNC_ERR_CLIENT_BAD_CHANGESET_SIZE         , ///< Bad changeset size in changeset header (DOWNLOAD)
    bad_origin_file_ident       = RLM_SYNC_ERR_CLIENT_BAD_ORIGIN_FILE_IDENT      , ///< Bad origin file identifier in changeset header (DOWNLOAD)
    bad_server_version          = RLM_SYNC_ERR_CLIENT_BAD_SERVER_VERSION         , ///< Bad server version in changeset header (DOWNLOAD)
    bad_changeset               = RLM_SYNC_ERR_CLIENT_BAD_CHANGESET              , ///< Bad changeset (DOWNLOAD)
    bad_request_ident           = RLM_SYNC_ERR_CLIENT_BAD_REQUEST_IDENT          , ///< Bad request identifier (MARK)
    bad_error_code              = RLM_SYNC_ERR_CLIENT_BAD_ERROR_CODE             , ///< Bad error code (ERROR),
    bad_compression             = RLM_SYNC_ERR_CLIENT_BAD_COMPRESSION            , ///< Bad compression (DOWNLOAD)
    bad_client_version          = RLM_SYNC_ERR_CLIENT_BAD_CLIENT_VERSION         , ///< Bad last integrated client version in changeset header (DOWNLOAD)
    ssl_server_cert_rejected    = RLM_SYNC_ERR_CLIENT_SSL_SERVER_CERT_REJECTED   , ///< SSL server certificate rejected
    pong_timeout                = RLM_SYNC_ERR_CLIENT_PONG_TIMEOUT               , ///< Timeout on reception of PONG respone message
    bad_client_file_ident_salt  = RLM_SYNC_ERR_CLIENT_BAD_CLIENT_FILE_IDENT_SALT , ///< Bad client file identifier salt (IDENT)
    bad_file_ident              = RLM_SYNC_ERR_CLIENT_BAD_FILE_IDENT             , ///< Bad file identifier (ALLOC)
    connect_timeout             = RLM_SYNC_ERR_CLIENT_CONNECT_TIMEOUT            , ///< Sync connection was not fully established in time
    bad_timestamp               = RLM_SYNC_ERR_CLIENT_BAD_TIMESTAMP              , ///< Bad timestamp (PONG)
    bad_protocol_from_server    = RLM_SYNC_ERR_CLIENT_BAD_PROTOCOL_FROM_SERVER   , ///< Bad or missing protocol version information from server
    client_too_old_for_server   = RLM_SYNC_ERR_CLIENT_CLIENT_TOO_OLD_FOR_SERVER  , ///< Protocol version negotiation failed: Client is too old for server
    client_too_new_for_server   = RLM_SYNC_ERR_CLIENT_CLIENT_TOO_NEW_FOR_SERVER  , ///< Protocol version negotiation failed: Client is too new for server
    protocol_mismatch           = RLM_SYNC_ERR_CLIENT_PROTOCOL_MISMATCH          , ///< Protocol version negotiation failed: No version supported by both client and server
    bad_state_message           = RLM_SYNC_ERR_CLIENT_BAD_STATE_MESSAGE          , ///< Bad values in state message (STATE)
    missing_protocol_feature    = RLM_SYNC_ERR_CLIENT_MISSING_PROTOCOL_FEATURE   , ///< Requested feature missing in negotiated protocol version
    http_tunnel_failed          = RLM_SYNC_ERR_CLIENT_HTTP_TUNNEL_FAILED         , ///< Failed to establish HTTP tunnel with configured proxy
    auto_client_reset_failure   = RLM_SYNC_ERR_CLIENT_AUTO_CLIENT_RESET_FAILURE  , ///< A fatal error was encountered which prevents completion of a client reset
    // clang-format on
};

const std::error_category& client_error_category() noexcept;

std::error_code make_error_code(ClientError) noexcept;

ProtocolError client_error_to_protocol_error(ClientError);
} // namespace realm::sync

namespace std {

template <>
struct is_error_code_enum<realm::sync::ClientError> {
    static const bool value = true;
};

} // namespace std

namespace realm::sync {

static constexpr milliseconds_type default_connect_timeout = 120000;        // 2 minutes
static constexpr milliseconds_type default_connection_linger_time = 30000;  // 30 seconds
static constexpr milliseconds_type default_ping_keepalive_period = 60000;   // 1 minute
static constexpr milliseconds_type default_pong_keepalive_timeout = 120000; // 2 minutes
static constexpr milliseconds_type default_fast_reconnect_limit = 60000;    // 1 minute

using RoundtripTimeHandler = void(milliseconds_type roundtrip_time);

struct ClientConfig {
    /// An optional logger to be used by the client. If no logger is
    /// specified, the client will use an instance of util::StderrLogger
    /// with the log level threshold set to util::Logger::Level::info. The
    /// client does not require a thread-safe logger, and it guarantees that
    /// all logging happens either on behalf of the constructor or on behalf
    /// of the invocation of run().
    std::shared_ptr<util::Logger> logger;

    // The SyncSocket instance used by the Sync Client for event synchronization
    // and creating WebSockets. If not provided the default implementation will be used.
    std::shared_ptr<sync::SyncSocketProvider> socket_provider;

    /// Use ports 80 and 443 by default instead of 7800 and 7801
    /// respectively. Ideally, these default ports should have been made
    /// available via a different URI scheme instead (http/https or ws/wss).
    bool enable_default_port_hack = true;

    /// For testing purposes only.
    ReconnectMode reconnect_mode = ReconnectMode::normal;

    /// Create a separate connection for each session.
#if REALM_DISABLE_SYNC_MULTIPLEXING
    bool one_connection_per_session = true;
#else
    bool one_connection_per_session = false;
#endif

    /// Do not access the local file system. Sessions will act as if
    /// initiated on behalf of an empty (or nonexisting) local Realm
    /// file. Received DOWNLOAD messages will be accepted, but otherwise
    /// ignored. No UPLOAD messages will be generated. For testing purposes
    /// only.
    ///
    /// Many operations, such as serialized transactions, are not suppored
    /// in this mode.
    bool dry_run = false;

    /// The maximum number of milliseconds to allow for a connection to
    /// become fully established. This includes the time to resolve the
    /// network address, the TCP connect operation, the SSL handshake, and
    /// the WebSocket handshake.
    milliseconds_type connect_timeout = default_connect_timeout;

    /// The number of milliseconds to keep a connection open after all
    /// sessions have been abandoned (or suspended by errors).
    ///
    /// The purpose of this linger time is to avoid close/reopen cycles
    /// during short periods of time where there are no sessions interested
    /// in using the connection.
    ///
    /// If the connection gets closed due to an error before the linger time
    /// expires, the connection will be kept closed until there are sessions
    /// willing to use it again.
    milliseconds_type connection_linger_time = default_connection_linger_time;

    /// The client will send PING messages periodically to allow the server
    /// to detect dead connections (heartbeat). This parameter specifies the
    /// time, in milliseconds, between these PING messages. When scheduling
    /// the next PING message, the client will deduct a small random amount
    /// from the specified value to help spread the load on the server from
    /// many clients.
    milliseconds_type ping_keepalive_period = default_ping_keepalive_period;

    /// Whenever the server receives a PING message, it is supposed to
    /// respond with a PONG messsage to allow the client to detect dead
    /// connections (heartbeat). This parameter specifies the time, in
    /// milliseconds, that the client will wait for the PONG response
    /// message before it assumes that the connection is dead, and
    /// terminates it.
    milliseconds_type pong_keepalive_timeout = default_pong_keepalive_timeout;

    /// The maximum amount of time, in milliseconds, since the loss of a
    /// prior connection, for a new connection to be considered a *fast
    /// reconnect*.
    ///
    /// In general, when a client establishes a connection to the server,
    /// the uploading process remains suspended until the initial
    /// downloading process completes (as if by invocation of
    /// Session::async_wait_for_download_completion()). However, to avoid
    /// unnecessary latency in change propagation during ongoing
    /// application-level activity, if the new connection is established
    /// less than a certain amount of time (`fast_reconnect_limit`) since
    /// the client was previously connected to the server, then the
    /// uploading process will be activated immediately.
    ///
    /// For now, the purpose of the general delaying of the activation of
    /// the uploading process, is to increase the chance of multiple initial
    /// transactions on the client-side, to be uploaded to, and processed by
    /// the server as a single unit. In the longer run, the intention is
    /// that the client should upload transformed (from reciprocal history),
    /// rather than original changesets when applicable to reduce the need
    /// for changeset to be transformed on both sides. The delaying of the
    /// upload process will increase the number of cases where this is
    /// possible.
    ///
    /// FIXME: Currently, the time between connections is not tracked across
    /// sessions, so if the application closes its session, and opens a new
    /// one immediately afterwards, the activation of the upload process
    /// will be delayed unconditionally.
    milliseconds_type fast_reconnect_limit = default_fast_reconnect_limit;

    /// If a connection is disconnected because of an error that isn't a
    /// sync protocol ERROR message, this parameter will be used to decide how
    /// long to wait between each re-connect attempt.
    ResumptionDelayInfo reconnect_backoff_info;

    /// Set to true to completely disable delaying of the upload process. In
    /// this mode, the upload process will be activated immediately, and the
    /// value of `fast_reconnect_limit` is ignored.
    ///
    /// For testing purposes only.
    bool disable_upload_activation_delay = false;

    /// If `disable_upload_compaction` is true, every changeset will be
    /// compacted before it is uploaded to the server. Compaction will
    /// reduce the size of a changeset if the same field is set multiple
    /// times or if newly created objects are deleted within the same
    /// transaction. Log compaction increeses CPU usage and memory
    /// consumption.
    bool disable_upload_compaction = false;

    /// The specified function will be called whenever a PONG message is
    /// received on any connection. The round-trip time in milliseconds will
    /// be pased to the function. The specified function will always be
    /// called by the client's event loop thread, i.e., the thread that
    /// calls `Client::run()`. This feature is mainly for testing purposes.
    std::function<RoundtripTimeHandler> roundtrip_time_handler;

    /// Disable sync to disk (fsync(), msync()) for all realm files managed
    /// by this client.
    ///
    /// Testing/debugging feature. Should never be enabled in production.
    bool disable_sync_to_disk = false;

    /// The sync client supports tables without primary keys by synthesizing a
    /// pk using the client file ident, which means that all changesets waiting
    /// to be uploaded need to be rewritten with the correct ident the first time
    /// we connect to the server. The modern server doesn't support this and
    /// requires pks for all tables, so this is now only applicable to old sync
    /// tests and so is disabled by default.
    bool fix_up_object_ids = false;
};

/// \brief Information about an error causing a session to be temporarily
/// disconnected from the server.
///
/// In general, the connection will be automatically reestablished
/// later. Whether this happens quickly, generally depends on \ref
/// is_fatal. If \ref is_fatal is true, it means that the error is deemed to
/// be of a kind that is likely to persist, and cause all future reconnect
/// attempts to fail. In that case, if another attempt is made at
/// reconnecting, the delay will be substantial (at least an hour).
///
/// \ref error_code specifies the error that caused the connection to be
/// closed. For the list of errors reported by the server, see \ref
/// ProtocolError (or `protocol.md`). For the list of errors corresponding
/// to protocol violations that are detected by the client, see
/// Client::Error. The error may also be a system level error, or an error
/// from one of the potential intermediate protocol layers (SSL or
/// WebSocket).
///
/// \ref detailed_message is the most detailed message available to describe
/// the error. It is generally equal to `error_code.message()`, but may also
/// be a more specific message (one that provides extra context). The
/// purpose of this message is mostly to aid in debugging. For non-debugging
/// purposes, `error_code.message()` should generally be considered
/// sufficient.
///
/// \sa set_connection_state_change_listener().
struct SessionErrorInfo : public ProtocolErrorInfo {
    SessionErrorInfo(const ProtocolErrorInfo& info, const std::error_code& ec)
        : ProtocolErrorInfo(info)
        , error_code(ec)
    {
    }
    SessionErrorInfo(const std::error_code& ec, bool try_again)
        : ProtocolErrorInfo(ec.value(), ec.message(), try_again)
        , error_code(ec)
    {
    }
    SessionErrorInfo(const std::error_code& ec, const std::string& msg, bool try_again)
        : ProtocolErrorInfo(ec.value(), msg, try_again)
        , error_code(ec)
    {
    }
    std::error_code error_code;
};

enum class ConnectionState { disconnected, connecting, connected };

inline std::ostream& operator<<(std::ostream& os, ConnectionState state)
{
    switch (state) {
        case ConnectionState::disconnected:
            return os << "Disconnected";
        case ConnectionState::connecting:
            return os << "Connecting";
        case ConnectionState::connected:
            return os << "Connected";
    }
    REALM_TERMINATE("Invalid ConnectionState value");
}

} // namespace realm::sync

#endif // REALM_SYNC_CLIENT_BASE_HPP