diff --git a/src/realm.h b/src/realm.h index 91ebbc194b3..2215b2d743d 100644 --- a/src/realm.h +++ b/src/realm.h @@ -3319,7 +3319,7 @@ typedef enum realm_sync_error_category { RLM_SYNC_ERROR_CATEGORY_CLIENT, RLM_SYNC_ERROR_CATEGORY_CONNECTION, RLM_SYNC_ERROR_CATEGORY_SESSION, - RLM_SYNC_ERROR_CATEGORY_RESOLVE, + RLM_SYNC_ERROR_CATEGORY_WEBSOCKET, /** * System error - POSIX errno, Win32 HRESULT, etc. @@ -3343,15 +3343,6 @@ typedef enum realm_sync_error_action { RLM_SYNC_ERROR_ACTION_CLIENT_RESET_NO_RECOVERY, } realm_sync_error_action_e; -typedef enum realm_sync_error_resolve { - RLM_SYNC_ERROR_RESOLVE_HOST_NOT_FOUND = 1, - RLM_SYNC_ERROR_RESOLVE_HOST_NOT_FOUND_TRY_AGAIN = 2, - RLM_SYNC_ERROR_RESOLVE_NO_DATA = 3, - RLM_SYNC_ERROR_RESOLVE_NO_RECOVERY = 4, - RLM_SYNC_ERROR_RESOLVE_SERVICE_NOT_FOUND = 5, - RLM_SYNC_ERROR_RESOLVE_SOCKET_TYPE_NOT_SUPPORTED = 6, -} realm_sync_error_resolve_e; - typedef struct realm_sync_session realm_sync_session_t; typedef struct realm_async_open_task realm_async_open_task_t; @@ -4092,30 +4083,6 @@ RLM_API bool realm_mongo_collection_find_one_and_delete(realm_mongodb_collection realm_userdata_t data, realm_free_userdata_func_t delete_data, realm_mongodb_callback_t callback); -typedef enum status_error_code { - STATUS_OK = 0, - STATUS_UNKNOWN_ERROR = 1, - STATUS_RUNTIME_ERROR = 2, - STATUS_LOGIC_ERROR = 3, - STATUS_BROKEN_PROMISE = 4, - STATUS_OPERATION_ABORTED = 5, - - /// WEBSOCKET ERRORS - // STATUS_WEBSOCKET_OK = 1000 IS NOT USED, JUST USE OK INSTEAD - STATUS_WEBSOCKET_GOING_AWAY = 1001, - STATUS_WEBSOCKET_PROTOCOL_ERROR = 1002, - STATUS_WEBSOCKET_UNSUPPORTED_DATA = 1003, - STATUS_WEBSOCKET_RESERVED = 1004, - STATUS_WEBSOCKET_NO_STATUS_RECEIVED = 1005, - STATUS_WEBSOCKET_ABNORMAL_CLOSURE = 1006, - STATUS_WEBSOCKET_INVALID_PAYLOAD_DATA = 1007, - STATUS_WEBSOCKET_POLICY_VIOLATION = 1008, - STATUS_WEBSOCKET_MESSAGE_TOO_BIG = 1009, - STATUS_WEBSOCKET_INAVALID_EXTENSION = 1010, - STATUS_WEBSOCKET_INTERNAL_SERVER_ERROR = 1011, - STATUS_WEBSOCKET_TLS_HANDSHAKE_FAILED = 1015, // USED BY DEFAULT WEBSOCKET -} status_error_code_e; - RLM_API realm_sync_socket_t* realm_sync_socket_new( realm_userdata_t userdata, realm_free_userdata_func_t userdata_free, realm_sync_socket_post_func_t post_func, realm_sync_socket_create_timer_func_t create_timer_func, @@ -4125,7 +4092,7 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( realm_sync_socket_websocket_free_func_t websocket_free_func); RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback_t* realm_callback, - status_error_code_e status, const char* reason); + realm_web_socket_errno_e status, const char* reason); RLM_API void realm_sync_socket_websocket_connected(realm_websocket_observer_t* realm_websocket_observer, const char* protocol); @@ -4136,7 +4103,7 @@ RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* rea const char* data, size_t data_size); RLM_API void realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean, - status_error_code_e status, const char* reason); + realm_web_socket_errno_e status, const char* reason); RLM_API void realm_sync_client_config_set_sync_socket(realm_sync_client_config_t*, realm_sync_socket_t*) RLM_API_NOEXCEPT; diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index ac9e627325f..bc9004f1db4 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -207,18 +207,9 @@ ErrorCategory ErrorCodes::error_categories(Error code) .set(ErrorCategory::app_error) .set(ErrorCategory::service_error); - case WebSocketGoingAway: - case WebSocketProtocolError: - case WebSocketUnsupportedData: - case WebSocketReserved: - case WebSocketNoStatusReceived: - case WebSocketAbnormalClosure: - case WebSocketInvalidPayloadData: - case WebSocketPolicyViolation: - case WebSocketMessageTooBig: - case WebSocketInavalidExtension: - case WebSocketInternalServerError: - case WebSocketTLSHandshakeFailed: + case WebSocketResolveFailedError: + case WebSocketConnectionClosedClientError: + case WebSocketConnectionClosedServerError: return ErrorCategory().set(ErrorCategory::runtime_error).set(ErrorCategory::websocket_error); case UnknownError: @@ -374,18 +365,9 @@ static const MapElem string_to_error_code[] = { {"ValueAlreadyExists", ErrorCodes::ValueAlreadyExists}, {"ValueDuplicateName", ErrorCodes::ValueDuplicateName}, {"ValueNotFound", ErrorCodes::ValueNotFound}, - {"WebSocketAbnormalClosure", ErrorCodes::WebSocketAbnormalClosure}, - {"WebSocketGoingAway", ErrorCodes::WebSocketGoingAway}, - {"WebSocketInavalidExtension", ErrorCodes::WebSocketInavalidExtension}, - {"WebSocketInternalServerError", ErrorCodes::WebSocketInternalServerError}, - {"WebSocketInvalidPayloadData", ErrorCodes::WebSocketInvalidPayloadData}, - {"WebSocketMessageTooBig", ErrorCodes::WebSocketMessageTooBig}, - {"WebSocketNoStatusReceived", ErrorCodes::WebSocketNoStatusReceived}, - {"WebSocketPolicyViolation", ErrorCodes::WebSocketPolicyViolation}, - {"WebSocketProtocolError", ErrorCodes::WebSocketProtocolError}, - {"WebSocketReserved", ErrorCodes::WebSocketReserved}, - {"WebSocketTLSHandshakeFailed", ErrorCodes::WebSocketTLSHandshakeFailed}, - {"WebSocketUnsupportedData", ErrorCodes::WebSocketUnsupportedData}, + {"WebSocketConnectionClosedClientError", ErrorCodes::WebSocketConnectionClosedClientError}, + {"WebSocketConnectionClosedServerError", ErrorCodes::WebSocketConnectionClosedServerError}, + {"WebSocketResolveFailedError", ErrorCodes::WebSocketResolveFailedError}, {"WrongThread", ErrorCodes::WrongThread}, {"WrongTransactionState", ErrorCodes::WrongTransactionState}, }; diff --git a/src/realm/error_codes.h b/src/realm/error_codes.h index adb8e6fc7da..186ff45f727 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -183,18 +183,9 @@ typedef enum realm_errno { RLM_ERR_MAINTENANCE_IN_PROGRESS = 4352, RLM_ERR_USERPASS_TOKEN_INVALID = 4353, - RLM_ERR_WEBSOCKET_GOINGAWAY = 4400, - RLM_ERR_WEBSOCKET_PROTOCOLERROR = 4401, - RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA = 4402, - RLM_ERR_WEBSOCKET_RESERVED = 4403, - RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED = 4404, - RLM_ERR_WEBSOCKET_ABNORMALCLOSURE = 4405, - RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA = 4406, - RLM_ERR_WEBSOCKET_POLICYVIOLATION = 4407, - RLM_ERR_WEBSOCKET_MESSAGETOOBIG = 4408, - RLM_ERR_WEBSOCKET_INAVALIDEXTENSION = 4409, - RLM_ERR_WEBSOCKET_INTERNALSERVERERROR = 4410, - RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED = 4411, + RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR = 4400, + RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR = 4401, + RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR = 4402, RLM_ERR_CALLBACK = 1000000, /**< A user-provided callback failed. */ RLM_ERR_UNKNOWN = 2000000 /* Should not be used in code */ @@ -287,4 +278,34 @@ typedef enum realm_sync_errno_session { RLM_SYNC_ERR_SESSION_BAD_PROGRESS = 233, } realm_sync_errno_session_e; +typedef enum realm_web_socket_errno { + RLM_ERR_WEBSOCKET_OK = 1000, + RLM_ERR_WEBSOCKET_GOINGAWAY = 1001, + RLM_ERR_WEBSOCKET_PROTOCOLERROR = 1002, + RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA = 1003, + RLM_ERR_WEBSOCKET_RESERVED = 1004, + RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED = 1005, + RLM_ERR_WEBSOCKET_ABNORMALCLOSURE = 1006, + RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA = 1007, + RLM_ERR_WEBSOCKET_POLICYVIOLATION = 1008, + RLM_ERR_WEBSOCKET_MESSAGETOOBIG = 1009, + RLM_ERR_WEBSOCKET_INAVALIDEXTENSION = 1010, + RLM_ERR_WEBSOCKET_INTERNALSERVERERROR = 1011, + RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED = 1015, + + RLM_ERR_WEBSOCKET_UNAUTHORIZED = 4001, + RLM_ERR_WEBSOCKET_FORBIDDEN = 4002, + RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY = 4003, + RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD = 4004, + RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW = 4005, + RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH = 4006, + + RLM_ERR_WEBSOCKET_RESOLVE_FAILED = 4400, + RLM_ERR_WEBSOCKET_CONNECTION_FAILED = 4401, + RLM_ERR_WEBSOCKET_READ_ERROR = 4402, + RLM_ERR_WEBSOCKET_WRITE_ERROR = 4403, + RLM_ERR_WEBSOCKET_RETRY_ERROR = 4404, + RLM_ERR_WEBSOCKET_FATAL_ERROR = 4405, +} realm_web_socket_errno_e; + #endif /* REALM_ERROR_CODES_H */ diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index 87ec5f3b7c8..85dce41a563 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -225,34 +225,9 @@ class ErrorCodes { AppUnknownError = RLM_ERR_APP_UNKNOWN, MaintenanceInProgress = RLM_ERR_MAINTENANCE_IN_PROGRESS, UserpassTokenInvalid = RLM_ERR_USERPASS_TOKEN_INVALID, - - WebSocketGoingAway = RLM_ERR_WEBSOCKET_GOINGAWAY, - WebSocketProtocolError = RLM_ERR_WEBSOCKET_PROTOCOLERROR, - WebSocketUnsupportedData = RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA, - WebSocketReserved = RLM_ERR_WEBSOCKET_RESERVED, - WebSocketNoStatusReceived = RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED, - WebSocketAbnormalClosure = RLM_ERR_WEBSOCKET_ABNORMALCLOSURE, - WebSocketInvalidPayloadData = RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA, - WebSocketPolicyViolation = RLM_ERR_WEBSOCKET_POLICYVIOLATION, - WebSocketMessageTooBig = RLM_ERR_WEBSOCKET_MESSAGETOOBIG, - WebSocketInavalidExtension = RLM_ERR_WEBSOCKET_INAVALIDEXTENSION, - WebSocketInternalServerError = RLM_ERR_WEBSOCKET_INTERNALSERVERERROR, - WebSocketTLSHandshakeFailed = RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED, // Used by default WebSocket - - // HACK - ReadError, - WriteError, - ResolveFailed, - ConnectionFailed, - WebSocketRetryError, - WebSocketFatalError, - WebSocketUnauthorized, - WebSocketForbidden, - WebSocketMovedPermanently, - WebSocketClient_Too_Old, - WebSocketClient_Too_New, - WebSocketProtocol_Mismatch, - // HACK + WebSocketResolveFailedError = RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR, + WebSocketConnectionClosedClientError = RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR, + WebSocketConnectionClosedServerError = RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR, CallbackFailed = RLM_ERR_CALLBACK, UnknownError = RLM_ERR_UNKNOWN, diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index 48701da9e02..a43d4fa1a87 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace realm::c_api { namespace { @@ -218,11 +219,12 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( } RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback* realm_callback, - status_error_code_e status, const char* reason) + realm_web_socket_errno_e code, const char* reason) { - auto complete_status = status == status_error_code_e::STATUS_OK + auto status = sync::websocket::WebSocketError(code); + auto complete_status = code == realm_web_socket_errno_e::RLM_ERR_WEBSOCKET_OK ? Status::OK() - : Status{static_cast(status), reason}; + : Status{sync::websocket::make_error_code(status), reason}; (*(realm_callback->get()))(complete_status); realm_release(realm_callback); } @@ -245,11 +247,12 @@ RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* rea } RLM_API void realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean, - status_error_code_e status, const char* reason) + realm_web_socket_errno_e code, const char* reason) { - auto closed_status = status == status_error_code_e::STATUS_OK + auto status = sync::websocket::WebSocketError(code); + auto closed_status = code == realm_web_socket_errno_e::RLM_ERR_WEBSOCKET_OK ? Status::OK() - : Status{static_cast(status), reason}; + : Status{sync::websocket::make_error_code(status), reason}; realm_websocket_observer->get()->websocket_closed_handler(was_clean, closed_status); } diff --git a/src/realm/object-store/c_api/sync.cpp b/src/realm/object-store/c_api/sync.cpp index 8382533b119..26aefc07015 100644 --- a/src/realm/object-store/c_api/sync.cpp +++ b/src/realm/object-store/c_api/sync.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include @@ -117,17 +117,6 @@ static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Su static_assert(realm_flx_sync_subscription_set_state_e(SubscriptionSet::State::Uncommitted) == RLM_SYNC_SUBSCRIPTION_UNCOMMITTED); -static_assert(realm_sync_error_resolve_e(network::ResolveErrors::host_not_found) == - RLM_SYNC_ERROR_RESOLVE_HOST_NOT_FOUND); -static_assert(realm_sync_error_resolve_e(network::ResolveErrors::host_not_found_try_again) == - RLM_SYNC_ERROR_RESOLVE_HOST_NOT_FOUND_TRY_AGAIN); -static_assert(realm_sync_error_resolve_e(network::ResolveErrors::no_data) == RLM_SYNC_ERROR_RESOLVE_NO_DATA); -static_assert(realm_sync_error_resolve_e(network::ResolveErrors::no_recovery) == RLM_SYNC_ERROR_RESOLVE_NO_RECOVERY); -static_assert(realm_sync_error_resolve_e(network::ResolveErrors::service_not_found) == - RLM_SYNC_ERROR_RESOLVE_SERVICE_NOT_FOUND); -static_assert(realm_sync_error_resolve_e(network::ResolveErrors::socket_type_not_supported) == - RLM_SYNC_ERROR_RESOLVE_SOCKET_TYPE_NOT_SUPPORTED); - } // namespace realm_sync_error_code_t to_capi(const Status& status, std::string& message) @@ -150,8 +139,8 @@ realm_sync_error_code_t to_capi(const Status& status, std::string& message) else if (category == std::system_category() || category == realm::util::error::basic_system_error_category()) { ret.category = RLM_SYNC_ERROR_CATEGORY_SYSTEM; } - else if (category == realm::sync::network::resolve_error_category()) { - ret.category = RLM_SYNC_ERROR_CATEGORY_RESOLVE; + else if (category == realm::sync::websocket::websocket_error_category()) { + ret.category = RLM_SYNC_ERROR_CATEGORY_WEBSOCKET; } else { ret.category = RLM_SYNC_ERROR_CATEGORY_UNKNOWN; @@ -179,8 +168,8 @@ void sync_error_to_error_code(const realm_sync_error_code_t& sync_error_code, st else if (category == RLM_SYNC_ERROR_CATEGORY_SYSTEM) { error_code_out->assign(sync_error_code.value, std::system_category()); } - else if (category == RLM_SYNC_ERROR_CATEGORY_RESOLVE) { - error_code_out->assign(sync_error_code.value, realm::sync::network::resolve_error_category()); + else if (category == RLM_SYNC_ERROR_CATEGORY_WEBSOCKET) { + error_code_out->assign(sync_error_code.value, realm::sync::websocket::websocket_error_category()); } else if (category == RLM_SYNC_ERROR_CATEGORY_UNKNOWN) { error_code_out->assign(sync_error_code.value, realm::util::error::basic_system_error_category()); diff --git a/src/realm/object-store/sync/generic_network_transport.hpp b/src/realm/object-store/sync/generic_network_transport.hpp index 2dd11d965b5..c680aea250b 100644 --- a/src/realm/object-store/sync/generic_network_transport.hpp +++ b/src/realm/object-store/sync/generic_network_transport.hpp @@ -63,6 +63,11 @@ struct AppError : public RuntimeError { { return ErrorCodes::error_categories(code()).test(ErrorCategory::client_error); } + + bool is_websocket_error() const + { + return ErrorCodes::error_categories(code()).test(ErrorCategory::websocket_error); + } }; std::ostream& operator<<(std::ostream& os, AppError error); diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index a62d15d7243..1f611e00640 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -681,21 +681,27 @@ void SyncSession::handle_error(SyncError error) break; } } - else { + else if (error_code.category() == sync::websocket::websocket_error_category()) { // The server replies with '401: unauthorized' if the access token is invalid, expired, revoked, or the user // is disabled. In this scenario we attempt an automatic token refresh and if that succeeds continue as // normal. If the refresh request also fails with 401 then we need to stop retrying and pass along the error; // see handle_refresh(). - if (error_code.category() == sync::websocket::websocket_close_status_category()) { - bool restart_session = error_code.value() == ErrorCodes::WebSocketMovedPermanently; - if (restart_session || error_code.value() == ErrorCodes::WebSocketUnauthorized || - error_code.value() == ErrorCodes::WebSocketAbnormalClosure) { - if (auto u = user()) { - u->refresh_custom_data(handle_refresh(shared_from_this(), restart_session)); - return; - } + bool restart_session = error_code == sync::websocket::WebSocketError::websocket_moved_permanently; + if (restart_session || error_code == sync::websocket::WebSocketError::websocket_unauthorized || + error_code == sync::websocket::WebSocketError::websocket_abnormal_closure) { + if (auto u = user()) { + u->refresh_custom_data(handle_refresh(shared_from_this(), restart_session)); + return; } } + + // Surface a simplified websocket error to the user. + auto simplified_error = sync::websocket::get_simplified_websocket_error( + static_cast(error_code.value())); + std::error_code new_error_code(simplified_error, sync::websocket::websocket_error_category()); + error = SyncError(new_error_code, error.reason(), error.is_fatal); + } + else { // Unrecognized error code. error.is_unrecognized_by_client = true; } diff --git a/src/realm/sync/network/default_socket.cpp b/src/realm/sync/network/default_socket.cpp index 38fc64a47df..5bd6aa4442c 100644 --- a/src/realm/sync/network/default_socket.cpp +++ b/src/realm/sync/network/default_socket.cpp @@ -78,44 +78,46 @@ class DefaultWebSocketImpl final : public DefaultWebSocket, public Config { { m_logger.error("Reading failed: %1", ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, Status{ErrorCodes::ReadError, ec.message()}); + websocket_error_and_close_handler( + was_clean, Status{make_error_code(WebSocketError::websocket_read_error), ec.message()}); } void websocket_write_error_handler(std::error_code ec) override { m_logger.error("Writing failed: %1", ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, Status{ErrorCodes::WriteError, ec.message()}); + websocket_error_and_close_handler( + was_clean, Status{make_error_code(WebSocketError::websocket_write_error), ec.message()}); } void websocket_handshake_error_handler(std::error_code ec, const HTTPHeaders*, const std::string_view* body) override { - ErrorCodes::Error error; + WebSocketError error = WebSocketError::websocket_ok; bool was_clean = true; - if (ec == websocket::Error::bad_response_301_moved_permanently || - ec == websocket::Error::bad_response_308_permanent_redirect) { - error = ErrorCodes::WebSocketMovedPermanently; + if (ec == websocket::HttpError::bad_response_301_moved_permanently || + ec == websocket::HttpError::bad_response_308_permanent_redirect) { + error = WebSocketError::websocket_moved_permanently; } - else if (ec == websocket::Error::bad_response_3xx_redirection) { - error = ErrorCodes::WebSocketRetryError; + else if (ec == websocket::HttpError::bad_response_3xx_redirection) { + error = WebSocketError::websocket_retry_error; was_clean = false; } - else if (ec == websocket::Error::bad_response_401_unauthorized) { - error = ErrorCodes::WebSocketUnauthorized; + else if (ec == websocket::HttpError::bad_response_401_unauthorized) { + error = WebSocketError::websocket_unauthorized; } - else if (ec == websocket::Error::bad_response_403_forbidden) { - error = ErrorCodes::WebSocketForbidden; + else if (ec == websocket::HttpError::bad_response_403_forbidden) { + error = WebSocketError::websocket_forbidden; } - else if (ec == websocket::Error::bad_response_5xx_server_error || - ec == websocket::Error::bad_response_500_internal_server_error || - ec == websocket::Error::bad_response_502_bad_gateway || - ec == websocket::Error::bad_response_503_service_unavailable || - ec == websocket::Error::bad_response_504_gateway_timeout) { - error = ErrorCodes::WebSocketInternalServerError; + else if (ec == websocket::HttpError::bad_response_5xx_server_error || + ec == websocket::HttpError::bad_response_500_internal_server_error || + ec == websocket::HttpError::bad_response_502_bad_gateway || + ec == websocket::HttpError::bad_response_503_service_unavailable || + ec == websocket::HttpError::bad_response_504_gateway_timeout) { + error = WebSocketError::websocket_internal_server_error; was_clean = false; } else { - error = ErrorCodes::WebSocketFatalError; + error = WebSocketError::websocket_fatal_error; was_clean = false; if (body) { std::string_view identifier = "REALM_SYNC_PROTOCOL_MISMATCH"; @@ -128,26 +130,27 @@ class DefaultWebSocketImpl final : public DefaultWebSocket, public Config { std::equal(string.data(), string.data() + prefix.size(), prefix.data())); }; if (begins_with(rest, ":CLIENT_TOO_OLD")) { - error = ErrorCodes::WebSocketClient_Too_Old; + error = WebSocketError::websocket_client_too_old; } else if (begins_with(rest, ":CLIENT_TOO_NEW")) { - error = ErrorCodes::WebSocketClient_Too_New; + error = WebSocketError::websocket_client_too_new; } else { // Other more complicated forms of mismatch - error = ErrorCodes::WebSocketProtocol_Mismatch; + error = WebSocketError::websocket_protocol_mismatch; } was_clean = true; } } } - websocket_error_and_close_handler(was_clean, Status{error, ec.message()}); + websocket_error_and_close_handler(was_clean, Status{make_error_code(error), ec.message()}); } void websocket_protocol_error_handler(std::error_code ec) override { constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, Status{ErrorCodes::WebSocketProtocolError, ec.message()}); + websocket_error_and_close_handler( + was_clean, Status{make_error_code(WebSocketError::websocket_protocol_error), ec.message()}); } bool websocket_close_message_received(std::error_code ec, StringData message) override { @@ -157,8 +160,7 @@ class DefaultWebSocketImpl final : public DefaultWebSocket, public Config { if (ec.value() == 1000) { return websocket_error_and_close_handler(was_clean, Status::OK()); } - return websocket_error_and_close_handler(was_clean, - Status{static_cast(ec.value()), message}); + return websocket_error_and_close_handler(was_clean, Status{ec, message}); } bool websocket_error_and_close_handler(bool was_clean, Status status) { @@ -273,7 +275,8 @@ void DefaultWebSocketImpl::handle_resolve(std::error_code ec, network::Endpoint: if (ec) { m_logger.error("Failed to resolve '%1:%2': %3", m_endpoint.address, m_endpoint.port, ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, Status{ErrorCodes::ResolveFailed, ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, Status{make_error_code(WebSocketError::websocket_resolve_failed), ec.message()}); // Throws return; } @@ -313,7 +316,8 @@ void DefaultWebSocketImpl::handle_tcp_connect(std::error_code ec, network::Endpo // All endpoints failed m_logger.error("Failed to connect to '%1:%2': All endpoints failed", m_endpoint.address, m_endpoint.port); constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, Status{ErrorCodes::ConnectionFailed, ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, Status{make_error_code(WebSocketError::websocket_connection_failed), ec.message()}); // Throws return; } @@ -353,16 +357,18 @@ void DefaultWebSocketImpl::initiate_http_tunnel() if (ec && ec != util::error::operation_aborted) { m_logger.error("Failed to establish HTTP tunnel: %1", ec.message()); constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, - Status{ErrorCodes::ConnectionFailed, ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, + Status{make_error_code(WebSocketError::websocket_connection_failed), ec.message()}); // Throws return; } if (response.status != HTTPStatus::Ok) { m_logger.error("Proxy server returned response '%1 %2'", response.status, response.reason); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, - Status{ErrorCodes::ConnectionFailed, response.reason}); // Throws + websocket_error_and_close_handler( + was_clean, + Status{make_error_code(WebSocketError::websocket_connection_failed), response.reason}); // Throws return; } @@ -425,8 +431,9 @@ void DefaultWebSocketImpl::handle_ssl_handshake(std::error_code ec) if (ec) { REALM_ASSERT(ec != util::error::operation_aborted); constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, - Status{ErrorCodes::WebSocketTLSHandshakeFailed, ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, + Status{make_error_code(WebSocketError::websocket_tls_handshake_failed), ec.message()}); // Throws return; } diff --git a/src/realm/sync/network/websocket.cpp b/src/realm/sync/network/websocket.cpp index cda1d788dde..8f31bdb2bba 100644 --- a/src/realm/sync/network/websocket.cpp +++ b/src/realm/sync/network/websocket.cpp @@ -8,7 +8,8 @@ using namespace realm; using namespace realm::sync; -using Error = websocket::Error; +using HttpError = websocket::HttpError; +using WebSocketError = websocket::WebSocketError; namespace { @@ -134,22 +135,22 @@ util::Optional do_make_http_response(const HTTPRequest& request, std::string sec_websocket_key; if (!validate_websocket_upgrade(request.headers)) { - ec = Error::bad_request_header_upgrade; + ec = HttpError::bad_request_header_upgrade; return util::none; } if (!validate_websocket_connection(request.headers)) { - ec = Error::bad_request_header_connection; + ec = HttpError::bad_request_header_connection; return util::none; } if (!validate_sec_websocket_version(request.headers)) { - ec = Error::bad_request_header_websocket_version; + ec = HttpError::bad_request_header_websocket_version; return util::none; } if (!find_sec_websocket_key(request.headers, sec_websocket_key)) { - ec = Error::bad_request_header_websocket_key; + ec = HttpError::bad_request_header_websocket_key; return util::none; } @@ -758,7 +759,7 @@ class WebSocket { { m_stopped = true; m_logger.error("WebSocket: Received malformed HTTP response"); - std::error_code ec = Error::bad_response_invalid_http; + std::error_code ec = HttpError::bad_response_invalid_http; m_config.websocket_handshake_error_handler(ec, nullptr, nullptr); // Throws } @@ -777,37 +778,37 @@ class WebSocket { status_code = *m_test_handshake_response; if (status_code == 200) - ec = Error::bad_response_200_ok; + ec = HttpError::bad_response_200_ok; else if (status_code >= 200 && status_code < 300) - ec = Error::bad_response_2xx_successful; + ec = HttpError::bad_response_2xx_successful; else if (status_code == 301) - ec = Error::bad_response_301_moved_permanently; + ec = HttpError::bad_response_301_moved_permanently; else if (status_code == 308) - ec = Error::bad_response_308_permanent_redirect; + ec = HttpError::bad_response_308_permanent_redirect; else if (status_code >= 300 && status_code < 400) - ec = Error::bad_response_3xx_redirection; + ec = HttpError::bad_response_3xx_redirection; else if (status_code == 401) - ec = Error::bad_response_401_unauthorized; + ec = HttpError::bad_response_401_unauthorized; else if (status_code == 403) - ec = Error::bad_response_403_forbidden; + ec = HttpError::bad_response_403_forbidden; else if (status_code == 404) - ec = Error::bad_response_404_not_found; + ec = HttpError::bad_response_404_not_found; else if (status_code == 410) - ec = Error::bad_response_410_gone; + ec = HttpError::bad_response_410_gone; else if (status_code >= 400 && status_code < 500) - ec = Error::bad_response_4xx_client_errors; + ec = HttpError::bad_response_4xx_client_errors; else if (status_code == 500) - ec = Error::bad_response_500_internal_server_error; + ec = HttpError::bad_response_500_internal_server_error; else if (status_code == 502) - ec = Error::bad_response_502_bad_gateway; + ec = HttpError::bad_response_502_bad_gateway; else if (status_code == 503) - ec = Error::bad_response_503_service_unavailable; + ec = HttpError::bad_response_503_service_unavailable; else if (status_code == 504) - ec = Error::bad_response_504_gateway_timeout; + ec = HttpError::bad_response_504_gateway_timeout; else if (status_code >= 500 && status_code < 600) - ec = Error::bad_response_5xx_server_error; + ec = HttpError::bad_response_5xx_server_error; else - ec = Error::bad_response_unexpected_status_code; + ec = HttpError::bad_response_unexpected_status_code; std::string_view body; std::string_view* body_ptr = nullptr; @@ -829,7 +830,7 @@ class WebSocket { m_logger.error("Websocket: HTTP response has invalid websocket headers." "HTTP response = \n%1", response); - std::error_code ec = Error::bad_response_header_protocol_violation; + std::error_code ec = HttpError::bad_response_header_protocol_violation; std::string_view body; std::string_view* body_ptr = nullptr; if (response.body) { @@ -843,7 +844,7 @@ class WebSocket { { m_stopped = true; m_logger.error("WebSocket: Received malformed HTTP request"); - std::error_code ec = Error::bad_request_malformed_http; + std::error_code ec = HttpError::bad_request_malformed_http; m_config.websocket_handshake_error_handler(ec, nullptr, nullptr); // Throws } @@ -964,7 +965,7 @@ class WebSocket { error_message = StringData(data + 2, size - 2); } - std::error_code error_code_with_category{error_code, websocket::websocket_close_status_category()}; + std::error_code error_code_with_category{error_code, websocket::websocket_error_category()}; return std::make_pair(error_code_with_category, error_message); } @@ -976,7 +977,7 @@ class WebSocket { m_frame_reader.next(); if (m_frame_reader.protocol_error) { - protocol_error(Error::bad_message); + protocol_error(HttpError::bad_message); return; } @@ -1043,71 +1044,71 @@ class WebSocket { }; -const char* get_error_message(Error error_code) +const char* get_error_message(HttpError error_code) { switch (error_code) { - case Error::bad_request_malformed_http: + case HttpError::bad_request_malformed_http: return "Bad WebSocket request malformed HTTP"; - case Error::bad_request_header_upgrade: + case HttpError::bad_request_header_upgrade: return "Bad WebSocket request header: Upgrade"; - case Error::bad_request_header_connection: + case HttpError::bad_request_header_connection: return "Bad WebSocket request header: Connection"; - case Error::bad_request_header_websocket_version: + case HttpError::bad_request_header_websocket_version: return "Bad WebSocket request header: Sec-Websocket-Version"; - case Error::bad_request_header_websocket_key: + case HttpError::bad_request_header_websocket_key: return "Bad WebSocket request header: Sec-Websocket-Key"; - case Error::bad_response_invalid_http: + case HttpError::bad_response_invalid_http: return "Bad WebSocket response invalid HTTP"; - case Error::bad_response_2xx_successful: + case HttpError::bad_response_2xx_successful: return "Bad WebSocket response 2xx successful"; - case Error::bad_response_200_ok: + case HttpError::bad_response_200_ok: return "Bad WebSocket response 200 ok"; - case Error::bad_response_3xx_redirection: + case HttpError::bad_response_3xx_redirection: return "Bad WebSocket response 3xx redirection"; - case Error::bad_response_301_moved_permanently: + case HttpError::bad_response_301_moved_permanently: return "Bad WebSocket response 301 moved permanently"; - case Error::bad_response_308_permanent_redirect: + case HttpError::bad_response_308_permanent_redirect: return "Bad WebSocket response 308 permanent redirect"; - case Error::bad_response_4xx_client_errors: + case HttpError::bad_response_4xx_client_errors: return "Bad WebSocket response 4xx client errors"; - case Error::bad_response_401_unauthorized: + case HttpError::bad_response_401_unauthorized: return "Bad WebSocket response 401 unauthorized"; - case Error::bad_response_403_forbidden: + case HttpError::bad_response_403_forbidden: return "Bad WebSocket response 403 forbidden"; - case Error::bad_response_404_not_found: + case HttpError::bad_response_404_not_found: return "Bad WebSocket response 404 not found"; - case Error::bad_response_410_gone: + case HttpError::bad_response_410_gone: return "Bad WebSocket response 410 gone"; - case Error::bad_response_5xx_server_error: + case HttpError::bad_response_5xx_server_error: return "Bad WebSocket response 5xx server error"; - case Error::bad_response_500_internal_server_error: + case HttpError::bad_response_500_internal_server_error: return "Bad WebSocket response 500 internal server error"; - case Error::bad_response_502_bad_gateway: + case HttpError::bad_response_502_bad_gateway: return "Bad WebSocket response 502 bad gateway"; - case Error::bad_response_503_service_unavailable: + case HttpError::bad_response_503_service_unavailable: return "Bad WebSocket response 503 service unavailable"; - case Error::bad_response_504_gateway_timeout: + case HttpError::bad_response_504_gateway_timeout: return "Bad WebSocket response 504 gateway timeout"; - case Error::bad_response_unexpected_status_code: + case HttpError::bad_response_unexpected_status_code: return "Bad Websocket response unexpected status code"; - case Error::bad_response_header_protocol_violation: + case HttpError::bad_response_header_protocol_violation: return "Bad WebSocket response header protocol violation"; - case Error::bad_message: + case HttpError::bad_message: return "Ill-formed WebSocket message"; } return nullptr; } -class ErrorCategoryImpl : public std::error_category { +class HttpErrorCategory : public std::error_category { public: const char* name() const noexcept override final { - return "realm::sync::websocket::Error"; + return "realm::sync::websocket::HttpError"; } std::string message(int error_code) const override final { - const char* msg = get_error_message(Error(error_code)); + const char* msg = get_error_message(HttpError(error_code)); if (!msg) msg = "Unknown error"; std::string msg_2{msg}; // Throws (copy) @@ -1115,21 +1116,80 @@ class ErrorCategoryImpl : public std::error_category { } }; -ErrorCategoryImpl g_error_category; +std::string error_string(WebSocketError code) +{ + /// WebSocket error codes + switch (code) { + case WebSocketError::websocket_ok: + return "WebSocket: OK"; + case WebSocketError::websocket_going_away: + return "WebSocket: Going Away"; + case WebSocketError::websocket_protocol_error: + return "WebSocket: Protocol Error"; + case WebSocketError::websocket_unsupported_data: + return "WebSocket: Unsupported Data"; + case WebSocketError::websocket_reserved: + return "WebSocket: Reserved"; + case WebSocketError::websocket_no_status_received: + return "WebSocket: No Status Received"; + case WebSocketError::websocket_abnormal_closure: + return "WebSocket: Abnormal Closure"; + case WebSocketError::websocket_invalid_payload_data: + return "WebSocket: Invalid Payload Data"; + case WebSocketError::websocket_policy_violation: + return "WebSocket: Policy Violation"; + case WebSocketError::websocket_message_too_big: + return "WebSocket: Message Too Big"; + case WebSocketError::websocket_invalid_extension: + return "WebSocket: Invalid Extension"; + case WebSocketError::websocket_internal_server_error: + return "WebSocket: Internal Server Error"; + case WebSocketError::websocket_tls_handshake_failed: + return "WebSocket: TLS Handshake Failed"; + + /// WebSocket Errors - reported by server + case WebSocketError::websocket_unauthorized: + return "WebSocket: Unauthorized"; + case WebSocketError::websocket_forbidden: + return "WebSocket: Forbidden"; + case WebSocketError::websocket_moved_permanently: + return "WebSocket: Moved Permanently"; + case WebSocketError::websocket_client_too_old: + return "WebSocket: Client Too Old"; + case WebSocketError::websocket_client_too_new: + return "WebSocket: Client Too New"; + case WebSocketError::websocket_protocol_mismatch: + return "WebSocket: Protocol Mismatch"; + + case WebSocketError::websocket_resolve_failed: + return "WebSocket: Resolve Failed"; + case WebSocketError::websocket_connection_failed: + return "WebSocket: Connection Failed"; + case WebSocketError::websocket_read_error: + return "WebSocket: Read Error"; + case WebSocketError::websocket_write_error: + return "WebSocket: Write Error"; + case WebSocketError::websocket_retry_error: + return "WebSocket: Retry Error"; + case WebSocketError::websocket_fatal_error: + return "WebSocket: Fatal Error"; + } + return ""; +} -class CloseStatusErrorCategory : public std::error_category { +class WebSocketErrorCategory : public std::error_category { const char* name() const noexcept final { - return "realm::sync::websocket::CloseStatus"; + return "realm::sync::websocket::WebSocketError"; } std::string message(int error_code) const final { // Converts an error_code to one of the pre-defined status codes in // https://tools.ietf.org/html/rfc6455#section-7.4.1 - if (error_code == 1000 || error_code == 0) { - return std::string(ErrorCodes::error_string(ErrorCodes::OK)); - } - return std::string(ErrorCodes::error_string(static_cast(error_code))); + auto msg = error_string(static_cast(error_code)); + if (msg.empty()) + msg = "Unknown error"; + return msg; } }; @@ -1254,23 +1314,46 @@ util::Optional websocket::make_http_response(const HTTPRequest& re return do_make_http_response(request, sec_websocket_protocol, ec); } -const std::error_category& websocket::websocket_close_status_category() noexcept +const std::error_category& websocket::websocket_error_category() noexcept { - static const CloseStatusErrorCategory category = {}; + static const WebSocketErrorCategory category = {}; return category; } -std::error_code websocket::make_error_code(ErrorCodes::Error error) noexcept +std::error_code websocket::make_error_code(WebSocketError error) noexcept { - return std::error_code{error, realm::sync::websocket::websocket_close_status_category()}; + return std::error_code{int(error), websocket_error_category()}; } -const std::error_category& websocket::error_category() noexcept +const std::error_category& websocket::http_error_category() noexcept { - return g_error_category; + static const HttpErrorCategory category = {}; + return category; } -std::error_code websocket::make_error_code(Error error_code) noexcept +std::error_code websocket::make_error_code(HttpError error_code) noexcept { - return std::error_code{int(error_code), g_error_category}; + return std::error_code{int(error_code), http_error_category()}; } + +ErrorCodes::Error websocket::get_simplified_websocket_error(WebSocketError error) +{ + if (error == sync::websocket::WebSocketError::websocket_resolve_failed) { + return ErrorCodes::WebSocketResolveFailedError; + } + else if (error == sync::websocket::WebSocketError::websocket_connection_failed || + error == sync::websocket::WebSocketError::websocket_unauthorized || + error == sync::websocket::WebSocketError::websocket_forbidden || + error == sync::websocket::WebSocketError::websocket_moved_permanently || + error == sync::websocket::WebSocketError::websocket_client_too_old || + error == sync::websocket::WebSocketError::websocket_client_too_new || + error == sync::websocket::WebSocketError::websocket_protocol_mismatch || + error == sync::websocket::WebSocketError::websocket_read_error || + error == sync::websocket::WebSocketError::websocket_write_error || + error == sync::websocket::WebSocketError::websocket_retry_error || + error == sync::websocket::WebSocketError::websocket_fatal_error) { + return ErrorCodes::WebSocketConnectionClosedClientError; + } + + return ErrorCodes::WebSocketConnectionClosedServerError; +} \ No newline at end of file diff --git a/src/realm/sync/network/websocket.hpp b/src/realm/sync/network/websocket.hpp index ae7835d8667..1cb518410bf 100644 --- a/src/realm/sync/network/websocket.hpp +++ b/src/realm/sync/network/websocket.hpp @@ -190,7 +190,7 @@ util::Optional read_sec_websocket_protocol(const HTTPRequest& reque util::Optional make_http_response(const HTTPRequest& request, const std::string& sec_websocket_protocol, std::error_code& ec); -enum class Error { +enum class HttpError { bad_request_malformed_http, bad_request_header_upgrade, bad_request_header_connection, @@ -217,20 +217,58 @@ enum class Error { bad_message }; -const std::error_category& websocket_close_status_category() noexcept; +const std::error_category& http_error_category() noexcept; + +std::error_code make_error_code(HttpError) noexcept; + +enum class WebSocketError { + websocket_ok = RLM_ERR_WEBSOCKET_OK, + websocket_going_away = RLM_ERR_WEBSOCKET_GOINGAWAY, + websocket_protocol_error = RLM_ERR_WEBSOCKET_PROTOCOLERROR, + websocket_unsupported_data = RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA, + websocket_reserved = RLM_ERR_WEBSOCKET_RESERVED, + websocket_no_status_received = RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED, + websocket_abnormal_closure = RLM_ERR_WEBSOCKET_ABNORMALCLOSURE, + websocket_invalid_payload_data = RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA, + websocket_policy_violation = RLM_ERR_WEBSOCKET_POLICYVIOLATION, + websocket_message_too_big = RLM_ERR_WEBSOCKET_MESSAGETOOBIG, + websocket_invalid_extension = RLM_ERR_WEBSOCKET_INAVALIDEXTENSION, + websocket_internal_server_error = RLM_ERR_WEBSOCKET_INTERNALSERVERERROR, + websocket_tls_handshake_failed = RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED, // Used by default WebSocket + + // WebSocket Errors - reported by server + websocket_unauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, + websocket_forbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, + websocket_moved_permanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, + websocket_client_too_old = RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD, + websocket_client_too_new = RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW, + websocket_protocol_mismatch = RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH, + + websocket_resolve_failed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, + websocket_connection_failed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, + websocket_read_error = RLM_ERR_WEBSOCKET_READ_ERROR, + websocket_write_error = RLM_ERR_WEBSOCKET_WRITE_ERROR, + websocket_retry_error = RLM_ERR_WEBSOCKET_RETRY_ERROR, + websocket_fatal_error = RLM_ERR_WEBSOCKET_FATAL_ERROR, +}; -std::error_code make_error_code(ErrorCodes::Error error) noexcept; +const std::error_category& websocket_error_category() noexcept; -const std::error_category& error_category() noexcept; +std::error_code make_error_code(WebSocketError) noexcept; -std::error_code make_error_code(Error) noexcept; +ErrorCodes::Error get_simplified_websocket_error(WebSocketError); } // namespace realm::sync::websocket namespace std { template <> -struct is_error_code_enum { +struct is_error_code_enum { + static const bool value = true; +}; + +template <> +struct is_error_code_enum { static const bool value = true; }; diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 3d5a2e3bc78..87b8c8fcd86 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -30,6 +30,7 @@ using namespace realm; using namespace _impl; using namespace realm::util; using namespace realm::sync; +using namespace realm::sync::websocket; // clang-format off using Connection = ClientImpl::Connection; @@ -483,7 +484,6 @@ void Connection::websocket_error_handler() m_websocket_error_received = true; } - bool Connection::websocket_closed_handler(bool was_clean, Status status) { if (m_force_closed) { @@ -491,79 +491,108 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) return false; } logger.info("Closing the websocket with status='%1', was_clean='%2'", status, was_clean); - // Return early. - if (status.is_ok()) { - return bool(m_websocket); - } + auto error_code = status.get_std_error_code(); - auto&& status_code = status.code(); - std::error_code error_code{static_cast(status_code), websocket::websocket_close_status_category()}; - - // TODO: Use a switch statement once websocket errors have their own category in exception unification. - if (status_code == ErrorCodes::ResolveFailed || status_code == ErrorCodes::ConnectionFailed) { - m_reconnect_info.m_reason = ConnectionTerminationReason::connect_operation_failed; - constexpr bool try_again = true; - involuntary_disconnect(SessionErrorInfo{error_code, try_again}); // Throws - } - else if (status_code == ErrorCodes::ReadError || status_code == ErrorCodes::WriteError) { - read_or_write_error(error_code, status.reason()); - } - else if (status_code == ErrorCodes::WebSocketGoingAway || status_code == ErrorCodes::WebSocketProtocolError || - status_code == ErrorCodes::WebSocketUnsupportedData || status_code == ErrorCodes::WebSocketReserved || - status_code == ErrorCodes::WebSocketInvalidPayloadData || - status_code == ErrorCodes::WebSocketPolicyViolation || - status_code == ErrorCodes::WebSocketInavalidExtension) { - m_reconnect_info.m_reason = ConnectionTerminationReason::websocket_protocol_violation; - constexpr bool try_again = true; - SessionErrorInfo error_info{error_code, status.reason(), try_again}; - involuntary_disconnect(std::move(error_info)); - } - else if (status_code == ErrorCodes::WebSocketMessageTooBig) { - m_reconnect_info.m_reason = ConnectionTerminationReason::websocket_protocol_violation; - constexpr bool try_again = true; - auto ec = make_error_code(ProtocolError::limits_exceeded); - auto message = util::format( - "Sync websocket closed because the server received a message that was too large: %1", status.reason()); - SessionErrorInfo error_info(ec, message, try_again); - error_info.server_requests_action = ProtocolErrorInfo::Action::ClientReset; - involuntary_disconnect(std::move(error_info)); - } - else if (status_code == ErrorCodes::WebSocketTLSHandshakeFailed) { - error_code = ClientError::ssl_server_cert_rejected; - constexpr bool is_fatal = true; - m_reconnect_info.m_reason = ConnectionTerminationReason::ssl_certificate_rejected; - close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws - } - else if (status_code == ErrorCodes::WebSocketClient_Too_Old) { - error_code = ClientError::client_too_old_for_server; - constexpr bool is_fatal = true; - m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_fatal_error; - close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws - } - else if (status_code == ErrorCodes::WebSocketClient_Too_New) { - error_code = ClientError::client_too_new_for_server; - constexpr bool is_fatal = true; - m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_fatal_error; - close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws - } - else if (status_code == ErrorCodes::WebSocketProtocol_Mismatch) { - error_code = ClientError::protocol_mismatch; - constexpr bool is_fatal = true; - m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_fatal_error; - close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws - } - else if (status_code == ErrorCodes::WebSocketFatalError || status_code == ErrorCodes::WebSocketForbidden) { - constexpr bool is_fatal = true; - m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_fatal_error; - close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws - } - else if (status_code == ErrorCodes::WebSocketUnauthorized || - status_code == ErrorCodes::WebSocketMovedPermanently || - status_code == ErrorCodes::WebSocketInternalServerError || - status_code == ErrorCodes::WebSocketAbnormalClosure || status_code == ErrorCodes::WebSocketRetryError) { - constexpr bool is_fatal = false; - m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_nonfatal_error; - close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws + switch (static_cast(error_code.value())) { + case WebSocketError::websocket_ok: + break; + case WebSocketError::websocket_resolve_failed: + [[fallthrough]]; + case WebSocketError::websocket_connection_failed: { + m_reconnect_info.m_reason = ConnectionTerminationReason::connect_operation_failed; + constexpr bool try_again = true; + involuntary_disconnect(SessionErrorInfo{error_code, try_again}); // Throws + break; + } + case WebSocketError::websocket_read_error: + [[fallthrough]]; + case WebSocketError::websocket_write_error: { + read_or_write_error(error_code, status.reason()); // Throws + break; + } + case WebSocketError::websocket_going_away: + [[fallthrough]]; + case WebSocketError::websocket_protocol_error: + [[fallthrough]]; + case WebSocketError::websocket_unsupported_data: + [[fallthrough]]; + case WebSocketError::websocket_invalid_payload_data: + [[fallthrough]]; + case WebSocketError::websocket_policy_violation: + [[fallthrough]]; + case WebSocketError::websocket_reserved: + [[fallthrough]]; + case WebSocketError::websocket_no_status_received: + [[fallthrough]]; + case WebSocketError::websocket_invalid_extension: { + m_reconnect_info.m_reason = ConnectionTerminationReason::websocket_protocol_violation; + constexpr bool try_again = true; + SessionErrorInfo error_info{error_code, status.reason(), try_again}; + involuntary_disconnect(std::move(error_info)); // Throws + break; + } + case WebSocketError::websocket_message_too_big: { + m_reconnect_info.m_reason = ConnectionTerminationReason::websocket_protocol_violation; + constexpr bool try_again = true; + auto ec = make_error_code(ProtocolError::limits_exceeded); + auto message = + util::format("Sync websocket closed because the server received a message that was too large: %1", + status.reason()); + SessionErrorInfo error_info(ec, message, try_again); + error_info.server_requests_action = ProtocolErrorInfo::Action::ClientReset; + involuntary_disconnect(std::move(error_info)); // Throws + break; + } + case WebSocketError::websocket_tls_handshake_failed: { + error_code = ClientError::ssl_server_cert_rejected; + constexpr bool is_fatal = true; + m_reconnect_info.m_reason = ConnectionTerminationReason::ssl_certificate_rejected; + close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws + break; + } + case WebSocketError::websocket_client_too_old: { + error_code = ClientError::client_too_old_for_server; + constexpr bool is_fatal = true; + m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_fatal_error; + close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws + break; + } + case WebSocketError::websocket_client_too_new: { + error_code = ClientError::client_too_new_for_server; + constexpr bool is_fatal = true; + m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_fatal_error; + close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws + break; + } + case WebSocketError::websocket_protocol_mismatch: { + error_code = ClientError::protocol_mismatch; + constexpr bool is_fatal = true; + m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_fatal_error; + close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws + break; + } + case WebSocketError::websocket_fatal_error: + [[fallthrough]]; + case WebSocketError::websocket_forbidden: { + constexpr bool is_fatal = true; + m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_fatal_error; + close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws + break; + } + case WebSocketError::websocket_unauthorized: + [[fallthrough]]; + case WebSocketError::websocket_moved_permanently: + [[fallthrough]]; + case WebSocketError::websocket_internal_server_error: + [[fallthrough]]; + case WebSocketError::websocket_abnormal_closure: + [[fallthrough]]; + case WebSocketError::websocket_retry_error: { + constexpr bool is_fatal = false; + m_reconnect_info.m_reason = ConnectionTerminationReason::http_response_says_nonfatal_error; + close_due_to_client_side_error(error_code, status.reason(), is_fatal); // Throws + break; + } } return bool(m_websocket); diff --git a/src/realm/sync/noinst/server/server.cpp b/src/realm/sync/noinst/server/server.cpp index e8512dfe46c..d74176c43ad 100644 --- a/src/realm/sync/noinst/server/server.cpp +++ b/src/realm/sync/noinst/server/server.cpp @@ -1822,16 +1822,16 @@ class HTTPConnection { websocket::make_http_response(request, sec_websocket_protocol_2, ec); // Throws if (ec) { - if (ec == websocket::Error::bad_request_header_upgrade) { + if (ec == websocket::HttpError::bad_request_header_upgrade) { logger.error("There must be a header of the form 'Upgrade: websocket'"); } - else if (ec == websocket::Error::bad_request_header_connection) { + else if (ec == websocket::HttpError::bad_request_header_connection) { logger.error("There must be a header of the form 'Connection: Upgrade'"); } - else if (ec == websocket::Error::bad_request_header_websocket_version) { + else if (ec == websocket::HttpError::bad_request_header_websocket_version) { logger.error("There must be a header of the form 'Sec-WebSocket-Version: 13'"); } - else if (ec == websocket::Error::bad_request_header_websocket_key) { + else if (ec == websocket::HttpError::bad_request_header_websocket_key) { logger.error("The header Sec-WebSocket-Key is missing"); } diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index c8497060171..f7355176e51 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -26,8 +26,7 @@ #if REALM_ENABLE_AUTH_TESTS #include #include -#include -#include +#include #include #include "sync/sync_test_utils.hpp" #include "util/baas_admin_api.hpp" @@ -696,13 +695,14 @@ TEST_CASE("C API (non-database)", "[c_api]") { CHECK(ec_check.category() == std::system_category()); CHECK(ec_check.value() == int(error_code.value())); - error_code = make_error_code(sync::network::ResolveErrors::socket_type_not_supported); + error_code.assign(ErrorCodes::WebSocketResolveFailedError, + realm::sync::websocket::websocket_error_category()); error = c_api::to_capi(SystemError(error_code, "").to_status(), message); - CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_RESOLVE); - CHECK(error.value == realm_sync_error_resolve_e::RLM_SYNC_ERROR_RESOLVE_SOCKET_TYPE_NOT_SUPPORTED); + CHECK(error.category == realm_sync_error_category_e::RLM_SYNC_ERROR_CATEGORY_WEBSOCKET); + CHECK(error.value == realm_errno::RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR); c_api::sync_error_to_error_code(error, &ec_check); - CHECK(ec_check.category() == realm::sync::network::resolve_error_category()); + CHECK(ec_check.category() == realm::sync::websocket::websocket_error_category()); CHECK(ec_check.value() == int(error_code.value())); error_code = make_error_code(util::error::misc_errors::unknown); @@ -5948,7 +5948,7 @@ TEST_CASE("C API app: websocket provider", "[c_api][sync][app]") { auto test_data = static_cast(userdata); REQUIRE(test_data); auto cb = [callback_copy = callback](Status s) { - realm_sync_socket_callback_complete(callback_copy, static_cast(s.code()), + realm_sync_socket_callback_complete(callback_copy, static_cast(s.code()), s.reason().c_str()); }; test_data->socket_provider->post(std::move(cb)); diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 176209c6ea4..a1426f60f86 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -4018,19 +4018,19 @@ TEST_CASE("Immutable Realms") { SECTION("unsupported functions") { SECTION("update_schema()") { - REQUIRE_THROWS_AS(realm->compact(), std::logic_error); + REQUIRE_THROWS_AS(realm->compact(), WrongTransactionState); } SECTION("begin_transaction()") { - REQUIRE_THROWS_AS(realm->begin_transaction(), std::logic_error); + REQUIRE_THROWS_AS(realm->begin_transaction(), WrongTransactionState); } SECTION("async_begin_transaction()") { - REQUIRE_THROWS_AS(realm->async_begin_transaction(nullptr), std::logic_error); + REQUIRE_THROWS_AS(realm->async_begin_transaction(nullptr), WrongTransactionState); } SECTION("refresh()") { - REQUIRE_THROWS_AS(realm->refresh(), std::logic_error); + REQUIRE_THROWS_AS(realm->refresh(), WrongTransactionState); } SECTION("compact()") { - REQUIRE_THROWS_AS(realm->compact(), std::logic_error); + REQUIRE_THROWS_AS(realm->compact(), WrongTransactionState); } } diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index dfce9b9dc01..ed4cc2aa804 100644 --- a/test/object-store/sync/client_reset.cpp +++ b/test/object-store/sync/client_reset.cpp @@ -227,7 +227,7 @@ TEST_CASE("sync: pending client resets are cleared when downloads are complete", SyncTestFile realm_config(app->current_user(), partition.value, schema); realm_config.sync_config->client_resync_mode = ClientResyncMode::Recover; realm_config.sync_config->error_handler = [&](std::shared_ptr, SyncError err) { - if (err.code() == ErrorCodes::ReadError) { + if (err.get_system_error() == sync::websocket::WebSocketError::websocket_read_error) { return; } diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 0547dc842bb..181226888fc 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -4439,7 +4439,8 @@ TEST(Sync_ServerDiscardDeadConnections) BowlOfStonesSemaphore bowl; auto error_handler = [&](std::error_code ec, bool, const std::string&) { - CHECK_EQUAL(ec, sync::websocket::make_error_code(ErrorCodes::ReadError)); + bool valid_error = ec == sync::websocket::WebSocketError::websocket_read_error; + CHECK(valid_error); bowl.add_stone(); }; fixture.set_client_side_error_handler(std::move(error_handler));