--- /dev/null
+/*************************************************************************
+ *
+ * REALM CONFIDENTIAL
+ * __________________
+ *
+ * [2011] - [2015] Realm Inc
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Realm Incorporated and its suppliers,
+ * if any. The intellectual and technical concepts contained
+ * herein are proprietary to Realm Incorporated
+ * and its suppliers and may be covered by U.S. and Foreign Patents,
+ * patents in process, and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * from Realm Incorporated.
+ *
+ **************************************************************************/
+
+#ifndef REALM_SYNC_TRANSFORM_HPP
+#define REALM_SYNC_TRANSFORM_HPP
+
+#include <stddef.h>
+
+#include <realm/util/buffer.hpp>
+#include <realm/impl/cont_transact_hist.hpp>
+#include <realm/group_shared.hpp>
+#include <realm/chunked_binary.hpp>
+
+namespace realm {
+namespace sync {
+
+struct Changeset;
+
+/// Represents an entry in the history of changes in a sync-enabled Realm
+/// file. Server and client use different history formats, but this class is
+/// used both on the server and the client side. Each history entry corresponds
+/// to a version of the Realm state. For server and client-side histories, these
+/// versions are referred to as *server versions* and *client versions*
+/// respectively. These versions may, or may not correspond to Realm snapshot
+/// numbers (on the server-side they currently do not).
+///
+/// FIXME: Move this class into a separate header
+/// (`<realm/sync/history_entry.hpp>`).
+class HistoryEntry {
+public:
+ using timestamp_type = uint_fast64_t;
+ using file_ident_type = uint_fast64_t;
+ using version_type = _impl::History::version_type;
+
+ /// The time of origination of the changes referenced by this history entry,
+ /// meassured as the number of milliseconds since 2015-01-01T00:00:00Z, not
+ /// including leap seconds. For changes of local origin, this is the local
+ /// time at the point when the local transaction was committed. For changes
+ /// of remote origin, it is the remote time of origin at the client
+ /// identified by `origin_client_file_ident`.
+ timestamp_type origin_timestamp;
+
+ /// For changes of local origin, this is the identifier of the local
+ /// file. On the client side, the special value, zero, is used as a stand-in
+ /// for the actual file identifier. This is necessary because changes may
+ /// occur on the client-side before it obtains the the actual identifier
+ /// from the server. Depending on context, the special value, zero, will, or
+ /// will not have been replaced by the actual local file identifier.
+ ///
+ /// For changes of remote origin, this is the identifier of the file in the
+ /// context of which this change originated. This may be a client, or a
+ /// server-side file. For example, when a change "travels" from client file
+ /// A with identifier 2, through the server, to client file B with
+ /// identifier 3, then `origin_client_file_ident` will be 2 on the server
+ /// and in client file A. On the other hand, if the change originates on the
+ /// server, and the server-side file identifier is 1, then
+ /// `origin_client_file_ident` will be 1 in both client files.
+ ///
+ /// FIXME: Rename this member to `origin_file_ident`. It is no longer
+ /// necessarily a client-side file.
+ file_ident_type origin_client_file_ident;
+
+ /// For changes of local origin on the client side, this is the last server
+ /// version integrated locally prior to this history entry. In other words,
+ /// it is a copy of `remote_version` of the last preceding history entry
+ /// that carries changes of remote origin, or zero if there is no such
+ /// preceding history entry.
+ ///
+ /// For changes of local origin on the server-side, this is always zero.
+ ///
+ /// For changes of remote origin, this is the version produced within the
+ /// remote-side Realm file by the change that gave rise to this history
+ /// entry. The remote-side Realm file is not always the same Realm file from
+ /// which the change originated. On the client side, the remote side is
+ /// always the server side, and `remote_version` is always a server version
+ /// (since clients do not speak directly to each other). On the server side,
+ /// the remote side is always a client side, and `remote_version` is always
+ /// a client version.
+ version_type remote_version;
+
+ /// Referenced memory is not owned by this class.
+ ChunkedBinaryData changeset;
+};
+
+
+/// The interface between the sync history and the operational transformer
+/// (Transformer).
+class TransformHistory {
+public:
+ using timestamp_type = HistoryEntry::timestamp_type;
+ using file_ident_type = HistoryEntry::file_ident_type;
+ using version_type = HistoryEntry::version_type;
+
+ /// Get the first history entry where the produced version is greater than
+ /// `begin_version` and less than or equal to `end_version`, and whose
+ /// changeset is neither empty, nor produced by integration of a changeset
+ /// received from the specified remote peer.
+ ///
+ /// If \a buffer is non-null, memory will be allocated and transferred to
+ /// \a buffer. The changeset will be copied into the newly allocated memory.
+ ///
+ /// If \a buffer is null, the changeset is not copied out of the Realm,
+ /// and entry.changeset.data() does not point to the changeset.
+ /// The changeset in the Realm could be chunked, hence it is not possible
+ /// to point to it with BinaryData. entry.changeset.size() always gives the
+ /// size of the changeset.
+ ///
+ /// \param begin_version, end_version The range of versions to consider. If
+ /// `begin_version` is equal to `end_version`, this is the empty range. If
+ /// `begin_version` is zero, it means that everything preceeding
+ /// `end_version` is to be considered, which is again the empty range if
+ /// `end_version` is also zero. Zero is is special value in that no
+ /// changeset produces that version. It is an error if `end_version`
+ /// preceeds `begin_version`, or if `end_version` is zero and
+ /// `begin_version` is not.
+ ///
+ /// \param not_from_remote_client_file_ident Skip entries whose changeset is
+ /// produced by integration of changesets received from this remote
+ /// peer. Zero if the remote peer is the server, otherwise the peer
+ /// identifier of a client.
+ ///
+ /// \param only_nonempty Skip entries with empty changesets.
+ ///
+ /// \return The version produced by the changeset of the located history
+ /// entry, or zero if no history entry exists matching the specified
+ /// criteria.
+ virtual version_type find_history_entry(version_type begin_version, version_type end_version,
+ file_ident_type not_from_remote_client_file_ident,
+ bool only_nonempty, HistoryEntry& entry) const noexcept = 0;
+
+ /// Get the specified reciprocal changeset. The targeted history entry is
+ /// the one whose untransformed changeset produced the specified version.
+ ///
+ /// \param remote_client_file_ident Zero if the remote peer is the server,
+ /// otherwise the peer identifier of a client.
+ virtual ChunkedBinaryData get_reciprocal_transform(version_type version,
+ file_ident_type remote_client_file_ident) const = 0;
+
+ /// Replace the specified reciprocally transformed changeset. The targeted
+ /// history entry is the one whose untransformed changeset produced the
+ /// specified version.
+ ///
+ /// \param remote_client_file_ident See get_reciprocal_transform().
+ ///
+ /// \param encoded_changeset The new reciprocally transformed changeset.
+ virtual void set_reciprocal_transform(version_type version,
+ file_ident_type remote_client_file_ident,
+ BinaryData encoded_changeset) = 0;
+
+ virtual ~TransformHistory() noexcept {}
+};
+
+
+class TransformError; // Exception
+
+class Transformer {
+public:
+ using timestamp_type = HistoryEntry::timestamp_type;
+ using file_ident_type = HistoryEntry::file_ident_type;
+ using version_type = HistoryEntry::version_type;
+
+ class RemoteChangeset;
+ class Reporter;
+
+ /// Produce an operationally transformed version of the specified changesets,
+ /// which are assumed to be of remote origin, and received from remote peer
+ /// P. Note that P is not necessarily the peer from which the changes
+ /// originated.
+ ///
+ /// Operational transformation is carried out between the specified
+ /// changesets and all causally unrelated changesets in the local history. A
+ /// changeset in the local history is causally unrelated if, and only if it
+ /// occurs after the local changeset that produced
+ /// `remote_changeset.last_integrated_local_version` and is not a produced
+ /// by integration of a changeset received from P. This assumes that
+ /// `remote_changeset.last_integrated_local_version` is set to the local
+ /// version produced by the last local changeset, that was integrated by P
+ /// before P produced the specified changeset.
+ ///
+ /// The operational transformation is reciprocal (two-way), so it also
+ /// transforms the causally unrelated local changesets. This process does
+ /// not modify the history itself (the changesets available through
+ /// TransformHistory::get_history_entry()), instead the reciprocally
+ /// transformed changesets are stored separately, and individually for each
+ /// remote peer, such that they can participate in transformation of the
+ /// next incoming changeset from P. Note that the peer identifier of P can
+ /// be derived from `origin_client_file_ident` and information about whether
+ /// the local peer is a server or a client.
+ ///
+ /// In general, if A and B are two causally unrelated (alternative)
+ /// changesets based on the same version V, then the operational
+ /// transformation between A and B produces changesets A' and B' such that
+ /// both of the concatenated changesets A + B' and B + A' produce the same
+ /// final state when applied to V. Operational transformation is meaningful
+ /// only when carried out between alternative changesets based on the same
+ /// version.
+ ///
+ /// \return The size of the transformed version of the specified
+ /// changesets. Upon return, the transformed changesets are concatenated
+ /// and placed in \a output_buffer.
+ ///
+ /// \throw TransformError Thrown if operational transformation fails due to
+ /// a problem with the specified changeset.
+ virtual void transform_remote_changesets(TransformHistory&,
+ version_type current_local_version,
+ Changeset* changesets,
+ std::size_t num_changesets,
+ Reporter* = nullptr) = 0;
+
+ virtual ~Transformer() noexcept {}
+};
+
+/// \param local_client_file_ident The server assigned local client file
+/// identifier. This must be zero on the server-side, and only on the
+/// server-side.
+std::unique_ptr<Transformer> make_transformer(Transformer::file_ident_type local_client_file_ident);
+
+class Transformer::RemoteChangeset {
+public:
+ /// The version produced on the remote peer by this changeset.
+ ///
+ /// On the server, the remote peer is the client from which the changeset
+ /// originated, and `remote_version` is the client version produced by the
+ /// changeset on that client.
+ ///
+ /// On a client, the remote peer is the server, and `remote_version` is the
+ /// server version produced by this changeset on the server. Since the
+ /// server is never the originator of changes, this changeset must in turn
+ /// have been produced on the server by integration of a changeset uploaded
+ /// by some other client.
+ version_type remote_version = 0;
+
+ /// The last local version that has been integrated into `remote_version`.
+ ///
+ /// A local version, L, has been integrated into a remote version, R, when,
+ /// and only when L is the latest local version such that all preceeding
+ /// changesets in the local history have been integrated by the remote peer
+ /// prior to R.
+ ///
+ /// On the server, this is the last server version integrated into the
+ /// client version `remote_version`. On a client, it is the last client
+ /// version integrated into the server version `remote_version`.
+ version_type last_integrated_local_version = 0;
+
+ /// The changeset itself.
+ ChunkedBinaryData data;
+
+ /// Same meaning as `HistoryEntry::origin_timestamp`.
+ timestamp_type origin_timestamp = 0;
+
+ /// Same meaning as `HistoryEntry::origin_client_file_ident`.
+ file_ident_type origin_client_file_ident = 0;
+
+ /// If the changeset was compacted during download, the size of the original
+ /// changeset.
+ size_t original_changeset_size = 0;
+
+ RemoteChangeset() {}
+ RemoteChangeset(version_type rv, version_type lv, ChunkedBinaryData d, timestamp_type ot,
+ file_ident_type fi);
+};
+
+class Transformer::Reporter {
+public:
+ virtual void report_merges(long num_merges) = 0;
+};
+
+
+void parse_remote_changeset(const Transformer::RemoteChangeset&, Changeset&);
+
+
+
+
+// Implementation
+
+class TransformError: public std::runtime_error {
+public:
+ TransformError(const std::string& message):
+ std::runtime_error(message)
+ {
+ }
+};
+
+inline Transformer::RemoteChangeset::RemoteChangeset(version_type rv, version_type lv,
+ ChunkedBinaryData d, timestamp_type ot,
+ file_ident_type fi):
+ remote_version(rv),
+ last_integrated_local_version(lv),
+ data(d),
+ origin_timestamp(ot),
+ origin_client_file_ident(fi)
+{
+}
+
+} // namespace sync
+} // namespace realm
+
+#endif // REALM_SYNC_TRANSFORM_HPP