protocol.hpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. #ifndef REALM_SYNC_PROTOCOL_HPP
  2. #define REALM_SYNC_PROTOCOL_HPP
  3. #include <cstdint>
  4. #include <system_error>
  5. #include <realm/replication.hpp>
  6. // NOTE: The protocol specification is in `/doc/protocol.md`
  7. namespace realm {
  8. namespace sync {
  9. // Protocol versions:
  10. //
  11. // 1 Initial version, matching io.realm.sync-30, but not including query-based
  12. // sync, serialized transactions, and state realms (async open).
  13. //
  14. // 2 Restored erase-always-wins OT behavior.
  15. //
  16. // 3 Support for Mixed, TypeLinks, Set, and Dictionary columns.
  17. //
  18. // XX Changes:
  19. // - TBD
  20. //
  21. constexpr int get_current_protocol_version() noexcept
  22. {
  23. return 3;
  24. }
  25. constexpr const char* get_websocket_protocol_prefix() noexcept
  26. {
  27. return "com.mongodb.realm-sync/";
  28. }
  29. /// Supported protocol envelopes:
  30. ///
  31. /// Alternative (*)
  32. /// Name Envelope URL scheme Default port default port
  33. /// ------------------------------------------------------------------------
  34. /// realm WebSocket realm: 7800 80
  35. /// realms WebSocket + SSL realms: 7801 443
  36. /// ws WebSocket ws: 80
  37. /// wss WebSocket + SSL wss: 443
  38. ///
  39. /// *) When Client::Config::enable_default_port_hack is true
  40. ///
  41. enum class ProtocolEnvelope { realm, realms, ws, wss };
  42. inline bool is_ssl(ProtocolEnvelope protocol) noexcept
  43. {
  44. switch (protocol) {
  45. case ProtocolEnvelope::realm:
  46. case ProtocolEnvelope::ws:
  47. break;
  48. case ProtocolEnvelope::realms:
  49. case ProtocolEnvelope::wss:
  50. return true;
  51. }
  52. return false;
  53. }
  54. // These integer types are selected so that they accomodate the requirements of
  55. // the protocol specification (`/doc/protocol.md`).
  56. //
  57. // clang-format off
  58. using file_ident_type = std::uint_fast64_t;
  59. using version_type = Replication::version_type;
  60. using salt_type = std::int_fast64_t;
  61. using timestamp_type = std::uint_fast64_t;
  62. using session_ident_type = std::uint_fast64_t;
  63. using request_ident_type = std::uint_fast64_t;
  64. using milliseconds_type = std::int_fast64_t;
  65. // clang-format on
  66. constexpr file_ident_type get_max_file_ident()
  67. {
  68. return 0x0'7FFF'FFFF'FFFF'FFFF;
  69. }
  70. struct SaltedFileIdent {
  71. file_ident_type ident;
  72. /// History divergence and identity spoofing protection.
  73. salt_type salt;
  74. };
  75. struct SaltedVersion {
  76. version_type version;
  77. /// History divergence protection.
  78. salt_type salt;
  79. };
  80. /// \brief A client's reference to a position in the server-side history.
  81. ///
  82. /// A download cursor refers to a position in the server-side history. If
  83. /// `server_version` is zero, the position is at the beginning of the history,
  84. /// otherwise the position is after the entry whose changeset produced that
  85. /// version. In general, positions are to be understood as places between two
  86. /// adjacent history entries.
  87. ///
  88. /// `last_integrated_client_version` is the version produced on the client by
  89. /// the last changeset that was sent to the server and integrated into the
  90. /// server-side Realm state at the time indicated by the history position
  91. /// specified by `server_version`, or zero if no changesets from the client were
  92. /// integrated by the server at that point in time.
  93. struct DownloadCursor {
  94. version_type server_version;
  95. version_type last_integrated_client_version;
  96. };
  97. /// Checks that `dc.last_integrated_client_version` is zero if
  98. /// `dc.server_version` is zero.
  99. bool is_consistent(DownloadCursor dc) noexcept;
  100. /// Checks that `a.last_integrated_client_version` and
  101. /// `b.last_integrated_client_version` are equal, if `a.server_version` and
  102. /// `b.server_version` are equal. Otherwise checks that
  103. /// `a.last_integrated_client_version` is less than, or equal to
  104. /// `b.last_integrated_client_version`, if `a.server_version` is less than
  105. /// `b.server_version`. Otherwise checks that `a.last_integrated_client_version`
  106. /// is greater than, or equal to `b.last_integrated_client_version`.
  107. bool are_mutually_consistent(DownloadCursor a, DownloadCursor b) noexcept;
  108. /// \brief The server's reference to a position in the client-side history.
  109. ///
  110. /// An upload cursor refers to a position in the client-side history. If
  111. /// `client_version` is zero, the position is at the beginning of the history,
  112. /// otherwise the position is after the entry whose changeset produced that
  113. /// version. In general, positions are to be understood as places between two
  114. /// adjacent history entries.
  115. ///
  116. /// `last_integrated_server_version` is the version produced on the server by
  117. /// the last changeset that was sent to the client and integrated into the
  118. /// client-side Realm state at the time indicated by the history position
  119. /// specified by `client_version`, or zero if no changesets from the server were
  120. /// integrated by the client at that point in time.
  121. struct UploadCursor {
  122. version_type client_version;
  123. version_type last_integrated_server_version;
  124. };
  125. /// Checks that `uc.last_integrated_server_version` is zero if
  126. /// `uc.client_version` is zero.
  127. bool is_consistent(UploadCursor uc) noexcept;
  128. /// Checks that `a.last_integrated_server_version` and
  129. /// `b.last_integrated_server_version` are equal, if `a.client_version` and
  130. /// `b.client_version` are equal. Otherwise checks that
  131. /// `a.last_integrated_server_version` is less than, or equal to
  132. /// `b.last_integrated_server_version`, if `a.client_version` is less than
  133. /// `b.client_version`. Otherwise checks that `a.last_integrated_server_version`
  134. /// is greater than, or equal to `b.last_integrated_server_version`.
  135. bool are_mutually_consistent(UploadCursor a, UploadCursor b) noexcept;
  136. /// A client's record of the current point of progress of the synchronization
  137. /// process. The client must store this persistently in the local Realm file.
  138. struct SyncProgress {
  139. /// The last server version that the client has heard about.
  140. SaltedVersion latest_server_version = {0, 0};
  141. /// The last server version integrated, or about to be integrated by the
  142. /// client.
  143. DownloadCursor download = {0, 0};
  144. /// The last client version integrated by the server.
  145. UploadCursor upload = {0, 0};
  146. };
  147. /// \brief Protocol errors discovered by the server, and reported to the client
  148. /// by way of ERROR messages.
  149. ///
  150. /// These errors will be reported to the client-side application via the error
  151. /// handlers of the affected sessions.
  152. ///
  153. /// ATTENTION: Please remember to update is_session_level_error() when
  154. /// adding/removing error codes.
  155. enum class ProtocolError {
  156. // clang-format off
  157. // Connection level and protocol errors
  158. connection_closed = 100, // Connection closed (no error)
  159. other_error = 101, // Other connection level error
  160. unknown_message = 102, // Unknown type of input message
  161. bad_syntax = 103, // Bad syntax in input message head
  162. limits_exceeded = 104, // Limits exceeded in input message
  163. wrong_protocol_version = 105, // Wrong protocol version (CLIENT) (obsolete)
  164. bad_session_ident = 106, // Bad session identifier in input message
  165. reuse_of_session_ident = 107, // Overlapping reuse of session identifier (BIND)
  166. bound_in_other_session = 108, // Client file bound in other session (IDENT)
  167. bad_message_order = 109, // Bad input message order
  168. bad_decompression = 110, // Error in decompression (UPLOAD)
  169. bad_changeset_header_syntax = 111, // Bad syntax in a changeset header (UPLOAD)
  170. bad_changeset_size = 112, // Bad size specified in changeset header (UPLOAD)
  171. bad_changesets = 113, // Bad changesets (UPLOAD)
  172. // Session level errors
  173. session_closed = 200, // Session closed (no error)
  174. other_session_error = 201, // Other session level error
  175. token_expired = 202, // Access token expired
  176. bad_authentication = 203, // Bad user authentication (BIND, REFRESH)
  177. illegal_realm_path = 204, // Illegal Realm path (BIND)
  178. no_such_realm = 205, // No such Realm (BIND)
  179. permission_denied = 206, // Permission denied (BIND, REFRESH)
  180. bad_server_file_ident = 207, // Bad server file identifier (IDENT) (obsolete!)
  181. bad_client_file_ident = 208, // Bad client file identifier (IDENT)
  182. bad_server_version = 209, // Bad server version (IDENT, UPLOAD, TRANSACT)
  183. bad_client_version = 210, // Bad client version (IDENT, UPLOAD)
  184. diverging_histories = 211, // Diverging histories (IDENT)
  185. bad_changeset = 212, // Bad changeset (UPLOAD)
  186. superseded = 213, // Superseded by new session for same client-side file (deprecated)
  187. disabled_session = 213, // Alias for `superseded` (deprecated)
  188. partial_sync_disabled = 214, // Partial sync disabled (BIND)
  189. unsupported_session_feature = 215, // Unsupported session-level feature
  190. bad_origin_file_ident = 216, // Bad origin file identifier (UPLOAD)
  191. bad_client_file = 217, // Synchronization no longer possible for client-side file
  192. server_file_deleted = 218, // Server file was deleted while session was bound to it
  193. client_file_blacklisted = 219, // Client file has been blacklisted (IDENT)
  194. user_blacklisted = 220, // User has been blacklisted (BIND)
  195. transact_before_upload = 221, // Serialized transaction before upload completion
  196. client_file_expired = 222, // Client file has expired
  197. user_mismatch = 223, // User mismatch for client file identifier (IDENT)
  198. too_many_sessions = 224, // Too many sessions in connection (BIND)
  199. invalid_schema_change = 225, // Invalid schema change (UPLOAD)
  200. // clang-format on
  201. };
  202. constexpr bool is_session_level_error(ProtocolError);
  203. /// Returns null if the specified protocol error code is not defined by
  204. /// ProtocolError.
  205. const char* get_protocol_error_message(int error_code) noexcept;
  206. const std::error_category& protocol_error_category() noexcept;
  207. std::error_code make_error_code(ProtocolError) noexcept;
  208. } // namespace sync
  209. } // namespace realm
  210. namespace std {
  211. template <>
  212. struct is_error_code_enum<realm::sync::ProtocolError> {
  213. static const bool value = true;
  214. };
  215. } // namespace std
  216. namespace realm {
  217. namespace sync {
  218. // Implementation
  219. inline bool is_consistent(DownloadCursor dc) noexcept
  220. {
  221. return (dc.server_version != 0 || dc.last_integrated_client_version == 0);
  222. }
  223. inline bool are_mutually_consistent(DownloadCursor a, DownloadCursor b) noexcept
  224. {
  225. if (a.server_version < b.server_version)
  226. return (a.last_integrated_client_version <= b.last_integrated_client_version);
  227. if (a.server_version > b.server_version)
  228. return (a.last_integrated_client_version >= b.last_integrated_client_version);
  229. return (a.last_integrated_client_version == b.last_integrated_client_version);
  230. }
  231. inline bool is_consistent(UploadCursor uc) noexcept
  232. {
  233. return (uc.client_version != 0 || uc.last_integrated_server_version == 0);
  234. }
  235. inline bool are_mutually_consistent(UploadCursor a, UploadCursor b) noexcept
  236. {
  237. if (a.client_version < b.client_version)
  238. return (a.last_integrated_server_version <= b.last_integrated_server_version);
  239. if (a.client_version > b.client_version)
  240. return (a.last_integrated_server_version >= b.last_integrated_server_version);
  241. return (a.last_integrated_server_version == b.last_integrated_server_version);
  242. }
  243. constexpr bool is_session_level_error(ProtocolError error)
  244. {
  245. return int(error) >= 200 && int(error) <= 299;
  246. }
  247. } // namespace sync
  248. } // namespace realm
  249. #endif // REALM_SYNC_PROTOCOL_HPP