From faa91d13fc58dc5739807671c047a9985ab0ae72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Wed, 1 Feb 2023 13:33:15 +0100 Subject: [PATCH 01/11] Make the network code compile The handling of error codes is a somewhat hybrid solution. To put it nicely. --- src/realm/error_codes.cpp | 85 ++++++++++++++------ src/realm/error_codes.h | 18 ++--- src/realm/error_codes.hpp | 43 +++++++--- src/realm/object-store/sync/sync_session.cpp | 6 +- src/realm/sync/network/default_socket.cpp | 51 +++++++----- src/realm/sync/network/websocket.cpp | 8 +- src/realm/sync/network/websocket.hpp | 2 +- src/realm/sync/noinst/client_impl_base.cpp | 60 +++++++++----- test/object-store/sync/client_reset.cpp | 2 +- test/test_sync.cpp | 2 +- 10 files changed, 178 insertions(+), 99 deletions(-) diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index ac9e627325f..81248635515 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -207,18 +207,13 @@ 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 WebSocketResolveFailed: + case WebSocketConnectionFailed: + case WebSocketReadError: + case WebSocketWriteError: + case WebSocketRetryError: + case WebSocketFatalError: + return ErrorCategory().set(ErrorCategory::runtime_error).set(ErrorCategory::websocket_error); case UnknownError: @@ -374,18 +369,12 @@ 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}, + {"WebSocketConnectionFailed", ErrorCodes::WebSocketConnectionFailed}, + {"WebSocketFatalError", ErrorCodes::WebSocketFatalError}, + {"WebSocketReadError", ErrorCodes::WebSocketReadError}, + {"WebSocketResolveFailed", ErrorCodes::WebSocketResolveFailed}, + {"WebSocketRetryError", ErrorCodes::WebSocketRetryError}, + {"WebSocketWriteError", ErrorCodes::WebSocketWriteError}, {"WrongThread", ErrorCodes::WrongThread}, {"WrongTransactionState", ErrorCodes::WrongTransactionState}, }; @@ -440,6 +429,54 @@ std::string_view ErrorCodes::error_string(Error code) return error_codes_map[code]; } +std::string ErrorCodes::error_string(WebSocketError code) +{ + /// WebSocket error codes + switch (code) { + case WebSocketOK: + return "WebSocket: OK"; + case WebSocketGoingAway: + return "WebSocket: Going Away"; + case WebSocketProtocolError: + return "WebSocket: Protocol Error"; + case WebSocketUnsupportedData: + return "WebSocket: Unsupported Data"; + case WebSocketReserved: + return "WebSocket: Reserved"; + case WebSocketNoStatusReceived: + return "WebSocket: No Status Received"; + case WebSocketAbnormalClosure: + return "WebSocket: Abnormal Closure"; + case WebSocketInvalidPayloadData: + return "WebSocket: Invalid Payload Data"; + case WebSocketPolicyViolation: + return "WebSocket: Policy Violation"; + case WebSocketMessageTooBig: + return "WebSocket: Message Too Big"; + case WebSocketInavalidExtension: + return "WebSocket: Invalid Extension"; + case WebSocketInternalServerError: + return "WebSocket: Internal Server Error"; + case WebSocketTLSHandshakeFailed: + return "WebSocket: TLS Handshake Failed"; + + /// WebSocket Errors - reported by server + case WebSocketUnauthorized: + return "WebSocket: Unauthorized"; + case WebSocketForbidden: + return "WebSocket: Forbidden"; + case WebSocketMovedPermanently: + return "WebSocket: Moved Permanently"; + case WebSocketClient_Too_Old: + return "WebSocket: Client Too Old"; + case WebSocketClient_Too_New: + return "WebSocket: Client Too New"; + case WebSocketProtocol_Mismatch: + return "WebSocket: Protocol Mismatch"; + } + return ""; +} + ErrorCodes::Error ErrorCodes::from_string(std::string_view name) { return error_codes_map[name]; diff --git a/src/realm/error_codes.h b/src/realm/error_codes.h index c55416b17b4..f92153c7e90 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -183,18 +183,12 @@ 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 = 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, RLM_ERR_CALLBACK = 1000000, /**< A user-provided callback failed. */ RLM_ERR_UNKNOWN = 2000000 /* Should not be used in code */ diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index 1f5b77414b4..c68bcacf651 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -226,25 +226,44 @@ class ErrorCodes { 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 + WebSocketResolveFailed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, + WebSocketConnectionFailed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, + WebSocketReadError = RLM_ERR_WEBSOCKET_RETRY_ERROR, + WebSocketWriteError = RLM_ERR_WEBSOCKET_READ_ERROR, + WebSocketRetryError = RLM_ERR_WEBSOCKET_WRITE_ERROR, + WebSocketFatalError = RLM_ERR_WEBSOCKET_FATAL_ERROR, CallbackFailed = RLM_ERR_CALLBACK, UnknownError = RLM_ERR_UNKNOWN, }; + enum WebSocketError : int32_t { + WebSocketOK = 1000, + WebSocketGoingAway = 1001, + WebSocketProtocolError = 1002, + WebSocketUnsupportedData = 1003, + WebSocketReserved = 1004, + WebSocketNoStatusReceived = 1005, + WebSocketAbnormalClosure = 1006, + WebSocketInvalidPayloadData = 1007, + WebSocketPolicyViolation = 1008, + WebSocketMessageTooBig = 1009, + WebSocketInavalidExtension = 1010, + WebSocketInternalServerError = 1011, + WebSocketTLSHandshakeFailed = 1015, // Used by default WebSocket + + // WebSocket Errors - reported by server + WebSocketUnauthorized = 4001, + WebSocketForbidden = 4002, + WebSocketMovedPermanently = 4003, + WebSocketClient_Too_Old = 4004, + WebSocketClient_Too_New = 4005, + WebSocketProtocol_Mismatch = 4006, + }; + static ErrorCategory error_categories(Error code); static std::string_view error_string(Error code); + static std::string error_string(WebSocketError code); static Error from_string(std::string_view str); static std::vector get_all_codes(); static std::vector get_all_names(); diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index a18ec5d8354..a3ac7d9a8aa 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -654,9 +654,9 @@ void SyncSession::handle_error(SyncError error) // 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() && - (error_code.value() == ErrorCodes::WebSocket_Unauthorized || - error_code.value() == ErrorCodes::WebSocket_AbnormalClosure || - error_code.value() == ErrorCodes::WebSocket_MovedPermanently)) { + (error_code.value() == ErrorCodes::WebSocketUnauthorized || + error_code.value() == ErrorCodes::WebSocketAbnormalClosure || + error_code.value() == ErrorCodes::WebSocketMovedPermanently)) { if (auto u = user()) { u->refresh_custom_data(handle_refresh(shared_from_this())); return; diff --git a/src/realm/sync/network/default_socket.cpp b/src/realm/sync/network/default_socket.cpp index 83c36c21a32..089a985e2b2 100644 --- a/src/realm/sync/network/default_socket.cpp +++ b/src/realm/sync/network/default_socket.cpp @@ -73,44 +73,45 @@ class DefaultWebSocketImpl final : public WebSocketInterface, 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{ErrorCodes::WebSocketReadError, 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{ErrorCodes::WebSocketWriteError, ec.message()}); } void websocket_handshake_error_handler(std::error_code ec, const HTTPHeaders*, const std::string_view* body) override { - ErrorCodes::Error error; + ErrorCodes::WebSocketError error = ErrorCodes::WebSocketOK; + ErrorCodes::Error error_code = ErrorCodes::OK; bool was_clean = true; if (ec == websocket::Error::bad_response_301_moved_permanently || ec == websocket::Error::bad_response_308_permanent_redirect) { - error = ErrorCodes::WebSocket_MovedPermanently; + error = ErrorCodes::WebSocketMovedPermanently; } else if (ec == websocket::Error::bad_response_3xx_redirection) { - error = ErrorCodes::WebSocket_Retry_Error; + error_code = ErrorCodes::WebSocketRetryError; was_clean = false; } else if (ec == websocket::Error::bad_response_401_unauthorized) { - error = ErrorCodes::WebSocket_Unauthorized; + error = ErrorCodes::WebSocketUnauthorized; } else if (ec == websocket::Error::bad_response_403_forbidden) { - error = ErrorCodes::WebSocket_Forbidden; + error = ErrorCodes::WebSocketForbidden; } 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::WebSocket_InternalServerError; + error = ErrorCodes::WebSocketInternalServerError; was_clean = false; } else { - error = ErrorCodes::WebSocket_Fatal_Error; + error_code = ErrorCodes::WebSocketFatalError; was_clean = false; if (body) { std::string_view identifier = "REALM_SYNC_PROTOCOL_MISMATCH"; @@ -123,26 +124,32 @@ class DefaultWebSocketImpl final : public WebSocketInterface, public Config { std::equal(string.data(), string.data() + prefix.size(), prefix.data())); }; if (begins_with(rest, ":CLIENT_TOO_OLD")) { - error = ErrorCodes::WebSocket_Client_Too_Old; + error = ErrorCodes::WebSocketClient_Too_Old; } else if (begins_with(rest, ":CLIENT_TOO_NEW")) { - error = ErrorCodes::WebSocket_Client_Too_New; + error = ErrorCodes::WebSocketClient_Too_New; } else { // Other more complicated forms of mismatch - error = ErrorCodes::WebSocket_Protocol_Mismatch; + error = ErrorCodes::WebSocketProtocol_Mismatch; } was_clean = true; } } } - websocket_error_and_close_handler(was_clean, Status{error, ec.message()}); + if (error != ErrorCodes::WebSocketOK) { + websocket_error_and_close_handler(was_clean, Status{make_error_code(error), ec.message()}); + } + else { + websocket_error_and_close_handler(was_clean, Status{error_code, 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::WebSocket_ProtocolError, ec.message()}); + websocket_error_and_close_handler(was_clean, + Status{make_error_code(ErrorCodes::WebSocketProtocolError), ec.message()}); } bool websocket_close_message_received(std::error_code ec, StringData message) override { @@ -268,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{ErrorCodes::WebSocketResolveFailed, ec.message()}); // Throws return; } @@ -308,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{ErrorCodes::WebSocketConnectionFailed, ec.message()}); // Throws return; } @@ -349,15 +358,15 @@ void DefaultWebSocketImpl::initiate_http_tunnel() 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 + Status{ErrorCodes::WebSocketConnectionFailed, 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{ErrorCodes::WebSocketConnectionFailed, response.reason}); // Throws return; } @@ -420,8 +429,8 @@ 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::WebSocket_TLSHandshakeFailed, ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, Status{make_error_code(ErrorCodes::WebSocketTLSHandshakeFailed), ec.message()}); // Throws return; } diff --git a/src/realm/sync/network/websocket.cpp b/src/realm/sync/network/websocket.cpp index 7f0b63695b1..b378d3e6e87 100644 --- a/src/realm/sync/network/websocket.cpp +++ b/src/realm/sync/network/websocket.cpp @@ -1109,10 +1109,10 @@ class CloseStatusErrorCategory : public std::error_category { { // 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 ErrorCodes::error_string(ErrorCodes::OK); + if (error_code == 0) { + return "OK"; } - return ErrorCodes::error_string(static_cast(error_code)); + return ErrorCodes::error_string(static_cast(error_code)); } }; @@ -1238,7 +1238,7 @@ const std::error_category& websocket::websocket_close_status_category() noexcept return category; } -std::error_code websocket::make_error_code(ErrorCodes::Error error) noexcept +std::error_code websocket::make_error_code(ErrorCodes::WebSocketError error) noexcept { return std::error_code{error, realm::sync::websocket::websocket_close_status_category()}; } diff --git a/src/realm/sync/network/websocket.hpp b/src/realm/sync/network/websocket.hpp index cd96f208770..bff92ef1df2 100644 --- a/src/realm/sync/network/websocket.hpp +++ b/src/realm/sync/network/websocket.hpp @@ -215,7 +215,7 @@ enum class Error { const std::error_category& websocket_close_status_category() noexcept; -std::error_code make_error_code(ErrorCodes::Error error) noexcept; +std::error_code make_error_code(ErrorCodes::WebSocketError error) noexcept; const std::error_category& error_category() noexcept; diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 9383ec8b3c8..85ac0b8d07d 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -403,6 +403,19 @@ void Connection::websocket_error_handler() m_websocket_error_received = true; } +namespace { +class DummyErrorCategory : public std::error_category { + const char* name() const noexcept final + { + return "realm::ErrorCodes"; + } + std::string message(int error_code) const final + { + return std::string(ErrorCodes::error_string(static_cast(error_code))); + } +}; +DummyErrorCategory g_dummy_category; +} // namespace bool Connection::websocket_closed_handler(bool was_clean, Status status) { @@ -412,29 +425,37 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) return bool(m_websocket); } - auto&& status_code = status.code(); - std::error_code error_code{static_cast(status_code), websocket::websocket_close_status_category()}; + int status_code = status.code(); + std::error_code error_code; + if (status_code == ErrorCodes::SystemError) { + error_code = status.get_std_error_code(); + status_code = error_code.value(); + } + else { + // HACK: + error_code = std::error_code{status_code, g_dummy_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) { + if (status_code == ErrorCodes::WebSocketResolveFailed || status_code == ErrorCodes::WebSocketConnectionFailed) { 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) { + else if (status_code == ErrorCodes::WebSocketReadError || status_code == ErrorCodes::WebSocketWriteError) { read_or_write_error(error_code); } - else if (status_code == ErrorCodes::WebSocket_GoingAway || status_code == ErrorCodes::WebSocket_ProtocolError || - status_code == ErrorCodes::WebSocket_UnsupportedData || status_code == ErrorCodes::WebSocket_Reserved || - status_code == ErrorCodes::WebSocket_InvalidPayloadData || - status_code == ErrorCodes::WebSocket_PolicyViolation || - status_code == ErrorCodes::WebSocket_InavalidExtension) { + 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::WebSocket_MessageTooBig) { + 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); @@ -444,40 +465,39 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) error_info.server_requests_action = ProtocolErrorInfo::Action::ClientReset; involuntary_disconnect(std::move(error_info)); } - else if (status_code == ErrorCodes::WebSocket_TLSHandshakeFailed) { + 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, std::nullopt, is_fatal); // Throws } - else if (status_code == ErrorCodes::WebSocket_Client_Too_Old) { + 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, std::nullopt, is_fatal); // Throws } - else if (status_code == ErrorCodes::WebSocket_Client_Too_New) { + 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, std::nullopt, is_fatal); // Throws } - else if (status_code == ErrorCodes::WebSocket_Protocol_Mismatch) { + 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, std::nullopt, is_fatal); // Throws } - else if (status_code == ErrorCodes::WebSocket_Retry_Error || status_code == ErrorCodes::WebSocket_Forbidden) { + else if (status_code == ErrorCodes::WebSocketRetryError || 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, std::nullopt, is_fatal); // Throws } - else if (status_code == ErrorCodes::WebSocket_Unauthorized || - status_code == ErrorCodes::WebSocket_MovedPermanently || - status_code == ErrorCodes::WebSocket_InternalServerError || - status_code == ErrorCodes::WebSocket_AbnormalClosure || - status_code == ErrorCodes::WebSocket_Retry_Error) { + 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, std::nullopt, is_fatal); // Throws diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index e7b3a41f843..2ec78e1b79b 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.code() == ErrorCodes::WebSocketReadError) { return; } diff --git a/test/test_sync.cpp b/test/test_sync.cpp index f025edb441b..81fe375876d 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -4458,7 +4458,7 @@ TEST(Sync_ServerDiscardDeadConnections) BowlOfStonesSemaphore bowl; auto error_handler = [&](std::error_code ec, bool, const std::string&) { - bool valid_error = ec == sync::websocket::make_error_code(ErrorCodes::ReadError); + bool valid_error = ec.value() == ErrorCodes::WebSocketReadError; CHECK(valid_error); bowl.add_stone(); }; From a51136f717029f9d84ca43c07632638f8274d700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Fri, 3 Feb 2023 10:52:58 +0100 Subject: [PATCH 02/11] More clean approach --- src/realm/error_codes.cpp | 28 ++++++------ src/realm/error_codes.h | 37 ++++++++++++--- src/realm/error_codes.hpp | 52 +++++++++++----------- src/realm/sync/network/default_socket.cpp | 32 ++++++------- src/realm/sync/noinst/client_impl_base.cpp | 12 +---- test/object-store/sync/client_reset.cpp | 2 +- 6 files changed, 86 insertions(+), 77 deletions(-) diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index 81248635515..dfb87d9f48f 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -207,15 +207,6 @@ ErrorCategory ErrorCodes::error_categories(Error code) .set(ErrorCategory::app_error) .set(ErrorCategory::service_error); - case WebSocketResolveFailed: - case WebSocketConnectionFailed: - case WebSocketReadError: - case WebSocketWriteError: - case WebSocketRetryError: - case WebSocketFatalError: - - return ErrorCategory().set(ErrorCategory::runtime_error).set(ErrorCategory::websocket_error); - case UnknownError: break; } @@ -369,12 +360,6 @@ static const MapElem string_to_error_code[] = { {"ValueAlreadyExists", ErrorCodes::ValueAlreadyExists}, {"ValueDuplicateName", ErrorCodes::ValueDuplicateName}, {"ValueNotFound", ErrorCodes::ValueNotFound}, - {"WebSocketConnectionFailed", ErrorCodes::WebSocketConnectionFailed}, - {"WebSocketFatalError", ErrorCodes::WebSocketFatalError}, - {"WebSocketReadError", ErrorCodes::WebSocketReadError}, - {"WebSocketResolveFailed", ErrorCodes::WebSocketResolveFailed}, - {"WebSocketRetryError", ErrorCodes::WebSocketRetryError}, - {"WebSocketWriteError", ErrorCodes::WebSocketWriteError}, {"WrongThread", ErrorCodes::WrongThread}, {"WrongTransactionState", ErrorCodes::WrongTransactionState}, }; @@ -473,6 +458,19 @@ std::string ErrorCodes::error_string(WebSocketError code) return "WebSocket: Client Too New"; case WebSocketProtocol_Mismatch: return "WebSocket: Protocol Mismatch"; + + case WebSocketResolveFailed: + return "WebSocket: Resolve Failed"; + case WebSocketConnectionFailed: + return "WebSocket: Connection Failed"; + case WebSocketReadError: + return "WebSocket: Read Error"; + case WebSocketWriteError: + return "WebSocket: Write Error"; + case WebSocketRetryError: + return "WebSocket: Retry Error"; + case WebSocketFatalError: + return "WebSocket: Fatal Error"; } return ""; } diff --git a/src/realm/error_codes.h b/src/realm/error_codes.h index f92153c7e90..9aa8a1622ac 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -183,13 +183,6 @@ typedef enum realm_errno { RLM_ERR_MAINTENANCE_IN_PROGRESS = 4352, RLM_ERR_USERPASS_TOKEN_INVALID = 4353, - 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, - RLM_ERR_CALLBACK = 1000000, /**< A user-provided callback failed. */ RLM_ERR_UNKNOWN = 2000000 /* Should not be used in code */ } realm_errno_e; @@ -280,4 +273,34 @@ typedef enum realm_sync_errno_session { RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE = 231, } 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 c68bcacf651..ff407770695 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -226,39 +226,39 @@ class ErrorCodes { MaintenanceInProgress = RLM_ERR_MAINTENANCE_IN_PROGRESS, UserpassTokenInvalid = RLM_ERR_USERPASS_TOKEN_INVALID, - WebSocketResolveFailed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, - WebSocketConnectionFailed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, - WebSocketReadError = RLM_ERR_WEBSOCKET_RETRY_ERROR, - WebSocketWriteError = RLM_ERR_WEBSOCKET_READ_ERROR, - WebSocketRetryError = RLM_ERR_WEBSOCKET_WRITE_ERROR, - WebSocketFatalError = RLM_ERR_WEBSOCKET_FATAL_ERROR, - CallbackFailed = RLM_ERR_CALLBACK, UnknownError = RLM_ERR_UNKNOWN, }; enum WebSocketError : int32_t { - WebSocketOK = 1000, - WebSocketGoingAway = 1001, - WebSocketProtocolError = 1002, - WebSocketUnsupportedData = 1003, - WebSocketReserved = 1004, - WebSocketNoStatusReceived = 1005, - WebSocketAbnormalClosure = 1006, - WebSocketInvalidPayloadData = 1007, - WebSocketPolicyViolation = 1008, - WebSocketMessageTooBig = 1009, - WebSocketInavalidExtension = 1010, - WebSocketInternalServerError = 1011, - WebSocketTLSHandshakeFailed = 1015, // Used by default WebSocket + WebSocketOK = RLM_ERR_WEBSOCKET_OK, + 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 // WebSocket Errors - reported by server - WebSocketUnauthorized = 4001, - WebSocketForbidden = 4002, - WebSocketMovedPermanently = 4003, - WebSocketClient_Too_Old = 4004, - WebSocketClient_Too_New = 4005, - WebSocketProtocol_Mismatch = 4006, + WebSocketUnauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, + WebSocketForbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, + WebSocketMovedPermanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, + WebSocketClient_Too_Old = RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD, + WebSocketClient_Too_New = RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW, + WebSocketProtocol_Mismatch = RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH, + + WebSocketResolveFailed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, + WebSocketConnectionFailed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, + WebSocketReadError = RLM_ERR_WEBSOCKET_RETRY_ERROR, + WebSocketWriteError = RLM_ERR_WEBSOCKET_READ_ERROR, + WebSocketRetryError = RLM_ERR_WEBSOCKET_WRITE_ERROR, + WebSocketFatalError = RLM_ERR_WEBSOCKET_FATAL_ERROR, }; static ErrorCategory error_categories(Error code); diff --git a/src/realm/sync/network/default_socket.cpp b/src/realm/sync/network/default_socket.cpp index 089a985e2b2..ad26e11aa22 100644 --- a/src/realm/sync/network/default_socket.cpp +++ b/src/realm/sync/network/default_socket.cpp @@ -73,19 +73,20 @@ class DefaultWebSocketImpl final : public WebSocketInterface, 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::WebSocketReadError, ec.message()}); + websocket_error_and_close_handler(was_clean, + Status{make_error_code(ErrorCodes::WebSocketReadError), 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::WebSocketWriteError, ec.message()}); + websocket_error_and_close_handler(was_clean, + Status{make_error_code(ErrorCodes::WebSocketWriteError), ec.message()}); } void websocket_handshake_error_handler(std::error_code ec, const HTTPHeaders*, const std::string_view* body) override { ErrorCodes::WebSocketError error = ErrorCodes::WebSocketOK; - ErrorCodes::Error error_code = ErrorCodes::OK; bool was_clean = true; if (ec == websocket::Error::bad_response_301_moved_permanently || @@ -93,7 +94,7 @@ class DefaultWebSocketImpl final : public WebSocketInterface, public Config { error = ErrorCodes::WebSocketMovedPermanently; } else if (ec == websocket::Error::bad_response_3xx_redirection) { - error_code = ErrorCodes::WebSocketRetryError; + error = ErrorCodes::WebSocketRetryError; was_clean = false; } else if (ec == websocket::Error::bad_response_401_unauthorized) { @@ -111,7 +112,7 @@ class DefaultWebSocketImpl final : public WebSocketInterface, public Config { was_clean = false; } else { - error_code = ErrorCodes::WebSocketFatalError; + error = ErrorCodes::WebSocketFatalError; was_clean = false; if (body) { std::string_view identifier = "REALM_SYNC_PROTOCOL_MISMATCH"; @@ -138,12 +139,7 @@ class DefaultWebSocketImpl final : public WebSocketInterface, public Config { } } - if (error != ErrorCodes::WebSocketOK) { - websocket_error_and_close_handler(was_clean, Status{make_error_code(error), ec.message()}); - } - else { - websocket_error_and_close_handler(was_clean, Status{error_code, 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 { @@ -275,8 +271,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::WebSocketResolveFailed, ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, Status{make_error_code(ErrorCodes::WebSocketResolveFailed), ec.message()}); // Throws return; } @@ -316,8 +312,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::WebSocketConnectionFailed, ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, Status{make_error_code(ErrorCodes::WebSocketConnectionFailed), ec.message()}); // Throws return; } @@ -357,8 +353,8 @@ 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::WebSocketConnectionFailed, ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, Status{make_error_code(ErrorCodes::WebSocketConnectionFailed), ec.message()}); // Throws return; } @@ -366,7 +362,7 @@ void DefaultWebSocketImpl::initiate_http_tunnel() 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::WebSocketConnectionFailed, response.reason}); // Throws + was_clean, Status{make_error_code(ErrorCodes::WebSocketConnectionFailed), response.reason}); // Throws return; } diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 85ac0b8d07d..68ce7580b31 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -425,16 +425,8 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) return bool(m_websocket); } - int status_code = status.code(); - std::error_code error_code; - if (status_code == ErrorCodes::SystemError) { - error_code = status.get_std_error_code(); - status_code = error_code.value(); - } - else { - // HACK: - error_code = std::error_code{status_code, g_dummy_category}; - } + auto error_code = status.get_std_error_code(); + auto status_code = error_code.value(); // TODO: Use a switch statement once websocket errors have their own category in exception unification. if (status_code == ErrorCodes::WebSocketResolveFailed || status_code == ErrorCodes::WebSocketConnectionFailed) { diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index 2ec78e1b79b..bb3eb4dff1f 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::WebSocketReadError) { + if (err.get_system_error() == sync::websocket::make_error_code(ErrorCodes::WebSocketReadError)) { return; } From 725dcd7b398c4604b41f8bf33b6f4b7ed16e605b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Fri, 3 Feb 2023 15:14:46 +0100 Subject: [PATCH 03/11] Update after review --- src/realm.h | 4 ++-- .../object-store/c_api/socket_provider.cpp | 17 ++++++++++------- src/realm/sync/network/websocket.cpp | 3 --- src/realm/sync/noinst/client_impl_base.cpp | 14 -------------- 4 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/realm.h b/src/realm.h index 6b5378b6272..7772a2e1e34 100644 --- a/src/realm.h +++ b/src/realm.h @@ -4095,7 +4095,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); @@ -4106,7 +4106,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/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index 2f82c80913b..276b973e2f2 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 { @@ -215,11 +216,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 = ErrorCodes::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); } @@ -242,11 +244,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 = ErrorCodes::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); } @@ -256,4 +259,4 @@ RLM_API void realm_sync_client_config_set_sync_socket(realm_sync_client_config_t config->socket_provider = *sync_socket; } -} // namespace realm::c_api \ No newline at end of file +} // namespace realm::c_api diff --git a/src/realm/sync/network/websocket.cpp b/src/realm/sync/network/websocket.cpp index b378d3e6e87..5f1aa320e03 100644 --- a/src/realm/sync/network/websocket.cpp +++ b/src/realm/sync/network/websocket.cpp @@ -1109,9 +1109,6 @@ class CloseStatusErrorCategory : public std::error_category { { // 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 == 0) { - return "OK"; - } return ErrorCodes::error_string(static_cast(error_code)); } }; diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 68ce7580b31..436eb581a84 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -403,20 +403,6 @@ void Connection::websocket_error_handler() m_websocket_error_received = true; } -namespace { -class DummyErrorCategory : public std::error_category { - const char* name() const noexcept final - { - return "realm::ErrorCodes"; - } - std::string message(int error_code) const final - { - return std::string(ErrorCodes::error_string(static_cast(error_code))); - } -}; -DummyErrorCategory g_dummy_category; -} // namespace - bool Connection::websocket_closed_handler(bool was_clean, Status status) { logger.info("Closing the websocket with status='%1', was_clean='%2'", status, was_clean); From 8a432d15678abdab93a8e643d0cfdd64997c86df Mon Sep 17 00:00:00 2001 From: Daniel Tabacaru Date: Thu, 9 Feb 2023 13:28:41 +0100 Subject: [PATCH 04/11] Clean-up --- src/realm.h | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/realm.h b/src/realm.h index 7772a2e1e34..51bf06b2579 100644 --- a/src/realm.h +++ b/src/realm.h @@ -4062,30 +4062,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, From 3a2f7608083d4a0820b2f2e2c40c7cc02a8a620e Mon Sep 17 00:00:00 2001 From: Daniel Tabacaru Date: Thu, 9 Feb 2023 20:45:45 +0100 Subject: [PATCH 05/11] fixes --- src/realm/error_codes.hpp | 6 +++--- src/realm/sync/network/default_socket.cpp | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index ff407770695..ba66d3e8de9 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -255,9 +255,9 @@ class ErrorCodes { WebSocketResolveFailed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, WebSocketConnectionFailed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, - WebSocketReadError = RLM_ERR_WEBSOCKET_RETRY_ERROR, - WebSocketWriteError = RLM_ERR_WEBSOCKET_READ_ERROR, - WebSocketRetryError = RLM_ERR_WEBSOCKET_WRITE_ERROR, + WebSocketReadError = RLM_ERR_WEBSOCKET_READ_ERROR, + WebSocketWriteError = RLM_ERR_WEBSOCKET_WRITE_ERROR, + WebSocketRetryError = RLM_ERR_WEBSOCKET_RETRY_ERROR, WebSocketFatalError = RLM_ERR_WEBSOCKET_FATAL_ERROR, }; diff --git a/src/realm/sync/network/default_socket.cpp b/src/realm/sync/network/default_socket.cpp index ad26e11aa22..160e7f915ff 100644 --- a/src/realm/sync/network/default_socket.cpp +++ b/src/realm/sync/network/default_socket.cpp @@ -155,8 +155,7 @@ class DefaultWebSocketImpl final : public WebSocketInterface, 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) { From 83cc6f2b0f75981cf663061ab9f3443a3e6fa91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Mon, 20 Feb 2023 13:04:19 +0100 Subject: [PATCH 06/11] Move WebSocketError definition to websocket.hpp --- src/realm/error_codes.cpp | 61 ----------------- src/realm/error_codes.hpp | 32 --------- .../object-store/c_api/socket_provider.cpp | 4 +- src/realm/object-store/sync/sync_session.cpp | 6 +- src/realm/sync/network/default_socket.cpp | 43 ++++++------ src/realm/sync/network/websocket.cpp | 65 ++++++++++++++++++- src/realm/sync/network/websocket.hpp | 34 +++++++++- src/realm/sync/noinst/client_impl_base.cpp | 33 +++++----- test/test_sync.cpp | 2 +- 9 files changed, 138 insertions(+), 142 deletions(-) diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index dfb87d9f48f..d2960dbebb7 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -414,67 +414,6 @@ std::string_view ErrorCodes::error_string(Error code) return error_codes_map[code]; } -std::string ErrorCodes::error_string(WebSocketError code) -{ - /// WebSocket error codes - switch (code) { - case WebSocketOK: - return "WebSocket: OK"; - case WebSocketGoingAway: - return "WebSocket: Going Away"; - case WebSocketProtocolError: - return "WebSocket: Protocol Error"; - case WebSocketUnsupportedData: - return "WebSocket: Unsupported Data"; - case WebSocketReserved: - return "WebSocket: Reserved"; - case WebSocketNoStatusReceived: - return "WebSocket: No Status Received"; - case WebSocketAbnormalClosure: - return "WebSocket: Abnormal Closure"; - case WebSocketInvalidPayloadData: - return "WebSocket: Invalid Payload Data"; - case WebSocketPolicyViolation: - return "WebSocket: Policy Violation"; - case WebSocketMessageTooBig: - return "WebSocket: Message Too Big"; - case WebSocketInavalidExtension: - return "WebSocket: Invalid Extension"; - case WebSocketInternalServerError: - return "WebSocket: Internal Server Error"; - case WebSocketTLSHandshakeFailed: - return "WebSocket: TLS Handshake Failed"; - - /// WebSocket Errors - reported by server - case WebSocketUnauthorized: - return "WebSocket: Unauthorized"; - case WebSocketForbidden: - return "WebSocket: Forbidden"; - case WebSocketMovedPermanently: - return "WebSocket: Moved Permanently"; - case WebSocketClient_Too_Old: - return "WebSocket: Client Too Old"; - case WebSocketClient_Too_New: - return "WebSocket: Client Too New"; - case WebSocketProtocol_Mismatch: - return "WebSocket: Protocol Mismatch"; - - case WebSocketResolveFailed: - return "WebSocket: Resolve Failed"; - case WebSocketConnectionFailed: - return "WebSocket: Connection Failed"; - case WebSocketReadError: - return "WebSocket: Read Error"; - case WebSocketWriteError: - return "WebSocket: Write Error"; - case WebSocketRetryError: - return "WebSocket: Retry Error"; - case WebSocketFatalError: - return "WebSocket: Fatal Error"; - } - return ""; -} - ErrorCodes::Error ErrorCodes::from_string(std::string_view name) { return error_codes_map[name]; diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index ba66d3e8de9..497f91c94ec 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -230,40 +230,8 @@ class ErrorCodes { UnknownError = RLM_ERR_UNKNOWN, }; - enum WebSocketError : int32_t { - WebSocketOK = RLM_ERR_WEBSOCKET_OK, - 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 - - // WebSocket Errors - reported by server - WebSocketUnauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, - WebSocketForbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, - WebSocketMovedPermanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, - WebSocketClient_Too_Old = RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD, - WebSocketClient_Too_New = RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW, - WebSocketProtocol_Mismatch = RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH, - - WebSocketResolveFailed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, - WebSocketConnectionFailed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, - WebSocketReadError = RLM_ERR_WEBSOCKET_READ_ERROR, - WebSocketWriteError = RLM_ERR_WEBSOCKET_WRITE_ERROR, - WebSocketRetryError = RLM_ERR_WEBSOCKET_RETRY_ERROR, - WebSocketFatalError = RLM_ERR_WEBSOCKET_FATAL_ERROR, - }; - static ErrorCategory error_categories(Error code); static std::string_view error_string(Error code); - static std::string error_string(WebSocketError code); static Error from_string(std::string_view str); static std::vector get_all_codes(); static std::vector get_all_names(); diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index 276b973e2f2..84e406c8bea 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -218,7 +218,7 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback* realm_callback, realm_web_socket_errno_e code, const char* reason) { - auto status = ErrorCodes::WebSocketError(code); + auto status = sync::websocket::WebSocketError(code); auto complete_status = code == realm_web_socket_errno_e::RLM_ERR_WEBSOCKET_OK ? Status::OK() : Status{sync::websocket::make_error_code(status), reason}; @@ -246,7 +246,7 @@ 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, realm_web_socket_errno_e code, const char* reason) { - auto status = ErrorCodes::WebSocketError(code); + auto status = sync::websocket::WebSocketError(code); auto closed_status = code == realm_web_socket_errno_e::RLM_ERR_WEBSOCKET_OK ? Status::OK() : Status{sync::websocket::make_error_code(status), reason}; diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index a3ac7d9a8aa..137fff90025 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -654,9 +654,9 @@ void SyncSession::handle_error(SyncError error) // 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() && - (error_code.value() == ErrorCodes::WebSocketUnauthorized || - error_code.value() == ErrorCodes::WebSocketAbnormalClosure || - error_code.value() == ErrorCodes::WebSocketMovedPermanently)) { + (error_code.value() == sync::websocket::WebSocketUnauthorized || + error_code.value() == sync::websocket::WebSocketAbnormalClosure || + error_code.value() == sync::websocket::WebSocketMovedPermanently)) { if (auto u = user()) { u->refresh_custom_data(handle_refresh(shared_from_this())); return; diff --git a/src/realm/sync/network/default_socket.cpp b/src/realm/sync/network/default_socket.cpp index 160e7f915ff..325cb4b6f31 100644 --- a/src/realm/sync/network/default_socket.cpp +++ b/src/realm/sync/network/default_socket.cpp @@ -73,46 +73,44 @@ class DefaultWebSocketImpl final : public WebSocketInterface, public Config { { m_logger.error("Reading failed: %1", ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, - Status{make_error_code(ErrorCodes::WebSocketReadError), ec.message()}); + websocket_error_and_close_handler(was_clean, Status{make_error_code(WebSocketReadError), 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{make_error_code(ErrorCodes::WebSocketWriteError), ec.message()}); + websocket_error_and_close_handler(was_clean, Status{make_error_code(WebSocketWriteError), ec.message()}); } void websocket_handshake_error_handler(std::error_code ec, const HTTPHeaders*, const std::string_view* body) override { - ErrorCodes::WebSocketError error = ErrorCodes::WebSocketOK; + WebSocketError error = WebSocketOK; bool was_clean = true; if (ec == websocket::Error::bad_response_301_moved_permanently || ec == websocket::Error::bad_response_308_permanent_redirect) { - error = ErrorCodes::WebSocketMovedPermanently; + error = WebSocketMovedPermanently; } else if (ec == websocket::Error::bad_response_3xx_redirection) { - error = ErrorCodes::WebSocketRetryError; + error = WebSocketRetryError; was_clean = false; } else if (ec == websocket::Error::bad_response_401_unauthorized) { - error = ErrorCodes::WebSocketUnauthorized; + error = WebSocketUnauthorized; } else if (ec == websocket::Error::bad_response_403_forbidden) { - error = ErrorCodes::WebSocketForbidden; + error = WebSocketForbidden; } 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; + error = WebSocketInternalServerError; was_clean = false; } else { - error = ErrorCodes::WebSocketFatalError; + error = WebSocketFatalError; was_clean = false; if (body) { std::string_view identifier = "REALM_SYNC_PROTOCOL_MISMATCH"; @@ -125,14 +123,14 @@ class DefaultWebSocketImpl final : public WebSocketInterface, 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 = WebSocketClient_Too_Old; } else if (begins_with(rest, ":CLIENT_TOO_NEW")) { - error = ErrorCodes::WebSocketClient_Too_New; + error = WebSocketClient_Too_New; } else { // Other more complicated forms of mismatch - error = ErrorCodes::WebSocketProtocol_Mismatch; + error = WebSocketProtocol_Mismatch; } was_clean = true; } @@ -144,8 +142,7 @@ class DefaultWebSocketImpl final : public WebSocketInterface, public Config { void websocket_protocol_error_handler(std::error_code ec) override { constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, - Status{make_error_code(ErrorCodes::WebSocketProtocolError), ec.message()}); + websocket_error_and_close_handler(was_clean, Status{make_error_code(WebSocketProtocolError), ec.message()}); } bool websocket_close_message_received(std::error_code ec, StringData message) override { @@ -270,8 +267,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{make_error_code(ErrorCodes::WebSocketResolveFailed), ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, + Status{make_error_code(WebSocketResolveFailed), ec.message()}); // Throws return; } @@ -311,8 +308,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{make_error_code(ErrorCodes::WebSocketConnectionFailed), ec.message()}); // Throws + websocket_error_and_close_handler(was_clean, + Status{make_error_code(WebSocketConnectionFailed), ec.message()}); // Throws return; } @@ -353,7 +350,7 @@ void DefaultWebSocketImpl::initiate_http_tunnel() m_logger.error("Failed to establish HTTP tunnel: %1", ec.message()); constexpr bool was_clean = false; websocket_error_and_close_handler( - was_clean, Status{make_error_code(ErrorCodes::WebSocketConnectionFailed), ec.message()}); // Throws + was_clean, Status{make_error_code(WebSocketConnectionFailed), ec.message()}); // Throws return; } @@ -361,7 +358,7 @@ void DefaultWebSocketImpl::initiate_http_tunnel() 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{make_error_code(ErrorCodes::WebSocketConnectionFailed), response.reason}); // Throws + was_clean, Status{make_error_code(WebSocketConnectionFailed), response.reason}); // Throws return; } @@ -425,7 +422,7 @@ void DefaultWebSocketImpl::handle_ssl_handshake(std::error_code ec) REALM_ASSERT(ec != util::error::operation_aborted); constexpr bool was_clean = false; websocket_error_and_close_handler( - was_clean, Status{make_error_code(ErrorCodes::WebSocketTLSHandshakeFailed), ec.message()}); // Throws + was_clean, Status{make_error_code(WebSocketTLSHandshakeFailed), ec.message()}); // Throws return; } diff --git a/src/realm/sync/network/websocket.cpp b/src/realm/sync/network/websocket.cpp index 5f1aa320e03..a30547bfe39 100644 --- a/src/realm/sync/network/websocket.cpp +++ b/src/realm/sync/network/websocket.cpp @@ -1109,7 +1109,7 @@ class CloseStatusErrorCategory : public std::error_category { { // Converts an error_code to one of the pre-defined status codes in // https://tools.ietf.org/html/rfc6455#section-7.4.1 - return ErrorCodes::error_string(static_cast(error_code)); + return error_string(static_cast(error_code)); } }; @@ -1235,7 +1235,7 @@ const std::error_category& websocket::websocket_close_status_category() noexcept return category; } -std::error_code websocket::make_error_code(ErrorCodes::WebSocketError error) noexcept +std::error_code websocket::make_error_code(WebSocketError error) noexcept { return std::error_code{error, realm::sync::websocket::websocket_close_status_category()}; } @@ -1249,3 +1249,64 @@ std::error_code websocket::make_error_code(Error error_code) noexcept { return std::error_code{int(error_code), g_error_category}; } + +std::string websocket::error_string(WebSocketError code) noexcept +{ + /// WebSocket error codes + switch (code) { + case WebSocketOK: + return "WebSocket: OK"; + case WebSocketGoingAway: + return "WebSocket: Going Away"; + case WebSocketProtocolError: + return "WebSocket: Protocol Error"; + case WebSocketUnsupportedData: + return "WebSocket: Unsupported Data"; + case WebSocketReserved: + return "WebSocket: Reserved"; + case WebSocketNoStatusReceived: + return "WebSocket: No Status Received"; + case WebSocketAbnormalClosure: + return "WebSocket: Abnormal Closure"; + case WebSocketInvalidPayloadData: + return "WebSocket: Invalid Payload Data"; + case WebSocketPolicyViolation: + return "WebSocket: Policy Violation"; + case WebSocketMessageTooBig: + return "WebSocket: Message Too Big"; + case WebSocketInavalidExtension: + return "WebSocket: Invalid Extension"; + case WebSocketInternalServerError: + return "WebSocket: Internal Server Error"; + case WebSocketTLSHandshakeFailed: + return "WebSocket: TLS Handshake Failed"; + + /// WebSocket Errors - reported by server + case WebSocketUnauthorized: + return "WebSocket: Unauthorized"; + case WebSocketForbidden: + return "WebSocket: Forbidden"; + case WebSocketMovedPermanently: + return "WebSocket: Moved Permanently"; + case WebSocketClient_Too_Old: + return "WebSocket: Client Too Old"; + case WebSocketClient_Too_New: + return "WebSocket: Client Too New"; + case WebSocketProtocol_Mismatch: + return "WebSocket: Protocol Mismatch"; + + case WebSocketResolveFailed: + return "WebSocket: Resolve Failed"; + case WebSocketConnectionFailed: + return "WebSocket: Connection Failed"; + case WebSocketReadError: + return "WebSocket: Read Error"; + case WebSocketWriteError: + return "WebSocket: Write Error"; + case WebSocketRetryError: + return "WebSocket: Retry Error"; + case WebSocketFatalError: + return "WebSocket: Fatal Error"; + } + return ""; +} diff --git a/src/realm/sync/network/websocket.hpp b/src/realm/sync/network/websocket.hpp index bff92ef1df2..ec8c4dc4f72 100644 --- a/src/realm/sync/network/websocket.hpp +++ b/src/realm/sync/network/websocket.hpp @@ -213,9 +213,41 @@ enum class Error { bad_message }; +enum WebSocketError : int32_t { + WebSocketOK = RLM_ERR_WEBSOCKET_OK, + 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 + + // WebSocket Errors - reported by server + WebSocketUnauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, + WebSocketForbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, + WebSocketMovedPermanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, + WebSocketClient_Too_Old = RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD, + WebSocketClient_Too_New = RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW, + WebSocketProtocol_Mismatch = RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH, + + WebSocketResolveFailed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, + WebSocketConnectionFailed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, + WebSocketReadError = RLM_ERR_WEBSOCKET_READ_ERROR, + WebSocketWriteError = RLM_ERR_WEBSOCKET_WRITE_ERROR, + WebSocketRetryError = RLM_ERR_WEBSOCKET_RETRY_ERROR, + WebSocketFatalError = RLM_ERR_WEBSOCKET_FATAL_ERROR, +}; + const std::error_category& websocket_close_status_category() noexcept; -std::error_code make_error_code(ErrorCodes::WebSocketError error) noexcept; +std::error_code make_error_code(WebSocketError error) noexcept; +std::string error_string(WebSocketError code) noexcept; const std::error_category& error_category() noexcept; diff --git a/src/realm/sync/noinst/client_impl_base.cpp b/src/realm/sync/noinst/client_impl_base.cpp index 436eb581a84..9fb2acf20cd 100644 --- a/src/realm/sync/noinst/client_impl_base.cpp +++ b/src/realm/sync/noinst/client_impl_base.cpp @@ -415,25 +415,25 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) auto status_code = error_code.value(); // TODO: Use a switch statement once websocket errors have their own category in exception unification. - if (status_code == ErrorCodes::WebSocketResolveFailed || status_code == ErrorCodes::WebSocketConnectionFailed) { + if (status_code == websocket::WebSocketResolveFailed || status_code == websocket::WebSocketConnectionFailed) { 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::WebSocketReadError || status_code == ErrorCodes::WebSocketWriteError) { + else if (status_code == websocket::WebSocketReadError || status_code == websocket::WebSocketWriteError) { read_or_write_error(error_code); } - 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) { + else if (status_code == websocket::WebSocketGoingAway || status_code == websocket::WebSocketProtocolError || + status_code == websocket::WebSocketUnsupportedData || status_code == websocket::WebSocketReserved || + status_code == websocket::WebSocketInvalidPayloadData || + status_code == websocket::WebSocketPolicyViolation || + status_code == websocket::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) { + else if (status_code == websocket::WebSocketMessageTooBig) { m_reconnect_info.m_reason = ConnectionTerminationReason::websocket_protocol_violation; constexpr bool try_again = true; auto ec = make_error_code(ProtocolError::limits_exceeded); @@ -443,39 +443,38 @@ bool Connection::websocket_closed_handler(bool was_clean, Status status) error_info.server_requests_action = ProtocolErrorInfo::Action::ClientReset; involuntary_disconnect(std::move(error_info)); } - else if (status_code == ErrorCodes::WebSocketTLSHandshakeFailed) { + else if (status_code == websocket::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, std::nullopt, is_fatal); // Throws } - else if (status_code == ErrorCodes::WebSocketClient_Too_Old) { + else if (status_code == websocket::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, std::nullopt, is_fatal); // Throws } - else if (status_code == ErrorCodes::WebSocketClient_Too_New) { + else if (status_code == websocket::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, std::nullopt, is_fatal); // Throws } - else if (status_code == ErrorCodes::WebSocketProtocol_Mismatch) { + else if (status_code == websocket::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, std::nullopt, is_fatal); // Throws } - else if (status_code == ErrorCodes::WebSocketRetryError || status_code == ErrorCodes::WebSocketForbidden) { + else if (status_code == websocket::WebSocketRetryError || status_code == websocket::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, std::nullopt, 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) { + else if (status_code == websocket::WebSocketUnauthorized || status_code == websocket::WebSocketMovedPermanently || + status_code == websocket::WebSocketInternalServerError || + status_code == websocket::WebSocketAbnormalClosure || status_code == websocket::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, std::nullopt, is_fatal); // Throws diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 81fe375876d..05c4e173a38 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -4458,7 +4458,7 @@ TEST(Sync_ServerDiscardDeadConnections) BowlOfStonesSemaphore bowl; auto error_handler = [&](std::error_code ec, bool, const std::string&) { - bool valid_error = ec.value() == ErrorCodes::WebSocketReadError; + bool valid_error = ec.value() == sync::websocket::WebSocketReadError; CHECK(valid_error); bowl.add_stone(); }; From 2486b5191136179bd3595aaf39e565aea44d52b4 Mon Sep 17 00:00:00 2001 From: Daniel Tabacaru Date: Wed, 22 Feb 2023 12:41:52 +0100 Subject: [PATCH 07/11] WebSocket errors refactoring --- .../sync/generic_network_transport.hpp | 5 + src/realm/object-store/sync/sync_session.cpp | 17 +- src/realm/sync/network/default_socket.cpp | 66 +++-- src/realm/sync/network/websocket.cpp | 271 ++++++++++-------- src/realm/sync/network/websocket.hpp | 76 ++--- src/realm/sync/noinst/client_impl_base.cpp | 173 ++++++----- src/realm/sync/noinst/server/server.cpp | 8 +- test/object-store/sync/client_reset.cpp | 2 +- test/test_sync.cpp | 2 +- 9 files changed, 350 insertions(+), 270 deletions(-) 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 137fff90025..bdd766fe199 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -648,20 +648,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() && - (error_code.value() == sync::websocket::WebSocketUnauthorized || - error_code.value() == sync::websocket::WebSocketAbnormalClosure || - error_code.value() == sync::websocket::WebSocketMovedPermanently)) { + if (error_code == sync::websocket::WebSocketError::websocket_unauthorized || + error_code == sync::websocket::WebSocketError::websocket_abnormal_closure || + error_code == sync::websocket::WebSocketError::websocket_moved_permanently) { if (auto u = user()) { u->refresh_custom_data(handle_refresh(shared_from_this())); 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 325cb4b6f31..74b445c5245 100644 --- a/src/realm/sync/network/default_socket.cpp +++ b/src/realm/sync/network/default_socket.cpp @@ -73,44 +73,46 @@ class DefaultWebSocketImpl final : public WebSocketInterface, public Config { { m_logger.error("Reading failed: %1", ec.message()); // Throws constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, Status{make_error_code(WebSocketReadError), 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{make_error_code(WebSocketWriteError), 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 { - WebSocketError error = WebSocketOK; + 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 = 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 = 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 = WebSocketUnauthorized; + else if (ec == websocket::HttpError::bad_response_401_unauthorized) { + error = WebSocketError::websocket_unauthorized; } - else if (ec == websocket::Error::bad_response_403_forbidden) { - error = 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 = 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 = WebSocketFatalError; + error = WebSocketError::websocket_fatal_error; was_clean = false; if (body) { std::string_view identifier = "REALM_SYNC_PROTOCOL_MISMATCH"; @@ -123,14 +125,14 @@ class DefaultWebSocketImpl final : public WebSocketInterface, public Config { std::equal(string.data(), string.data() + prefix.size(), prefix.data())); }; if (begins_with(rest, ":CLIENT_TOO_OLD")) { - error = WebSocketClient_Too_Old; + error = WebSocketError::websocket_client_too_old; } else if (begins_with(rest, ":CLIENT_TOO_NEW")) { - error = WebSocketClient_Too_New; + error = WebSocketError::websocket_client_too_new; } else { // Other more complicated forms of mismatch - error = WebSocketProtocol_Mismatch; + error = WebSocketError::websocket_protocol_mismatch; } was_clean = true; } @@ -142,7 +144,8 @@ class DefaultWebSocketImpl final : public WebSocketInterface, public Config { void websocket_protocol_error_handler(std::error_code ec) override { constexpr bool was_clean = false; - websocket_error_and_close_handler(was_clean, Status{make_error_code(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 { @@ -267,8 +270,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{make_error_code(WebSocketResolveFailed), ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, Status{make_error_code(WebSocketError::websocket_resolve_failed), ec.message()}); // Throws return; } @@ -308,8 +311,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{make_error_code(WebSocketConnectionFailed), ec.message()}); // Throws + websocket_error_and_close_handler( + was_clean, Status{make_error_code(WebSocketError::websocket_connection_failed), ec.message()}); // Throws return; } @@ -350,7 +353,8 @@ void DefaultWebSocketImpl::initiate_http_tunnel() m_logger.error("Failed to establish HTTP tunnel: %1", ec.message()); constexpr bool was_clean = false; websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketConnectionFailed), ec.message()}); // Throws + was_clean, + Status{make_error_code(WebSocketError::websocket_connection_failed), ec.message()}); // Throws return; } @@ -358,7 +362,8 @@ void DefaultWebSocketImpl::initiate_http_tunnel() 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{make_error_code(WebSocketConnectionFailed), response.reason}); // Throws + was_clean, + Status{make_error_code(WebSocketError::websocket_connection_failed), response.reason}); // Throws return; } @@ -422,7 +427,8 @@ void DefaultWebSocketImpl::handle_ssl_handshake(std::error_code ec) REALM_ASSERT(ec != util::error::operation_aborted); constexpr bool was_clean = false; websocket_error_and_close_handler( - was_clean, Status{make_error_code(WebSocketTLSHandshakeFailed), ec.message()}); // Throws + 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 a30547bfe39..4cb3ad00145 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; } @@ -749,7 +750,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 } @@ -765,37 +766,37 @@ class WebSocket { std::error_code ec; 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; @@ -813,7 +814,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) { @@ -827,7 +828,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 } @@ -947,7 +948,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); } @@ -959,7 +960,7 @@ class WebSocket { m_frame_reader.next(); if (m_frame_reader.protocol_error) { - protocol_error(Error::bad_message); + protocol_error(HttpError::bad_message); return; } @@ -1026,71 +1027,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) @@ -1098,18 +1099,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"; -class CloseStatusErrorCategory : public std::error_category { + /// 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 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 - return error_string(static_cast(error_code)); + auto msg = error_string(static_cast(error_code)); + if (msg.empty()) + msg = "Unknown error"; + return msg; } }; @@ -1229,84 +1292,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(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()}; } -std::string websocket::error_string(WebSocketError code) noexcept +ErrorCodes::Error websocket::get_simplified_websocket_error(WebSocketError error) { - /// WebSocket error codes - switch (code) { - case WebSocketOK: - return "WebSocket: OK"; - case WebSocketGoingAway: - return "WebSocket: Going Away"; - case WebSocketProtocolError: - return "WebSocket: Protocol Error"; - case WebSocketUnsupportedData: - return "WebSocket: Unsupported Data"; - case WebSocketReserved: - return "WebSocket: Reserved"; - case WebSocketNoStatusReceived: - return "WebSocket: No Status Received"; - case WebSocketAbnormalClosure: - return "WebSocket: Abnormal Closure"; - case WebSocketInvalidPayloadData: - return "WebSocket: Invalid Payload Data"; - case WebSocketPolicyViolation: - return "WebSocket: Policy Violation"; - case WebSocketMessageTooBig: - return "WebSocket: Message Too Big"; - case WebSocketInavalidExtension: - return "WebSocket: Invalid Extension"; - case WebSocketInternalServerError: - return "WebSocket: Internal Server Error"; - case WebSocketTLSHandshakeFailed: - return "WebSocket: TLS Handshake Failed"; - - /// WebSocket Errors - reported by server - case WebSocketUnauthorized: - return "WebSocket: Unauthorized"; - case WebSocketForbidden: - return "WebSocket: Forbidden"; - case WebSocketMovedPermanently: - return "WebSocket: Moved Permanently"; - case WebSocketClient_Too_Old: - return "WebSocket: Client Too Old"; - case WebSocketClient_Too_New: - return "WebSocket: Client Too New"; - case WebSocketProtocol_Mismatch: - return "WebSocket: Protocol Mismatch"; - - case WebSocketResolveFailed: - return "WebSocket: Resolve Failed"; - case WebSocketConnectionFailed: - return "WebSocket: Connection Failed"; - case WebSocketReadError: - return "WebSocket: Read Error"; - case WebSocketWriteError: - return "WebSocket: Write Error"; - case WebSocketRetryError: - return "WebSocket: Retry Error"; - case WebSocketFatalError: - return "WebSocket: Fatal Error"; + if (error == sync::websocket::WebSocketError::websocket_resolve_failed) { + return ErrorCodes::WebSocketResolveFailedError; } - return ""; -} + 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 ec8c4dc4f72..1bed15b695c 100644 --- a/src/realm/sync/network/websocket.hpp +++ b/src/realm/sync/network/websocket.hpp @@ -186,7 +186,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, @@ -213,52 +213,58 @@ enum class Error { bad_message }; -enum WebSocketError : int32_t { - WebSocketOK = RLM_ERR_WEBSOCKET_OK, - 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 +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 - WebSocketUnauthorized = RLM_ERR_WEBSOCKET_UNAUTHORIZED, - WebSocketForbidden = RLM_ERR_WEBSOCKET_FORBIDDEN, - WebSocketMovedPermanently = RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, - WebSocketClient_Too_Old = RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD, - WebSocketClient_Too_New = RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW, - WebSocketProtocol_Mismatch = RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH, - - WebSocketResolveFailed = RLM_ERR_WEBSOCKET_RESOLVE_FAILED, - WebSocketConnectionFailed = RLM_ERR_WEBSOCKET_CONNECTION_FAILED, - WebSocketReadError = RLM_ERR_WEBSOCKET_READ_ERROR, - WebSocketWriteError = RLM_ERR_WEBSOCKET_WRITE_ERROR, - WebSocketRetryError = RLM_ERR_WEBSOCKET_RETRY_ERROR, - WebSocketFatalError = RLM_ERR_WEBSOCKET_FATAL_ERROR, + 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, }; -const std::error_category& websocket_close_status_category() noexcept; +const std::error_category& websocket_error_category() noexcept; -std::error_code make_error_code(WebSocketError error) noexcept; -std::string error_string(WebSocketError code) noexcept; +std::error_code make_error_code(WebSocketError) noexcept; -const std::error_category& error_category() 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 9fb2acf20cd..6c1e7732bf1 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; @@ -406,78 +407,108 @@ void Connection::websocket_error_handler() bool Connection::websocket_closed_handler(bool was_clean, Status status) { 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 = error_code.value(); - - // TODO: Use a switch statement once websocket errors have their own category in exception unification. - if (status_code == websocket::WebSocketResolveFailed || status_code == websocket::WebSocketConnectionFailed) { - 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 == websocket::WebSocketReadError || status_code == websocket::WebSocketWriteError) { - read_or_write_error(error_code); - } - else if (status_code == websocket::WebSocketGoingAway || status_code == websocket::WebSocketProtocolError || - status_code == websocket::WebSocketUnsupportedData || status_code == websocket::WebSocketReserved || - status_code == websocket::WebSocketInvalidPayloadData || - status_code == websocket::WebSocketPolicyViolation || - status_code == websocket::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 == websocket::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 == websocket::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, std::nullopt, is_fatal); // Throws - } - else if (status_code == websocket::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, std::nullopt, is_fatal); // Throws - } - else if (status_code == websocket::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, std::nullopt, is_fatal); // Throws - } - else if (status_code == websocket::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, std::nullopt, is_fatal); // Throws - } - else if (status_code == websocket::WebSocketRetryError || status_code == websocket::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, std::nullopt, is_fatal); // Throws - } - else if (status_code == websocket::WebSocketUnauthorized || status_code == websocket::WebSocketMovedPermanently || - status_code == websocket::WebSocketInternalServerError || - status_code == websocket::WebSocketAbnormalClosure || status_code == websocket::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, std::nullopt, 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); // 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, std::nullopt, 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, std::nullopt, 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, std::nullopt, 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, std::nullopt, 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, std::nullopt, 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, std::nullopt, 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/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index bb3eb4dff1f..aea074741ee 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.get_system_error() == sync::websocket::make_error_code(ErrorCodes::WebSocketReadError)) { + 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 05c4e173a38..af86fedf817 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -4458,7 +4458,7 @@ TEST(Sync_ServerDiscardDeadConnections) BowlOfStonesSemaphore bowl; auto error_handler = [&](std::error_code ec, bool, const std::string&) { - bool valid_error = ec.value() == sync::websocket::WebSocketReadError; + bool valid_error = ec == sync::websocket::WebSocketError::websocket_read_error; CHECK(valid_error); bowl.add_stone(); }; From f86236d3755709de404c8376bbb2aa5434d31118 Mon Sep 17 00:00:00 2001 From: Daniel Tabacaru Date: Wed, 22 Feb 2023 12:44:29 +0100 Subject: [PATCH 08/11] Introduce coarse-grained websocket errors to ErrorCodes::Error --- src/realm/error_codes.cpp | 8 ++++++++ src/realm/error_codes.h | 4 ++++ src/realm/error_codes.hpp | 3 +++ 3 files changed, 15 insertions(+) diff --git a/src/realm/error_codes.cpp b/src/realm/error_codes.cpp index d2960dbebb7..bc9004f1db4 100644 --- a/src/realm/error_codes.cpp +++ b/src/realm/error_codes.cpp @@ -207,6 +207,11 @@ ErrorCategory ErrorCodes::error_categories(Error code) .set(ErrorCategory::app_error) .set(ErrorCategory::service_error); + case WebSocketResolveFailedError: + case WebSocketConnectionClosedClientError: + case WebSocketConnectionClosedServerError: + return ErrorCategory().set(ErrorCategory::runtime_error).set(ErrorCategory::websocket_error); + case UnknownError: break; } @@ -360,6 +365,9 @@ static const MapElem string_to_error_code[] = { {"ValueAlreadyExists", ErrorCodes::ValueAlreadyExists}, {"ValueDuplicateName", ErrorCodes::ValueDuplicateName}, {"ValueNotFound", ErrorCodes::ValueNotFound}, + {"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 9aa8a1622ac..be1a71604d9 100644 --- a/src/realm/error_codes.h +++ b/src/realm/error_codes.h @@ -183,6 +183,10 @@ typedef enum realm_errno { RLM_ERR_MAINTENANCE_IN_PROGRESS = 4352, RLM_ERR_USERPASS_TOKEN_INVALID = 4353, + 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 */ } realm_errno_e; diff --git a/src/realm/error_codes.hpp b/src/realm/error_codes.hpp index 497f91c94ec..85dce41a563 100644 --- a/src/realm/error_codes.hpp +++ b/src/realm/error_codes.hpp @@ -225,6 +225,9 @@ class ErrorCodes { AppUnknownError = RLM_ERR_APP_UNKNOWN, MaintenanceInProgress = RLM_ERR_MAINTENANCE_IN_PROGRESS, UserpassTokenInvalid = RLM_ERR_USERPASS_TOKEN_INVALID, + 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, From 0f7e1351c25d8295a8cdd60a777dc6d465a8c103 Mon Sep 17 00:00:00 2001 From: Daniel Tabacaru Date: Wed, 22 Feb 2023 12:45:46 +0100 Subject: [PATCH 09/11] Replace RLM_SYNC_ERROR_CATEGORY_RESOLVE with RLM_SYNC_ERROR_CATEGORY_WEBSOCKET for sync errors in C API --- src/realm.h | 11 +---------- src/realm/object-store/c_api/sync.cpp | 21 +++++---------------- test/object-store/c_api/c_api.cpp | 11 ++++++----- 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/src/realm.h b/src/realm.h index 51bf06b2579..e6f1ff2b5e4 100644 --- a/src/realm.h +++ b/src/realm.h @@ -3289,7 +3289,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. @@ -3313,15 +3313,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; 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/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index e0a4fa1d846..77fba462e36 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -26,7 +26,7 @@ #if REALM_ENABLE_AUTH_TESTS #include #include -#include +#include #include #include "sync/sync_test_utils.hpp" #include "util/baas_admin_api.hpp" @@ -695,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); From 9f029d2267fc0f07cbb24c74152edda39b650ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Thu, 23 Feb 2023 15:20:24 +0100 Subject: [PATCH 10/11] Fix object-store tests --- test/object-store/realm.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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); } } From 5767d6d80bd179406e08c16fd6c801428ba88ffe Mon Sep 17 00:00:00 2001 From: Daniel Tabacaru Date: Fri, 24 Feb 2023 00:06:44 +0100 Subject: [PATCH 11/11] Fix wrong merge in sync_session.cpp --- src/realm/object-store/sync/sync_session.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index dca32ddd41b..1f611e00640 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -686,11 +686,11 @@ void SyncSession::handle_error(SyncError error) // 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 == sync::websocket::WebSocketError::websocket_unauthorized || - error_code == sync::websocket::WebSocketError::websocket_abnormal_closure || - error_code == sync::websocket::WebSocketError::websocket_moved_permanently) { + 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())); + u->refresh_custom_data(handle_refresh(shared_from_this(), restart_session)); return; } }