protocol.hpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. #ifndef REALM_SYNC_PROTOCOL_HPP
  2. #define REALM_SYNC_PROTOCOL_HPP
  3. #include <cstdint>
  4. #include <system_error>
  5. #include <realm/error_codes.h>
  6. #include <realm/mixed.hpp>
  7. #include <realm/replication.hpp>
  8. // NOTE: The protocol specification is in `/doc/protocol.md`
  9. namespace realm {
  10. namespace sync {
  11. // Protocol versions:
  12. //
  13. // 1 Initial version, matching io.realm.sync-30, but not including query-based
  14. // sync, serialized transactions, and state realms (async open).
  15. //
  16. // 2 Restored erase-always-wins OT behavior.
  17. //
  18. // 3 Support for Mixed, TypeLinks, Set, and Dictionary columns.
  19. //
  20. // 4 Error messaging format accepts a flexible JSON field in 'json_error'.
  21. // JSONErrorMessage.IsClientReset controls recovery mode.
  22. //
  23. // 5 Introduces compensating write errors.
  24. //
  25. // 6 Support for asymmetric tables.
  26. //
  27. // 7 Client takes the 'action' specified in the 'json_error' messages received
  28. // from server. Client sends 'json_error' messages to the server.
  29. //
  30. // XX Changes:
  31. // - TBD
  32. //
  33. constexpr int get_current_protocol_version() noexcept
  34. {
  35. #ifdef REALM_SYNC_PROTOCOL_V8
  36. return 8;
  37. #else
  38. return 7;
  39. #endif // REALM_SYNC_PROTOCOL_V8
  40. }
  41. constexpr std::string_view get_pbs_websocket_protocol_prefix() noexcept
  42. {
  43. #ifdef REALM_SYNC_PROTOCOL_V8
  44. return "com.mongodb.realm-sync#";
  45. #else
  46. return "com.mongodb.realm-sync/";
  47. #endif // REALM_SYNC_PROTOCOL_V8
  48. }
  49. constexpr std::string_view get_flx_websocket_protocol_prefix() noexcept
  50. {
  51. #ifdef REALM_SYNC_PROTOCOL_V8
  52. return "com.mongodb.realm-query-sync#";
  53. #else
  54. return "com.mongodb.realm-query-sync/";
  55. #endif // REALM_SYNC_PROTOCOL_V8
  56. }
  57. enum class SyncServerMode { PBS, FLX };
  58. /// Supported protocol envelopes:
  59. ///
  60. /// Alternative (*)
  61. /// Name Envelope URL scheme Default port default port
  62. /// ------------------------------------------------------------------------
  63. /// realm WebSocket realm: 7800 80
  64. /// realms WebSocket + SSL realms: 7801 443
  65. /// ws WebSocket ws: 80
  66. /// wss WebSocket + SSL wss: 443
  67. ///
  68. /// *) When Client::Config::enable_default_port_hack is true
  69. ///
  70. enum class ProtocolEnvelope { realm, realms, ws, wss };
  71. inline bool is_ssl(ProtocolEnvelope protocol) noexcept
  72. {
  73. switch (protocol) {
  74. case ProtocolEnvelope::realm:
  75. case ProtocolEnvelope::ws:
  76. break;
  77. case ProtocolEnvelope::realms:
  78. case ProtocolEnvelope::wss:
  79. return true;
  80. }
  81. return false;
  82. }
  83. // These integer types are selected so that they accomodate the requirements of
  84. // the protocol specification (`/doc/protocol.md`).
  85. //
  86. // clang-format off
  87. using file_ident_type = std::uint_fast64_t;
  88. using version_type = Replication::version_type;
  89. using salt_type = std::int_fast64_t;
  90. using timestamp_type = std::uint_fast64_t;
  91. using session_ident_type = std::uint_fast64_t;
  92. using request_ident_type = std::uint_fast64_t;
  93. using milliseconds_type = std::int_fast64_t;
  94. // clang-format on
  95. constexpr file_ident_type get_max_file_ident()
  96. {
  97. return 0x0'7FFF'FFFF'FFFF'FFFF;
  98. }
  99. struct SaltedFileIdent {
  100. file_ident_type ident;
  101. /// History divergence and identity spoofing protection.
  102. salt_type salt;
  103. };
  104. struct SaltedVersion {
  105. version_type version;
  106. /// History divergence protection.
  107. salt_type salt;
  108. };
  109. /// \brief A client's reference to a position in the server-side history.
  110. ///
  111. /// A download cursor refers to a position in the server-side history. If
  112. /// `server_version` is zero, the position is at the beginning of the history,
  113. /// otherwise the position is after the entry whose changeset produced that
  114. /// version. In general, positions are to be understood as places between two
  115. /// adjacent history entries.
  116. ///
  117. /// `last_integrated_client_version` is the version produced on the client by
  118. /// the last changeset that was sent to the server and integrated into the
  119. /// server-side Realm state at the time indicated by the history position
  120. /// specified by `server_version`, or zero if no changesets from the client were
  121. /// integrated by the server at that point in time.
  122. struct DownloadCursor {
  123. version_type server_version;
  124. version_type last_integrated_client_version;
  125. };
  126. enum class DownloadBatchState {
  127. MoreToCome,
  128. LastInBatch,
  129. SteadyState,
  130. };
  131. /// Checks that `dc.last_integrated_client_version` is zero if
  132. /// `dc.server_version` is zero.
  133. bool is_consistent(DownloadCursor dc) noexcept;
  134. /// Checks that `a.last_integrated_client_version` and
  135. /// `b.last_integrated_client_version` are equal, if `a.server_version` and
  136. /// `b.server_version` are equal. Otherwise checks that
  137. /// `a.last_integrated_client_version` is less than, or equal to
  138. /// `b.last_integrated_client_version`, if `a.server_version` is less than
  139. /// `b.server_version`. Otherwise checks that `a.last_integrated_client_version`
  140. /// is greater than, or equal to `b.last_integrated_client_version`.
  141. bool are_mutually_consistent(DownloadCursor a, DownloadCursor b) noexcept;
  142. /// \brief The server's reference to a position in the client-side history.
  143. ///
  144. /// An upload cursor refers to a position in the client-side history. If
  145. /// `client_version` is zero, the position is at the beginning of the history,
  146. /// otherwise the position is after the entry whose changeset produced that
  147. /// version. In general, positions are to be understood as places between two
  148. /// adjacent history entries.
  149. ///
  150. /// `last_integrated_server_version` is the version produced on the server by
  151. /// the last changeset that was sent to the client and integrated into the
  152. /// client-side Realm state at the time indicated by the history position
  153. /// specified by `client_version`, or zero if no changesets from the server were
  154. /// integrated by the client at that point in time.
  155. struct UploadCursor {
  156. version_type client_version;
  157. version_type last_integrated_server_version;
  158. };
  159. /// Checks that `uc.last_integrated_server_version` is zero if
  160. /// `uc.client_version` is zero.
  161. bool is_consistent(UploadCursor uc) noexcept;
  162. /// Checks that `a.last_integrated_server_version` and
  163. /// `b.last_integrated_server_version` are equal, if `a.client_version` and
  164. /// `b.client_version` are equal. Otherwise checks that
  165. /// `a.last_integrated_server_version` is less than, or equal to
  166. /// `b.last_integrated_server_version`, if `a.client_version` is less than
  167. /// `b.client_version`. Otherwise checks that `a.last_integrated_server_version`
  168. /// is greater than, or equal to `b.last_integrated_server_version`.
  169. bool are_mutually_consistent(UploadCursor a, UploadCursor b) noexcept;
  170. /// A client's record of the current point of progress of the synchronization
  171. /// process. The client must store this persistently in the local Realm file.
  172. struct SyncProgress {
  173. /// The last server version that the client has heard about.
  174. SaltedVersion latest_server_version = {0, 0};
  175. /// The last server version integrated, or about to be integrated by the
  176. /// client.
  177. DownloadCursor download = {0, 0};
  178. /// The last client version integrated by the server.
  179. UploadCursor upload = {0, 0};
  180. };
  181. struct CompensatingWriteErrorInfo {
  182. std::string object_name;
  183. OwnedMixed primary_key;
  184. std::string reason;
  185. };
  186. struct ResumptionDelayInfo {
  187. std::chrono::milliseconds max_resumption_delay_interval = std::chrono::minutes{5};
  188. std::chrono::milliseconds resumption_delay_interval = std::chrono::seconds{1};
  189. int resumption_delay_backoff_multiplier = 2;
  190. };
  191. struct ProtocolErrorInfo {
  192. enum class Action {
  193. NoAction,
  194. ProtocolViolation,
  195. ApplicationBug,
  196. Warning,
  197. Transient,
  198. DeleteRealm,
  199. ClientReset,
  200. ClientResetNoRecovery,
  201. MigrateToFLX,
  202. RevertToPBS
  203. };
  204. ProtocolErrorInfo() = default;
  205. ProtocolErrorInfo(int error_code, const std::string& msg, bool do_try_again)
  206. : raw_error_code(error_code)
  207. , message(msg)
  208. , try_again(do_try_again)
  209. , client_reset_recovery_is_disabled(false)
  210. , should_client_reset(util::none)
  211. , server_requests_action(Action::NoAction)
  212. {
  213. }
  214. int raw_error_code = 0;
  215. std::string message;
  216. bool try_again = false;
  217. bool client_reset_recovery_is_disabled = false;
  218. std::optional<bool> should_client_reset;
  219. std::optional<std::string> log_url;
  220. version_type compensating_write_server_version = 0;
  221. version_type compensating_write_rejected_client_version = 0;
  222. std::vector<CompensatingWriteErrorInfo> compensating_writes;
  223. std::optional<ResumptionDelayInfo> resumption_delay_interval;
  224. Action server_requests_action;
  225. std::optional<std::string> migration_query_string;
  226. bool is_fatal() const
  227. {
  228. return !try_again;
  229. }
  230. };
  231. /// \brief Protocol errors discovered by the server, and reported to the client
  232. /// by way of ERROR messages.
  233. ///
  234. /// These errors will be reported to the client-side application via the error
  235. /// handlers of the affected sessions.
  236. ///
  237. /// ATTENTION: Please remember to update is_session_level_error() when
  238. /// adding/removing error codes.
  239. enum class ProtocolError {
  240. // clang-format off
  241. // Connection level and protocol errors
  242. connection_closed = RLM_SYNC_ERR_CONNECTION_CONNECTION_CLOSED, // Connection closed (no error)
  243. other_error = RLM_SYNC_ERR_CONNECTION_OTHER_ERROR, // Other connection level error
  244. unknown_message = RLM_SYNC_ERR_CONNECTION_UNKNOWN_MESSAGE, // Unknown type of input message
  245. bad_syntax = RLM_SYNC_ERR_CONNECTION_BAD_SYNTAX, // Bad syntax in input message head
  246. limits_exceeded = RLM_SYNC_ERR_CONNECTION_LIMITS_EXCEEDED, // Limits exceeded in input message
  247. wrong_protocol_version = RLM_SYNC_ERR_CONNECTION_WRONG_PROTOCOL_VERSION, // Wrong protocol version (CLIENT) (obsolete)
  248. bad_session_ident = RLM_SYNC_ERR_CONNECTION_BAD_SESSION_IDENT, // Bad session identifier in input message
  249. reuse_of_session_ident = RLM_SYNC_ERR_CONNECTION_REUSE_OF_SESSION_IDENT, // Overlapping reuse of session identifier (BIND)
  250. bound_in_other_session = RLM_SYNC_ERR_CONNECTION_BOUND_IN_OTHER_SESSION, // Client file bound in other session (IDENT)
  251. bad_message_order = RLM_SYNC_ERR_CONNECTION_BAD_MESSAGE_ORDER, // Bad input message order
  252. bad_decompression = RLM_SYNC_ERR_CONNECTION_BAD_DECOMPRESSION, // Error in decompression (UPLOAD)
  253. bad_changeset_header_syntax = RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_HEADER_SYNTAX, // Bad syntax in a changeset header (UPLOAD)
  254. bad_changeset_size = RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_SIZE, // Bad size specified in changeset header (UPLOAD)
  255. switch_to_flx_sync = RLM_SYNC_ERR_CONNECTION_SWITCH_TO_FLX_SYNC, // Connected with wrong wire protocol - should switch to FLX sync
  256. switch_to_pbs = RLM_SYNC_ERR_CONNECTION_SWITCH_TO_PBS, // Connected with wrong wire protocol - should switch to PBS
  257. // Session level errors
  258. session_closed = RLM_SYNC_ERR_SESSION_SESSION_CLOSED, // Session closed (no error)
  259. other_session_error = RLM_SYNC_ERR_SESSION_OTHER_SESSION_ERROR, // Other session level error
  260. token_expired = RLM_SYNC_ERR_SESSION_TOKEN_EXPIRED, // Access token expired
  261. bad_authentication = RLM_SYNC_ERR_SESSION_BAD_AUTHENTICATION, // Bad user authentication (BIND)
  262. illegal_realm_path = RLM_SYNC_ERR_SESSION_ILLEGAL_REALM_PATH, // Illegal Realm path (BIND)
  263. no_such_realm = RLM_SYNC_ERR_SESSION_NO_SUCH_REALM, // No such Realm (BIND)
  264. permission_denied = RLM_SYNC_ERR_SESSION_PERMISSION_DENIED, // Permission denied (BIND)
  265. bad_server_file_ident = RLM_SYNC_ERR_SESSION_BAD_SERVER_FILE_IDENT, // Bad server file identifier (IDENT) (obsolete!)
  266. bad_client_file_ident = RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE_IDENT, // Bad client file identifier (IDENT)
  267. bad_server_version = RLM_SYNC_ERR_SESSION_BAD_SERVER_VERSION, // Bad server version (IDENT, UPLOAD, TRANSACT)
  268. bad_client_version = RLM_SYNC_ERR_SESSION_BAD_CLIENT_VERSION, // Bad client version (IDENT, UPLOAD)
  269. diverging_histories = RLM_SYNC_ERR_SESSION_DIVERGING_HISTORIES, // Diverging histories (IDENT)
  270. bad_changeset = RLM_SYNC_ERR_SESSION_BAD_CHANGESET, // Bad changeset (UPLOAD, ERROR)
  271. partial_sync_disabled = RLM_SYNC_ERR_SESSION_PARTIAL_SYNC_DISABLED, // Partial sync disabled (BIND)
  272. unsupported_session_feature = RLM_SYNC_ERR_SESSION_UNSUPPORTED_SESSION_FEATURE, // Unsupported session-level feature
  273. bad_origin_file_ident = RLM_SYNC_ERR_SESSION_BAD_ORIGIN_FILE_IDENT, // Bad origin file identifier (UPLOAD)
  274. bad_client_file = RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE, // Synchronization no longer possible for client-side file
  275. server_file_deleted = RLM_SYNC_ERR_SESSION_SERVER_FILE_DELETED, // Server file was deleted while session was bound to it
  276. client_file_blacklisted = RLM_SYNC_ERR_SESSION_CLIENT_FILE_BLACKLISTED, // Client file has been blacklisted (IDENT)
  277. user_blacklisted = RLM_SYNC_ERR_SESSION_USER_BLACKLISTED, // User has been blacklisted (BIND)
  278. transact_before_upload = RLM_SYNC_ERR_SESSION_TRANSACT_BEFORE_UPLOAD, // Serialized transaction before upload completion
  279. client_file_expired = RLM_SYNC_ERR_SESSION_CLIENT_FILE_EXPIRED, // Client file has expired
  280. user_mismatch = RLM_SYNC_ERR_SESSION_USER_MISMATCH, // User mismatch for client file identifier (IDENT)
  281. too_many_sessions = RLM_SYNC_ERR_SESSION_TOO_MANY_SESSIONS, // Too many sessions in connection (BIND)
  282. invalid_schema_change = RLM_SYNC_ERR_SESSION_INVALID_SCHEMA_CHANGE, // Invalid schema change (UPLOAD)
  283. bad_query = RLM_SYNC_ERR_SESSION_BAD_QUERY, // Client query is invalid/malformed (IDENT, QUERY)
  284. object_already_exists = RLM_SYNC_ERR_SESSION_OBJECT_ALREADY_EXISTS, // Client tried to create an object that already exists outside their view (UPLOAD)
  285. server_permissions_changed = RLM_SYNC_ERR_SESSION_SERVER_PERMISSIONS_CHANGED, // Server permissions for this file ident have changed since the last time it was used (IDENT)
  286. initial_sync_not_completed = RLM_SYNC_ERR_SESSION_INITIAL_SYNC_NOT_COMPLETED, // Client tried to open a session before initial sync is complete (BIND)
  287. write_not_allowed = RLM_SYNC_ERR_SESSION_WRITE_NOT_ALLOWED, // Client attempted a write that is disallowed by permissions, or modifies an
  288. // object outside the current query - requires client reset (UPLOAD)
  289. compensating_write = RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE, // Client attempted a write that is disallowed by permissions, or modifies an
  290. // object outside the current query, and the server undid the modification
  291. // (UPLOAD)
  292. migrate_to_flx = RLM_SYNC_ERR_SESSION_MIGRATE_TO_FLX, // Server migrated from PBS to FLX - migrate client to FLX (BIND)
  293. bad_progress = RLM_SYNC_ERR_SESSION_BAD_PROGRESS, // Bad progress information (ERROR)
  294. revert_to_pbs = RLM_SYNC_ERR_SESSION_REVERT_TO_PBS, // Server rolled back to PBS after FLX migration - revert FLX client migration (BIND)
  295. // clang-format on
  296. };
  297. constexpr bool is_session_level_error(ProtocolError);
  298. /// Returns null if the specified protocol error code is not defined by
  299. /// ProtocolError.
  300. const char* get_protocol_error_message(int error_code) noexcept;
  301. const std::error_category& protocol_error_category() noexcept;
  302. std::error_code make_error_code(ProtocolError) noexcept;
  303. } // namespace sync
  304. } // namespace realm
  305. namespace std {
  306. template <>
  307. struct is_error_code_enum<realm::sync::ProtocolError> {
  308. static const bool value = true;
  309. };
  310. } // namespace std
  311. namespace realm {
  312. namespace sync {
  313. // Implementation
  314. inline bool is_consistent(DownloadCursor dc) noexcept
  315. {
  316. return (dc.server_version != 0 || dc.last_integrated_client_version == 0);
  317. }
  318. inline bool are_mutually_consistent(DownloadCursor a, DownloadCursor b) noexcept
  319. {
  320. if (a.server_version < b.server_version)
  321. return (a.last_integrated_client_version <= b.last_integrated_client_version);
  322. if (a.server_version > b.server_version)
  323. return (a.last_integrated_client_version >= b.last_integrated_client_version);
  324. return (a.last_integrated_client_version == b.last_integrated_client_version);
  325. }
  326. inline bool is_consistent(UploadCursor uc) noexcept
  327. {
  328. return (uc.client_version != 0 || uc.last_integrated_server_version == 0);
  329. }
  330. inline bool are_mutually_consistent(UploadCursor a, UploadCursor b) noexcept
  331. {
  332. if (a.client_version < b.client_version)
  333. return (a.last_integrated_server_version <= b.last_integrated_server_version);
  334. if (a.client_version > b.client_version)
  335. return (a.last_integrated_server_version >= b.last_integrated_server_version);
  336. return (a.last_integrated_server_version == b.last_integrated_server_version);
  337. }
  338. constexpr bool is_session_level_error(ProtocolError error)
  339. {
  340. return int(error) >= 200 && int(error) <= 299;
  341. }
  342. inline std::ostream& operator<<(std::ostream& o, ProtocolErrorInfo::Action action)
  343. {
  344. switch (action) {
  345. case ProtocolErrorInfo::Action::NoAction:
  346. return o << "NoAction";
  347. case ProtocolErrorInfo::Action::ProtocolViolation:
  348. return o << "ProtocolViolation";
  349. case ProtocolErrorInfo::Action::ApplicationBug:
  350. return o << "ApplicationBug";
  351. case ProtocolErrorInfo::Action::Warning:
  352. return o << "Warning";
  353. case ProtocolErrorInfo::Action::Transient:
  354. return o << "Transient";
  355. case ProtocolErrorInfo::Action::DeleteRealm:
  356. return o << "DeleteRealm";
  357. case ProtocolErrorInfo::Action::ClientReset:
  358. return o << "ClientReset";
  359. case ProtocolErrorInfo::Action::ClientResetNoRecovery:
  360. return o << "ClientResetNoRecovery";
  361. case ProtocolErrorInfo::Action::MigrateToFLX:
  362. return o << "MigrateToFLX";
  363. case ProtocolErrorInfo::Action::RevertToPBS:
  364. return o << "RevertToPBS";
  365. }
  366. return o << "Invalid error action: " << int64_t(action);
  367. }
  368. } // namespace sync
  369. } // namespace realm
  370. #endif // REALM_SYNC_PROTOCOL_HPP