• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / github_pull_request_278228

04 Oct 2023 10:15PM UTC coverage: 91.582% (+0.007%) from 91.575%
github_pull_request_278228

Pull #7029

Evergreen

tgoyne
Use UNITTEST_LOG_LEVEL in objectstore tests

For historical reasons core and sync tests use the UNITTEST_LOG_LEVEL
environment variable to determine the test log level, while object store tests
used a build time setting. This brings them into alignment on using the env
variable, and applies it via setting the default log level on startup in a
single place.
Pull Request #7029: Use UNITTEST_LOG_LEVEL in objectstore tests

94218 of 173442 branches covered (0.0%)

46 of 54 new or added lines in 5 files covered. (85.19%)

51 existing lines in 12 files now uncovered.

230351 of 251523 relevant lines covered (91.58%)

6704577.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.69
/src/realm/sync/noinst/server/server_history.cpp
1
#include <algorithm>
2
#include <cstring>
3
#include <stack>
4

5
#include <realm/sync/changeset_encoder.hpp>
6
#include <realm/sync/changeset_parser.hpp>
7
#include <realm/sync/impl/clamped_hex_dump.hpp>
8
#include <realm/sync/instruction_applier.hpp>
9
#include <realm/sync/noinst/compact_changesets.hpp>
10
#include <realm/sync/noinst/server/server_history.hpp>
11
#include <realm/table_view.hpp>
12
#include <realm/util/hex_dump.hpp>
13
#include <realm/util/input_stream.hpp>
14
#include <realm/util/value_reset_guard.hpp>
15
#include <realm/version.hpp>
16

17
using namespace realm;
18
using namespace realm::sync;
19
using namespace realm::util;
20
using namespace _impl;
21

22

23
/*
24

25
Client file            Entry  Client  Identifier  Proxy  Reciprocal   Last seen      Locked server
26
entry type             index  type    Salt        file   history (1)  timestamp (2)  version
27
---------------------------------------------------------------------------------------------------
28
Special (3)            0      0       no          no     YES          no             no
29
Root (4)               1      0       no          no     no           no             no
30
Upstream (5)           2+     0       no          no     no           no             no
31
Self (6)               2+     6       no          no     no           no             no
32
Indirect client        2+     1       no          YES    no           no             no
33
Legacy (7)             2+     5       YES         no     YES          YES            YES
34
Direct regular client  2+     2       YES         no     YES          YES            YES
35
Direct subserver       2+     4       YES         no     YES          YES            YES
36
Direct partial view    2+     3       YES         no     YES          YES            YES
37

38
1) Entries that may have a reciprocal history may also have nonzero
39
   `client_version` and nonzero `rh_base_version`. The reciprocal history is
40
   absent in expired entries.
41

42
2) An entry that represents a direct client has expired if and only if this
43
   timestamp is zero. Here, subservers, partial views, and legacy entries are
44
   considered to be direct clients.
45

46
3) This is a special entry used to represent the upstream server on a subtier
47
   node of a star topology server cluster. Note that there is no valid client
48
   file identifier associated with this entry, because a valid client file
49
   identifier is strictly greater than zero.
50

51
4) This entry represents the root of a star topology server cluster. This entry
52
   is always present. For files that do not function as partial views (or have
53
   not yet been initialized as such) and do not reside on a subtier node of a
54
   star topology server cluster (or have not yet been initialized as such), this
55
   entry represents the file itself.
56

57
5) For a nonpartial file on a subtier server, this is an entry for the upstream
58
   server or for a file reachable via the upstream server. For a partial file,
59
   this is an entry for the reference file, or a file reachable via the
60
   reference file.
61

62
6) This is the entry that represents the file itself. It is present if if the
63
   file functions as a partial view (and has been initialized as such), or if it
64
   resides on a subtier node of a star topology server cluster (and has been
65
   initialized as such).
66

67
7) This is an entry created prior to history schema version 10. The exact type
68
   of this entry is unknown. However, since the star topology feature has not
69
   been publicly released yet, it is likely to represent either an immediate
70
   regular client, an immediate partial view, or a client of an immediate
71
   partial view.
72

73

74
Abstract history schema
75
-----------------------
76

77
  table client_files:
78
    int ident_salt
79
    int client_version
80
    int rh_base_version (0 if recip_hist is null) (doubles as last server version integrated by client in
81
`client_version`)
82
    // recip_hist_index = produced_server_version - recip_hist_base_version - 1
83
    table recip_hist: (nullable)
84
      string changeset (nullable)
85
    link proxy_file (references client_files)
86
    int client_type
87
    int last_seen (UNIX timestamp)
88
    int locked_server_version
89

90
  // Server version on which the first entry in `sync_history` is based. This
91
  // will be zero unless the history has been trimmed (note, history trimming is
92
  // not yet implemented, so for now, it is always zero).
93
  //
94
  // `curr_server_version = history_base_version + sync_history.size()`
95
  int history_base_version
96

97
  // Server version until which the history has been compacted. This can never
98
  // decrease.
99
  int compacted_until_version
100

101
  // Salt attached to `history_base_version`. This is required to be zero if
102
  // `history_base_version` is zero.
103
  int base_version_salt
104

105
  // History of server versions (one entry per server version).
106
  //
107
  // For entries whose changesets are of local origin, `origin_file` is
108
  // zero. For other entries, `origin_file` points to the entry in
109
  // `client_files` representing the file in which the initial untransformed
110
  // changeset originated.
111
  //
112
  // For entries whose changesets are of local origin, `client_version` is
113
  // always zero. For other entries, the a changeset was produced by integration
114
  // of a changeset received from a remote peer (client or upstream server), and
115
  // `client_version` is the version produced on that remote peer by the
116
  // received changeset.
117
  //
118
  // `history_entry_index = produced_server_version - history_base_version - 1`
119
  table sync_history:
120
    int version_salt (random) (formerly known as server_session_ident)
121
    link origin_file (references client_files)
122
    int client_version
123
    int timestamp
124
    string changeset
125

126
  // Continuous transactions history (one entry per Realm version (snapshot number), trimmed)
127
  //
128
  // `ct_history_entry_index = realm_version - ct_history_base_version - 1`
129
  table ct_history:
130
    link history_entry (nullable) (references sync_history) (null for empty changesets of local origin) // FIXME: This
131
looks wrong!
132

133
  int history_byte_size
134

135
  // This object is only present after a successful invocation of
136
  // ServerHistory::add_upstream_sync_status()
137
  optional object upstream_status:
138
    SaltedFileIdent client_file_ident
139
    SaltedVersion   progress_latest_server_version
140
    DownloadCursor  progress_download
141
    UploadCursor    progress_upload
142

143
  // This array is only present after a successful invocation of
144
  // ServerHistory::initiate_as_partial_view()
145
  optional array partial_sync:
146
    // A file identifier allocated in the context of both the reference, and the
147
    // partial Realm, and with the purpose of identifying changesets in the
148
    // history of the reference Realm, that originate from, or via the partial
149
    // Realm.
150
    //
151
    // At the same time, it is used to identify outgoing changesets in the
152
    // history of the partial Realm that originate from, or via the reference
153
    // Realm.
154
    int partial_file_ident
155
    int partial_file_ident_salt
156
    int progress_server_version
157
    int progress_reference_version
158
    int progress_reference_version_salt
159

160

161

162
History representation
163
----------------------
164

165
  mixed_array_ref history:
166
    0 -> mixed_array_ref client_files:
167
      0 -> int_bptree_ref client_file_ident_salts:
168
        file_ident -> ident_salt
169
      1 -> int_bptree_ref client_file_client_versions:
170
        file_ident -> client_version
171
      2 -> int_bptree_ref client_file_rh_base_versions:
172
        file_ident -> rh_base_version
173
      3 -> mixed_bptree_ref cf_recip_hist_refs:
174
        file_ident -> nullable_binary_bptree_ref recip_hist:
175
          recip_hist_index -> changeset
176
      4 -> int_bptree_ref cf_proxy_files:
177
        file_ident -> proxy_file_ident
178
      5 -> int_bptree_ref cf_client_types:
179
        file_ident -> client_type
180
      6 -> int_bptree_ref cf_last_seen:
181
        file_indet -> unix_timestamp
182
      7 -> int_bptree_ref cf_locked_server_versions:
183
        file_ident -> locked_server_version
184
    1 -> tagged_int history_base_version
185
    2 -> tagged_int base_version_salt
186
    3 -> mixed_array_ref sync_history:
187
      0 -> int_bptree_ref sh_version_salts:
188
        history_entry_index -> server_version_salt
189
      1 -> int_bptree_ref sh_origin_files:
190
        history_entry_index -> file_ident
191
      2 -> int_bptree_ref sh_client_versions:
192
        history_entry_index -> sync_history.client_version
193
      3 -> int_bptree_ref sync_history_timestamps:
194
        history_entry_index -> sync_history.timestamp
195
      4 -> binary_bptree_ref sh_changesets:
196
        history_entry_index -> sync_history.changeset
197
    4 -> binary_bptree_ref ct_history
198
      Core history_entry_index -> (changeset in Core's format)
199
    5 -> tagged_int history_byte_size
200
    6 -> ref to ObjectIDHistoryState
201
    7 -> int_array_ref upstream_status:
202
      0 -> int client_file_ident
203
      1 -> int client_file_ident_salt
204
      2 -> int progress_latest_server_version
205
      3 -> int progress_latest_server_version_salt
206
      4 -> int progress_download_server_version
207
      5 -> int progress_download_client_version
208
      6 -> int progress_upload_client_version
209
      7 -> int progress_upload_server_version
210
    8 -> int_array_ref partial_sync:
211
      0 -> int partial_file_ident
212
      1 -> int partial_file_ident_salt
213
      2 -> int progress_server_version
214
      3 -> int progress_reference_version
215
      4 -> int progress_reference_version_salt
216
    9 -> tagged_int compacted_until_version
217
   10 -> tagged_int last_compaction_at
218
*/
219

220

221
namespace {
222

223
// This is the hard-coded file identifier that represents changes of local
224
// origin in a file on the root node of a star topology server cluster, or a
225
// file on a server that is not part of a cluster.
226
constexpr ServerHistory::file_ident_type g_root_node_file_ident = 1;
227

228

229
} // unnamed namespace
230

231

232
void ServerHistory::get_status(VersionInfo& version_info, bool& has_upstream_sync_status,
233
                               file_ident_type& partial_file_ident,
234
                               version_type& partial_progress_reference_version) const
235
{
5,890✔
236
    TransactionRef rt = m_db->start_read(); // Throws
5,890✔
237
    version_type realm_version = rt->get_version();
5,890✔
238
    const_cast<ServerHistory*>(this)->set_group(rt.get());
5,890✔
239
    ensure_updated(realm_version); // Throws
5,890✔
240
    version_info.realm_version = realm_version;
5,890✔
241
    version_info.sync_version = get_salted_server_version();
5,890✔
242
    has_upstream_sync_status = (m_acc && m_acc->upstream_status.is_attached());
5,890✔
243
    bool is_initiated_as_partial_view = (m_acc && m_acc->partial_sync.is_attached());
5,890✔
244
    if (is_initiated_as_partial_view) {
5,890✔
245
        partial_file_ident = file_ident_type(m_acc->partial_sync.get(s_ps_partial_file_ident_iip));
×
246
        REALM_ASSERT(partial_file_ident != 0);
×
247
        partial_progress_reference_version =
×
248
            version_type(m_acc->partial_sync.get(s_ps_progress_reference_version_iip));
×
249
    }
×
250
    else {
5,890✔
251
        partial_file_ident = 0;
5,890✔
252
        partial_progress_reference_version = 0;
5,890✔
253
    }
5,890✔
254
}
5,890✔
255

256

257
void ServerHistory::allocate_file_identifiers(FileIdentAllocSlots& slots, VersionInfo& version_info)
258
{
1,480✔
259
    TransactionRef tr = m_db->start_write(); // Throws
1,480✔
260
    version_type realm_version = tr->get_version();
1,480✔
261
    ensure_updated(realm_version); // Throws
1,480✔
262
    prepare_for_write();           // Throws
1,480✔
263

590✔
264
    if (REALM_UNLIKELY(m_acc->upstream_status.is_attached())) {
1,480✔
265
        throw util::runtime_error("Cannot allocate new client file identifiers in a file "
×
266
                                  "that is associated with an upstream server");
×
267
    }
×
268

590✔
269
    for (FileIdentAllocSlot& slot : slots)
1,480✔
270
        slot.file_ident = allocate_file_ident(slot.proxy_file, slot.client_type); // Throws
1,534✔
271

590✔
272
    version_type new_realm_version = tr->commit(); // Throws
1,480✔
273
    version_info.realm_version = new_realm_version;
1,480✔
274
    version_info.sync_version = get_salted_server_version();
1,480✔
275
}
1,480✔
276

277

278
bool ServerHistory::register_received_file_identifier(file_ident_type received_file_ident,
279
                                                      file_ident_type proxy_file_ident, ClientType client_type,
280
                                                      salt_type& file_ident_salt, VersionInfo& version_info)
281
{
×
282
    TransactionRef tr = m_db->start_write(); // Throws
×
283
    version_type realm_version = tr->get_version();
×
284
    ensure_updated(realm_version); // Throws
×
285
    prepare_for_write();           // Throws
×
286

287
    salt_type salt = 0;
×
288
    bool success = try_register_file_ident(received_file_ident, proxy_file_ident, client_type,
×
289
                                           salt); // Throws
×
290
    if (REALM_UNLIKELY(!success))
×
291
        return false;
×
292

293
    version_type new_realm_version = tr->commit(); // Throws
×
294
    file_ident_salt = salt;
×
295
    version_info.realm_version = new_realm_version;
×
296
    version_info.sync_version = get_salted_server_version();
×
297
    return true;
×
298
}
×
299

300

301
bool ServerHistory::integrate_client_changesets(const IntegratableChangesets& integratable_changesets,
302
                                                VersionInfo& version_info, bool& backup_whole_realm,
303
                                                IntegrationResult& result, util::Logger& logger)
304
{
35,654✔
305
    REALM_ASSERT(!integratable_changesets.empty());
35,654✔
306

18,596✔
307
    // Determine the order in which to process client files. Client files with
18,596✔
308
    // serialized transactions must be processed first. At most one of the
18,596✔
309
    // available serialized transactions can succeed.
18,596✔
310
    //
18,596✔
311
    // Subordinately, the order in which to process client files is randomized
18,596✔
312
    // to prevent integer ordering between client file identifiers from giving
18,596✔
313
    // unfair priority to some client files.
18,596✔
314
    std::vector<file_ident_type>& client_file_order = m_client_file_order_buffer;
35,654✔
315
    client_file_order.clear();
35,654✔
316
    bool has_changesets = false;
35,654✔
317
    for (const auto& pair : integratable_changesets) {
38,618✔
318
        file_ident_type client_file_ident = pair.first;
38,618✔
319
        client_file_order.push_back(client_file_ident);
38,618✔
320
        const IntegratableChangesetList& list = pair.second;
38,618✔
321
        if (list.has_changesets())
38,618✔
322
            has_changesets = true;
18,768✔
323
    }
38,618✔
324

18,596✔
325
    result = {};
35,654✔
326
    for (;;) {
35,672✔
327
        if (has_changesets) {
35,672✔
328
            result.partial_clear();
16,798✔
329
        }
16,798✔
330

18,604✔
331
        bool anything_to_do = (integratable_changesets.size() > result.excluded_client_files.size());
35,672✔
332
        if (REALM_UNLIKELY(!anything_to_do))
35,672✔
333
            return false;
18,614✔
334

18,594✔
335
        file_ident_type current_client_file_ident = 0;
35,652✔
336
        ExtendedIntegrationError current_error_potential = {};
35,652✔
337
        std::size_t num_changesets_to_dump = 0;
35,652✔
338
        bool dump_changeset_info = false;
35,652✔
339

18,594✔
340
        try {
35,652✔
341
            TransactionRef tr = m_db->start_write(); // Throws
35,652✔
342
            version_type realm_version = tr->get_version_of_current_transaction().version;
35,652✔
343
            ensure_updated(realm_version); // Throws
35,652✔
344
            prepare_for_write();           // Throws
35,652✔
345

18,594✔
346
            bool dirty = false;
35,652✔
347
            bool backup_whole_realm_2 = false;
35,652✔
348
            for (file_ident_type client_file_ident : client_file_order) {
38,616✔
349
                REALM_ASSERT(client_file_ident > 0);
38,616✔
350
                REALM_ASSERT(client_file_ident != g_root_node_file_ident);
38,616✔
351
                REALM_ASSERT(client_file_ident != m_local_file_ident);
38,616✔
352
                bool excluded =
38,616✔
353
                    (result.excluded_client_files.find(client_file_ident) != result.excluded_client_files.end());
38,616✔
354
                if (REALM_UNLIKELY(excluded))
38,616✔
355
                    continue;
20,056✔
356
                current_client_file_ident = client_file_ident;
38,616✔
357
                // Verify that the client file entry has not expired
20,056✔
358
                current_error_potential = ExtendedIntegrationError::client_file_expired;
38,616✔
359
                std::size_t client_file_index = std::size_t(client_file_ident);
38,616✔
360
                std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(client_file_index);
38,616✔
361
                bool expired = (last_seen_timestamp == 0);
38,616✔
362
                if (REALM_UNLIKELY(expired))
38,616✔
363
                    goto error;
20,056✔
364
                const IntegratableChangesetList& list = integratable_changesets.find(client_file_ident)->second;
38,616✔
365
                std::vector<RemoteChangeset> changesets;
38,616✔
366
                current_error_potential = ExtendedIntegrationError::bad_origin_file_ident;
38,616✔
367
                for (const IntegratableChangeset& ic : list.changesets) {
37,664✔
368
                    REALM_ASSERT(ic.client_file_ident == client_file_ident);
34,378✔
369
                    // Verify that the origin file identifier either is the
16,770✔
370
                    // client's file identifier, or a file identifier of a
16,770✔
371
                    // subordinate client for which the sending client acts as a
16,770✔
372
                    // proxy.
16,770✔
373
                    file_ident_type origin_file_ident = ic.origin_file_ident;
34,378✔
374
                    if (origin_file_ident != 0) {
34,378✔
375
                        static_assert(g_root_node_file_ident == 1, "");
×
376
                        if (REALM_UNLIKELY(origin_file_ident <= g_root_node_file_ident))
×
377
                            goto error;
×
378
                        if (REALM_UNLIKELY(std::uint_fast64_t(origin_file_ident) >= m_num_client_files))
×
379
                            goto error;
×
380
                        std::size_t index = std::size_t(origin_file_ident);
×
381
                        file_ident_type proxy_file_ident = file_ident_type(m_acc->cf_proxy_files.get(index));
×
382
                        if (REALM_UNLIKELY(proxy_file_ident != ic.client_file_ident))
×
383
                            goto error;
×
384
                    }
34,378✔
385
                    RemoteChangeset changeset{ic};
34,378✔
386
                    changesets.push_back(changeset);             // Throws
34,378✔
387
                    result.integrated_changesets.push_back(&ic); // Throws
34,378✔
388
                }
34,378✔
389

20,056✔
390
                auto integrate = [&](std::size_t begin, std::size_t end, UploadCursor upload_progress) {
38,618✔
391
                    const RemoteChangeset* changesets_2 = changesets.data() + begin;
38,618✔
392
                    std::size_t num_changesets = end - begin;
38,618✔
393
                    num_changesets_to_dump += num_changesets;
38,618✔
394
                    bool dirty_2 = integrate_remote_changesets(
38,618✔
395
                        client_file_ident, upload_progress, list.locked_server_version, changesets_2, num_changesets,
38,618✔
396
                        logger); // Throws
38,618✔
397
                    if (dirty_2) {
38,618✔
398
                        dirty = true;
38,600✔
399
                        bool backup_whole_realm_3 =
38,600✔
400
                            (begin == end || upload_progress.client_version != changesets[end - 1].remote_version ||
38,600✔
401
                             list.locked_server_version != upload_progress.last_integrated_server_version);
25,614✔
402
                        if (backup_whole_realm_3)
38,600✔
403
                            backup_whole_realm_2 = true;
27,694✔
404
                    }
38,600✔
405
                };
38,618✔
406

20,056✔
407
                // Note: This value will be read if an exception is thrown
20,056✔
408
                // below. Clang's static analyzer incorrectly reports it as
20,056✔
409
                // a dead store.
20,056✔
410
                current_error_potential = ExtendedIntegrationError::bad_changeset;
38,616✔
411
                static_cast<void>(current_error_potential);
38,616✔
412

20,056✔
413
                std::size_t num_changesets = changesets.size();
38,616✔
414
                std::size_t aug_num_changesets = num_changesets;
38,616✔
415
                logger.debug("Integrating %1 changesets from client file %2", aug_num_changesets, client_file_ident);
38,616✔
416

20,056✔
417
                integrate(0, num_changesets, list.upload_progress); // Throws
38,616✔
418
            }
38,616✔
419

18,594✔
420
            if (dirty) {
35,652✔
421
                auto ta = util::make_temp_assign(m_is_local_changeset, false, true);
35,634✔
422
                version_info.realm_version = tr->commit(); // Throws
35,634✔
423
                version_info.sync_version = get_salted_server_version();
35,634✔
424
                if (backup_whole_realm_2)
35,634✔
425
                    backup_whole_realm = true;
26,462✔
426
                return true;
35,634✔
427
            }
35,634✔
428
            return false;
18✔
429
        }
18✔
430
        catch (BadChangesetError& e) {
4✔
431
            logger.error("Failed to parse, or apply changeset received from client: %1",
4✔
432
                         e.what()); // Throws
4✔
433
            dump_changeset_info = true;
4✔
434
        }
4✔
435
        catch (TransformError& e) {
18,602✔
436
            logger.error("Failed to transform changeset received from client: %1",
16✔
437
                         e.what()); // Throws
16✔
438
            dump_changeset_info = true;
16✔
439
        }
16✔
440

18,594✔
441
        if (dump_changeset_info) {
18,604✔
442
            std::size_t changeset_ndx = 0;
20✔
443
            std::size_t num_parts = num_changesets_to_dump;
20✔
444
            for (std::size_t i = 0; i < num_parts; ++i) {
40✔
445
                // Regular changeset
10✔
446
                const IntegratableChangeset& ic = *result.integrated_changesets[changeset_ndx];
20✔
447
                std::string hex_dump = util::hex_dump(ic.changeset.data(),
20✔
448
                                                      ic.changeset.size()); // Throws
20✔
449
                logger.error("Failed transaction (part %1/%2): Changeset "
20✔
450
                             "(client_file_ident=%3, origin_timestamp=%4, "
20✔
451
                             "origin_file_ident=%5, client_version=%6, "
20✔
452
                             "last_integrated_server_version=%7): %8",
20✔
453
                             (i + 1), num_parts, ic.client_file_ident, ic.origin_timestamp, ic.origin_file_ident,
20✔
454
                             ic.upload_cursor.client_version, ic.upload_cursor.last_integrated_server_version,
20✔
455
                             hex_dump); // Throws
20✔
456
                ++changeset_ndx;
20✔
457
                continue;
20✔
458
            }
20✔
459
        }
20✔
460

10✔
461
    error:
20✔
462
        REALM_ASSERT(current_client_file_ident != 0);
20✔
463
        result.excluded_client_files[current_client_file_ident] = current_error_potential; // Throws
20✔
464
    }
20✔
465
}
35,654✔
466

467

468
auto ServerHistory::integrate_backup_idents_and_changeset(
469
    version_type expected_realm_version, salt_type server_version_salt,
470
    const FileIdentAllocSlots& file_ident_alloc_slots,
471
    const std::vector<IntegratableChangeset>& integratable_changesets, util::Logger& logger) -> IntegratedBackup
472
{
×
473
    IntegratedBackup result;
×
474
    result.success = false;
×
475

476
    try {
×
477
        TransactionRef tr = m_db->start_write(); // Throws
×
478
        version_type realm_version = tr->get_version_of_current_transaction().version;
×
479
        ensure_updated(realm_version); // Throws
×
480
        prepare_for_write();           // Throws
×
481

482
        result.version_info.realm_version = realm_version;
×
483

484
        if (realm_version + 1 != expected_realm_version)
×
485
            return result;
×
486

487
        // To ensure identity of a server Realm and its backup, it is necessary
488
        // to set the server_version_salt of the backup Realm to the same value
489
        // as that of the original Realm.
490
        m_salt_for_new_server_versions = server_version_salt;
×
491

492
        for (const auto& slot : file_ident_alloc_slots) {
×
493
            if (std::uint_fast64_t(slot.file_ident.ident) != m_num_client_files)
×
494
                return result;
×
495
            add_client_file(slot.file_ident.salt, slot.proxy_file, slot.client_type); // Throws
×
496
        }
×
497

498
        std::map<file_ident_type, std::vector<RemoteChangeset>> changesets;
×
499

500
        for (const IntegratableChangeset& ic : integratable_changesets)
×
501
            changesets[ic.client_file_ident].push_back(ic);
×
502

503
        for (auto& pair : changesets) {
×
504
            file_ident_type client_file_ident = pair.first;
×
505
            // FIXME: Backup should also get the proper upload progress and
506
            // locked server version. This requires extending the backup
507
            // protocol.
508
            const auto& back = pair.second.back();
×
509
            UploadCursor upload_progress = {back.remote_version, back.last_integrated_local_version};
×
510
            version_type locked_server_version = upload_progress.last_integrated_server_version;
×
511
            integrate_remote_changesets(client_file_ident, upload_progress, locked_server_version, pair.second.data(),
×
512
                                        pair.second.size(), logger); // Throws
×
513
        }
×
514

515
        auto ta = util::make_temp_assign(m_is_local_changeset, false, true);
×
516
        result.version_info.realm_version = tr->commit(); // Throws
×
517
        result.version_info.sync_version = get_salted_server_version();
×
518
        result.success = true;
×
519
    }
×
520
    catch (BadChangesetError& e) {
×
521
        logger.error("Bad incremental backup", e.what()); // Throws
×
522
    }
×
523
    catch (TransformError& e) {
×
524
        logger.error("Bad incremental backup", e.what()); // Throws
×
525
    }
×
526

527
    return result;
×
528
}
×
529

530

531
auto ServerHistory::allocate_file_ident(file_ident_type proxy_file_ident, ClientType client_type) -> SaltedFileIdent
532
{
1,532✔
533
    REALM_ASSERT(!m_acc->upstream_status.is_attached());
1,532✔
534

622✔
535
    std::size_t file_index = m_num_client_files;
1,532✔
536
    salt_type salt = register_client_file_by_index(file_index, proxy_file_ident, client_type); // Throws
1,532✔
537

622✔
538
    if (uint64_t(file_index) > get_max_file_ident())
1,532✔
539
        throw util::overflow_error{"File identifier"};
×
540

622✔
541
    file_ident_type ident = file_ident_type(file_index);
1,532✔
542
    return {ident, salt};
1,532✔
543
}
1,532✔
544

545

546
void ServerHistory::register_assigned_file_ident(file_ident_type file_ident)
547
{
×
548
    file_ident_type proxy_file_ident = 0; // No proxy
×
549
    ClientType client_type = ClientType::self;
×
550
    salt_type file_ident_salt; // Dummy
×
551
    bool success = try_register_file_ident(file_ident, proxy_file_ident, client_type,
×
552
                                           file_ident_salt); // Throws
×
553
    REALM_ASSERT(success);
×
554
}
×
555

556

557
bool ServerHistory::try_register_file_ident(file_ident_type file_ident, file_ident_type proxy_file_ident,
558
                                            ClientType client_type, salt_type& file_ident_salt)
559
{
×
560
    REALM_ASSERT(m_acc->upstream_status.is_attached());
×
561
    static_assert(g_root_node_file_ident == 1, "");
×
562
    if (REALM_UNLIKELY(file_ident < 2))
×
563
        return false;
×
564
    std::size_t max = std::numeric_limits<std::size_t>::max();
×
565
    if (REALM_UNLIKELY(std::uint_fast64_t(file_ident) > max))
×
566
        throw util::overflow_error{"Client file index"};
×
567
    std::size_t file_index = std::size_t(file_ident);
×
568
    if (file_index < m_num_client_files)
×
569
        return false;
×
570
    file_ident_salt = register_client_file_by_index(file_index, proxy_file_ident, client_type); // Throws
×
571
    return true;
×
572
}
×
573

574

575
auto ServerHistory::register_client_file_by_index(std::size_t file_index, file_ident_type proxy_file_ident,
576
                                                  ClientType client_type) -> salt_type
577
{
1,532✔
578
    REALM_ASSERT(file_index >= m_num_client_files);
1,532✔
579
    REALM_ASSERT(proxy_file_ident == 0 || is_valid_proxy_file_ident(proxy_file_ident));
1,532!
580
    bool generate_salt = is_direct_client(client_type);
1,532✔
581
    salt_type salt = 0;
1,532✔
582
    if (generate_salt) {
1,532✔
583
        auto max_salt = 0x0'7FFF'FFFF'FFFF'FFFF;
1,532✔
584
        std::mt19937_64& random = m_context.server_history_get_random();
1,532✔
585
        salt = std::uniform_int_distribution<salt_type>(1, max_salt)(random);
1,532✔
586
    }
1,532✔
587
    while (file_index > m_num_client_files)
1,532✔
588
        add_client_file(0, 0, ClientType::upstream);      // Throws
×
589
    add_client_file(salt, proxy_file_ident, client_type); // Throws
1,532✔
590
    return salt;
1,532✔
591
}
1,532✔
592

593

594
bool ServerHistory::ensure_upstream_file_ident(file_ident_type file_ident)
595
{
×
596
    REALM_ASSERT(m_acc->upstream_status.is_attached());
×
597

598
    static_assert(g_root_node_file_ident == 1, "");
×
599
    if (REALM_UNLIKELY(file_ident < 2))
×
600
        return (file_ident == 1);
×
601
    std::size_t max = std::numeric_limits<std::size_t>::max();
×
602
    if (REALM_UNLIKELY(std::uint_fast64_t(file_ident) > max))
×
603
        throw util::overflow_error{"Client file index"};
×
604
    std::size_t file_index = std::size_t(file_ident);
×
605
    if (REALM_LIKELY(file_index < m_num_client_files)) {
×
606
        auto client_type = m_acc->cf_client_types.get(file_index);
×
607
        if (REALM_UNLIKELY(client_type != int(ClientType::upstream)))
×
608
            return false;
×
609
        REALM_ASSERT(m_acc->cf_ident_salts.get(file_index) == 0);
×
610
        REALM_ASSERT(m_acc->cf_proxy_files.get(file_index) == 0);
×
611
        return true;
×
612
    }
×
613
    do
×
614
        add_client_file(0, 0, ClientType::upstream); // Throws
×
615
    while (file_index >= m_num_client_files);
×
616
    return true;
×
617
}
×
618

619

620
void ServerHistory::add_client_file(salt_type file_ident_salt, file_ident_type proxy_file_ident,
621
                                    ClientType client_type)
622
{
1,534✔
623
    switch (client_type) {
1,534✔
624
        case ClientType::upstream:
✔
625
        case ClientType::self:
✔
626
            REALM_ASSERT(file_ident_salt == 0);
×
627
            REALM_ASSERT(proxy_file_ident == 0);
×
628
            break;
×
629
        case ClientType::indirect:
✔
630
            REALM_ASSERT(file_ident_salt == 0);
×
631
            REALM_ASSERT(proxy_file_ident != 0);
×
632
            break;
×
633
        case ClientType::regular:
1,532✔
634
        case ClientType::subserver:
1,532✔
635
            REALM_ASSERT(file_ident_salt != 0);
1,532✔
636
            REALM_ASSERT(proxy_file_ident == 0);
1,532✔
637
            break;
1,532✔
638
        case ClientType::legacy:
622✔
639
            REALM_ASSERT(false);
×
640
            break;
×
641
    }
1,532✔
642
    std::int_fast64_t client_version = 0;
1,532✔
643
    std::int_fast64_t recip_hist_base_version = 0;
1,532✔
644
    ref_type recip_hist_ref = 0;
1,532✔
645
    std::int_fast64_t last_seen_timestamp = 0;
1,532✔
646
    std::int_fast64_t locked_server_version = 0;
1,532✔
647
    if (is_direct_client(client_type)) {
1,532✔
648
        last_seen_timestamp = 1;
1,532✔
649
    }
1,532✔
650
    m_acc->cf_ident_salts.insert(realm::npos, std::int_fast64_t(file_ident_salt));  // Throws
1,532✔
651
    m_acc->cf_client_versions.insert(realm::npos, client_version);                  // Throws
1,532✔
652
    m_acc->cf_rh_base_versions.insert(realm::npos, recip_hist_base_version);        // Throws
1,532✔
653
    m_acc->cf_recip_hist_refs.insert(realm::npos, recip_hist_ref);                  // Throws
1,532✔
654
    m_acc->cf_proxy_files.insert(realm::npos, std::int_fast64_t(proxy_file_ident)); // Throws
1,532✔
655
    m_acc->cf_client_types.insert(realm::npos, std::int_fast64_t(client_type));     // Throws
1,532✔
656
    m_acc->cf_last_seen_timestamps.insert(realm::npos, last_seen_timestamp);        // Throws
1,532✔
657
    m_acc->cf_locked_server_versions.insert(realm::npos, locked_server_version);    // Throws
1,532✔
658
    std::size_t max_size = std::numeric_limits<std::size_t>::max();
1,532✔
659
    if (m_num_client_files == max_size)
1,532✔
660
        throw util::overflow_error{"Client file index"};
×
661
    ++m_num_client_files;
1,532✔
662
}
1,532✔
663

664

665
void ServerHistory::save_upstream_sync_progress(const SyncProgress& progress)
666
{
×
667
    Array& us = m_acc->upstream_status;
×
668
    us.set(s_us_progress_download_server_version_iip,
×
669
           std::int_fast64_t(progress.download.server_version)); // Throws
×
670
    us.set(s_us_progress_download_client_version_iip,
×
671
           std::int_fast64_t(progress.download.last_integrated_client_version)); // Throws
×
672
    us.set(s_us_progress_latest_server_version_iip,
×
673
           std::int_fast64_t(progress.latest_server_version.version)); // Throws
×
674
    us.set(s_us_progress_latest_server_version_salt_iip,
×
675
           std::int_fast64_t(progress.latest_server_version.salt)); // Throws
×
676
    us.set(s_us_progress_upload_client_version_iip,
×
677
           std::int_fast64_t(progress.upload.client_version)); // Throws
×
678
    us.set(s_us_progress_upload_server_version_iip,
×
679
           std::int_fast64_t(progress.upload.last_integrated_server_version)); // Throws
×
680
}
×
681

682

683
auto ServerHistory::do_bootstrap_client_session(SaltedFileIdent client_file_ident, DownloadCursor download_progress,
684
                                                SaltedVersion server_version, ClientType client_type,
685
                                                UploadCursor& upload_progress, version_type& locked_server_version,
686
                                                Logger& logger) const noexcept -> BootstrapError
687
{
4,368✔
688
    REALM_ASSERT(is_direct_client(client_type));
4,368✔
689
    REALM_ASSERT(client_type != ClientType::legacy);
4,368✔
690

2,104✔
691
    // Validate `client_file_ident`
2,104✔
692
    if (!m_acc)
4,368✔
693
        return BootstrapError::bad_client_file_ident;
×
694
    {
4,368✔
695
        bool good =
4,368✔
696
            (client_file_ident.ident >= 1 && util::int_less_than(client_file_ident.ident, m_num_client_files));
4,368✔
697
        if (!good)
4,368✔
698
            return BootstrapError::bad_client_file_ident;
×
699
    }
4,368✔
700
    std::size_t client_file_index = std::size_t(client_file_ident.ident);
4,368✔
701
    {
4,368✔
702
        auto correct_salt = salt_type(m_acc->cf_ident_salts.get(client_file_index));
4,368✔
703
        bool good = (correct_salt != 0 && // Prevent (spoofed) match on special entries with no salt
4,368✔
704
                     client_file_ident.salt == correct_salt);
4,368✔
705
        if (!good)
4,368✔
706
            return BootstrapError::bad_client_file_ident_salt;
4✔
707
    }
4,364✔
708

2,102✔
709
    // Besides being superfluous, it is also a protocol violation if a client
2,102✔
710
    // asks to download from a point before the base of its reciprocal history.
2,102✔
711
    auto recip_hist_base_version = version_type(m_acc->cf_rh_base_versions.get(client_file_index));
4,364✔
712
    if (download_progress.server_version < recip_hist_base_version) {
4,364✔
713
        logger.debug("Bad download progress: %1 < %2", download_progress.server_version, recip_hist_base_version);
×
714
        return BootstrapError::bad_download_server_version;
×
715
    }
×
716

2,102✔
717
    // If the main history has been trimmed or compacted to a point beyond the
2,102✔
718
    // beginning of the reciprocal history, then the client file entry has
2,102✔
719
    // expired.
2,102✔
720
    //
2,102✔
721
    // NOTE: History trimming (removal of leading history entries) is currently
2,102✔
722
    // never done on server-side files.
2,102✔
723
    //
2,102✔
724
    // NOTE: For an overview of the in-place history compaction mechanism, see
2,102✔
725
    // `/doc/history_compaction.md` in the `realm-sync` Git repository.
2,102✔
726
    std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(client_file_index);
4,364✔
727
    bool expired_due_to_compaction = (last_seen_timestamp == 0);
4,364✔
728
    if (REALM_UNLIKELY(expired_due_to_compaction)) {
4,364✔
729
        logger.debug("Client expired because history has been compacted");
×
730
        return BootstrapError::client_file_expired;
×
731
    }
×
732

2,102✔
733
    REALM_ASSERT_RELEASE(recip_hist_base_version >= m_history_base_version);
4,364✔
734

2,102✔
735
    // Validate `download_progress`
2,102✔
736
    version_type current_server_version = get_server_version();
4,364✔
737
    if (download_progress.server_version > current_server_version)
4,364✔
738
        return BootstrapError::bad_download_server_version;
×
739
    auto last_integrated_client_version = version_type(m_acc->cf_client_versions.get(client_file_index));
4,364✔
740
    if (download_progress.last_integrated_client_version > last_integrated_client_version)
4,364✔
741
        return BootstrapError::bad_download_client_version;
4✔
742

2,100✔
743
    // Validate `server_version`
2,100✔
744
    {
4,360✔
745
        bool good = (server_version.version >= download_progress.server_version &&
4,360✔
746
                     server_version.version <= current_server_version);
4,360✔
747
        if (!good)
4,360✔
748
            return BootstrapError::bad_server_version;
×
749
    }
4,360✔
750
    {
4,360✔
751
        salt_type correct_salt = get_server_version_salt(server_version.version);
4,360✔
752
        bool good = (server_version.salt == correct_salt);
4,360✔
753
        if (!good)
4,360✔
754
            return BootstrapError::bad_server_version_salt;
4✔
755
    }
4,356✔
756

2,098✔
757
    // Validate client type
2,098✔
758
    {
4,356✔
759
        auto client_type_2 = ClientType(m_acc->cf_client_types.get(client_file_index));
4,356✔
760
        bool good = (client_type_2 == ClientType::legacy || client_type == client_type_2);
4,356✔
761
        if (!good)
4,356✔
762
            return BootstrapError::bad_client_type;
×
763
    }
4,356✔
764

2,098✔
765
    upload_progress.client_version = last_integrated_client_version;
4,356✔
766
    upload_progress.last_integrated_server_version = recip_hist_base_version;
4,356✔
767
    locked_server_version = version_type(m_acc->cf_locked_server_versions.get(client_file_index));
4,356✔
768
    return BootstrapError::no_error;
4,356✔
769
}
4,356✔
770

771

772
auto ServerHistory::bootstrap_client_session(SaltedFileIdent client_file_ident, DownloadCursor download_progress,
773
                                             SaltedVersion server_version, ClientType client_type,
774
                                             UploadCursor& upload_progress, version_type& locked_server_version,
775
                                             Logger& logger) const -> BootstrapError
776
{
4,368✔
777
    TransactionRef tr = m_db->start_read(); // Throws
4,368✔
778
    auto realm_version = tr->get_version();
4,368✔
779
    const_cast<ServerHistory*>(this)->set_group(tr.get());
4,368✔
780
    ensure_updated(realm_version); // Throws
4,368✔
781

2,104✔
782
    BootstrapError error = do_bootstrap_client_session(client_file_ident, download_progress, server_version,
4,368✔
783
                                                       client_type, upload_progress, locked_server_version, logger);
4,368✔
784
    return error;
4,368✔
785
}
4,368✔
786

787

788
bool ServerHistory::fetch_download_info(file_ident_type client_file_ident, DownloadCursor& download_progress,
789
                                        version_type end_version, UploadCursor& upload_progress,
790
                                        HistoryEntryHandler& handler,
791
                                        std::uint_fast64_t& cumulative_byte_size_current,
792
                                        std::uint_fast64_t& cumulative_byte_size_total,
793
                                        bool disable_download_compaction,
794
                                        std::size_t accum_byte_size_soft_limit) const
795
{
38,074✔
796
    REALM_ASSERT(client_file_ident != 0);
38,074✔
797
    REALM_ASSERT(download_progress.server_version <= end_version);
38,074✔
798

21,166✔
799
    TransactionRef tr = m_db->start_read(); // Throws
38,074✔
800
    version_type realm_version = tr->get_version();
38,074✔
801
    const_cast<ServerHistory*>(this)->set_group(tr.get());
38,074✔
802
    ensure_updated(realm_version); // Throws
38,074✔
803

21,166✔
804
    REALM_ASSERT(download_progress.server_version >= m_history_base_version);
38,074✔
805

21,166✔
806
    std::size_t client_file_index = std::size_t(client_file_ident);
38,074✔
807
    {
38,074✔
808
        auto client_type = ClientType(m_acc->cf_client_types.get(client_file_index));
38,074✔
809
        REALM_ASSERT_RELEASE(is_direct_client(client_type));
38,074✔
810
        std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(client_file_index);
38,074✔
811
        bool expired = (last_seen_timestamp == 0);
38,074✔
812
        if (REALM_UNLIKELY(expired))
38,074✔
813
            return false;
21,166✔
814
    }
38,074✔
815

21,166✔
816
    std::size_t accum_byte_size = 0;
38,074✔
817
    DownloadCursor download_progress_2 = download_progress;
38,074✔
818

21,166✔
819
    std::vector<Changeset> changesets;
38,074✔
820
    std::vector<std::size_t> original_changeset_sizes;
38,074✔
821
    if (!disable_download_compaction) {
38,074✔
822
        std::size_t reserve = to_size_t(end_version - download_progress_2.server_version);
38,074✔
823
        changesets.reserve(reserve);               // Throws
38,074✔
824
        original_changeset_sizes.reserve(reserve); // Throws
38,074✔
825
    }
38,074✔
826

21,166✔
827
    for (;;) {
78,942✔
828
        version_type begin_version = download_progress_2.server_version;
78,942✔
829
        HistoryEntry entry;
78,942✔
830
        version_type version = find_history_entry(client_file_ident, begin_version, end_version, entry,
78,942✔
831
                                                  download_progress_2.last_integrated_client_version);
78,942✔
832
        if (version == 0) {
78,942✔
833
            // End of history reached
21,146✔
834
            download_progress_2.server_version = end_version;
38,036✔
835
            break;
38,036✔
836
        }
38,036✔
837

21,224✔
838
        download_progress_2.server_version = version;
40,906✔
839

21,224✔
840
        entry.remote_version = download_progress_2.last_integrated_client_version;
40,906✔
841

21,224✔
842
        if (entry.origin_file_ident == 0)
40,906✔
843
            entry.origin_file_ident = m_local_file_ident;
4,800✔
844

21,224✔
845
        if (!disable_download_compaction) {
40,908✔
846
            ChunkedBinaryInputStream stream{entry.changeset};
40,906✔
847
            Changeset changeset;
40,906✔
848
            parse_changeset(stream, changeset); // Throws
40,906✔
849
            changeset.version = download_progress_2.server_version;
40,906✔
850
            changeset.last_integrated_remote_version = entry.remote_version;
40,906✔
851
            changeset.origin_timestamp = entry.origin_timestamp;
40,906✔
852
            changeset.origin_file_ident = entry.origin_file_ident;
40,906✔
853
            changesets.push_back(std::move(changeset));                 // Throws
40,906✔
854
            original_changeset_sizes.push_back(entry.changeset.size()); // Throws
40,906✔
855
        }
40,906✔
856
        else {
2,147,483,649✔
857
            handler.handle(download_progress_2.server_version, entry, entry.changeset.size()); // Throws
2,147,483,649✔
858
        }
2,147,483,649✔
859

21,224✔
860
        accum_byte_size += entry.changeset.size();
40,906✔
861

21,224✔
862
        if (accum_byte_size > accum_byte_size_soft_limit)
40,906✔
863
            break;
36✔
864
    }
40,906✔
865

21,166✔
866
    if (!disable_download_compaction) {
38,074✔
867
        compact_changesets(changesets.data(), changesets.size());
38,072✔
868

21,164✔
869
        ChangesetEncoder::Buffer encode_buffer;
38,072✔
870
        for (std::size_t i = 0; i < changesets.size(); ++i) {
78,976✔
871
            auto& changeset = changesets[i];
40,904✔
872
            encode_changeset(changeset, encode_buffer); // Throws
40,904✔
873
            HistoryEntry entry;
40,904✔
874
            entry.remote_version = changeset.last_integrated_remote_version;
40,904✔
875
            entry.origin_file_ident = changeset.origin_file_ident;
40,904✔
876
            entry.origin_timestamp = changeset.origin_timestamp;
40,904✔
877
            entry.changeset = BinaryData{encode_buffer.data(), encode_buffer.size()};
40,904✔
878
            handler.handle(changeset.version, entry, original_changeset_sizes[i]); // Throws
40,904✔
879
            encode_buffer.clear();
40,904✔
880
        }
40,904✔
881
    }
38,072✔
882

21,166✔
883
    // Set cumulative byte sizes.
21,166✔
884
    std::int_fast64_t cumulative_byte_size_current_2 = 0;
38,074✔
885
    std::int_fast64_t cumulative_byte_size_total_2 = 0;
38,074✔
886
    if (download_progress_2.server_version > m_history_base_version) {
38,074✔
887
        std::size_t begin_ndx = to_size_t(download_progress_2.server_version - m_history_base_version) - 1;
36,662✔
888
        cumulative_byte_size_current_2 = m_acc->sh_cumul_byte_sizes.get(begin_ndx);
36,662✔
889
        REALM_ASSERT(cumulative_byte_size_current_2 >= 0);
36,662✔
890
    }
36,662✔
891
    if (m_history_size > 0) {
38,074✔
892
        std::size_t end_ndx = m_history_size - 1;
36,666✔
893
        cumulative_byte_size_total_2 = m_acc->sh_cumul_byte_sizes.get(end_ndx);
36,666✔
894
    }
36,666✔
895
    REALM_ASSERT(cumulative_byte_size_current_2 <= cumulative_byte_size_total_2);
38,074✔
896

21,166✔
897
    version_type upload_client_version = version_type(m_acc->cf_client_versions.get(client_file_index));
38,074✔
898
    version_type upload_server_version = version_type(m_acc->cf_rh_base_versions.get(client_file_index));
38,074✔
899

21,166✔
900
    download_progress = download_progress_2;
38,074✔
901
    cumulative_byte_size_current = std::uint_fast64_t(cumulative_byte_size_current_2);
38,074✔
902
    cumulative_byte_size_total = std::uint_fast64_t(cumulative_byte_size_total_2);
38,074✔
903
    upload_progress = UploadCursor{upload_client_version, upload_server_version};
38,074✔
904

21,166✔
905
    return true;
38,074✔
906
}
38,074✔
907

908

909
void ServerHistory::add_upstream_sync_status()
910
{
×
911
    TransactionRef tr = m_db->start_write(); // Throws
×
912
    version_type realm_version = tr->get_version();
×
913
    ensure_updated(realm_version); // Throws
×
914
    prepare_for_write();           // Throws
×
915

916
    REALM_ASSERT(!m_acc->upstream_status.is_attached());
×
917
    REALM_ASSERT(m_local_file_ident == g_root_node_file_ident);
×
918

919
    // An upstream status cannot be added to a file from which new client file
920
    // identifiers have already been allocated, since in a star topology server
921
    // cluster, all file identifiers must be allocated by the root node.
922
    static_assert(g_root_node_file_ident == 1, "");
×
923
    if (REALM_UNLIKELY(m_num_client_files > 2)) {
×
924
        throw util::runtime_error("Realm file has registered client file identifiers, "
×
925
                                  "so can no longer be associated with upstream server "
×
926
                                  "(star topology server cluster)");
×
927
    }
×
928

929
    bool context_flag_no = false;
×
930
    std::size_t size = s_upstream_status_size;
×
931
    m_acc->upstream_status.create(Array::type_Normal, context_flag_no, size); // Throws
×
932
    _impl::ShallowArrayDestroyGuard adg{&m_acc->upstream_status};
×
933
    m_acc->upstream_status.update_parent(); // Throws
×
934
    adg.release();                          // Ref ownership transferred to parent array
×
935
    tr->commit();                           // Throws
×
936
}
×
937

938
std::vector<sync::Changeset> ServerHistory::get_parsed_changesets(version_type begin, version_type end) const
939
{
×
940
    TransactionRef rt = m_db->start_read(); // Throws
×
941
    version_type realm_version = rt->get_version();
×
942
    const_cast<ServerHistory*>(this)->set_group(rt.get());
×
943
    ensure_updated(realm_version);
×
944

945
    REALM_ASSERT(begin > m_history_base_version);
×
946
    if (end == version_type(-1)) {
×
947
        end = m_history_base_version + m_history_size + 1;
×
948
    }
×
949
    REALM_ASSERT(begin <= end);
×
950

951
    std::vector<sync::Changeset> changesets;
×
952
    changesets.reserve(std::size_t(end - begin)); // Throws
×
953
    for (version_type version = begin; version < end; ++version) {
×
954
        std::size_t ndx = std::size_t(version - m_history_base_version - 1);
×
955
        Changeset changeset;
×
956

957
        auto binary = ChunkedBinaryData{m_acc->sh_changesets, ndx};
×
958
        ChunkedBinaryInputStream stream{binary};
×
959
        parse_changeset(stream, changeset); // Throws
×
960

961
        // Add the attributes for the changeset.
962
        changeset.last_integrated_remote_version = m_acc->sh_client_versions.get(ndx);
×
963
        changeset.origin_file_ident = m_acc->sh_origin_files.get(ndx);
×
964
        changeset.origin_timestamp = m_acc->sh_timestamps.get(ndx);
×
965
        changeset.version = version;
×
966
        changesets.emplace_back(std::move(changeset));
×
967
    }
×
968
    return changesets;
×
969
}
×
970

971

972
class ServerHistory::ReciprocalHistory : private ArrayParent {
973
public:
974
    ReciprocalHistory(BPlusTree<ref_type>& cf_recip_hist_refs, std::size_t remote_file_index,
975
                      version_type base_version)
976
        : m_cf_recip_hist_refs{cf_recip_hist_refs}
977
        , m_remote_file_index{remote_file_index}
978
        , m_base_version{base_version}
979
    {
38,620✔
980
        if (ref_type ref = to_ref(cf_recip_hist_refs.get(remote_file_index))) {
38,620✔
981
            init(ref);                     // Throws
32,218✔
982
            m_size = m_changesets->size(); // Relatively expensive
32,218✔
983
        }
32,218✔
984
    }
38,620✔
985

986
    std::size_t remote_file_index() const noexcept
987
    {
38,598✔
988
        return m_remote_file_index;
38,598✔
989
    }
38,598✔
990

991
    version_type base_version() const noexcept
992
    {
38,598✔
993
        return m_base_version;
38,598✔
994
    }
38,598✔
995

996
    std::size_t size() const noexcept
997
    {
×
998
        return m_size;
×
999
    }
×
1000

1001
    // Returns true iff the reciprocal history has been instantiated
1002
    explicit operator bool() const noexcept
1003
    {
×
1004
        return bool(m_changesets);
×
1005
    }
×
1006

1007
    void ensure_instantiated()
1008
    {
18,768✔
1009
        if (m_changesets)
18,768✔
1010
            return;
17,926✔
1011

354✔
1012
        // Instantiate the reciprocal history
354✔
1013
        Allocator& alloc = m_cf_recip_hist_refs.get_alloc();
842✔
1014
        BinaryColumn recip_hist(alloc);
842✔
1015
        recip_hist.create();
842✔
1016
        auto ref = recip_hist.get_ref();
842✔
1017
        DeepArrayRefDestroyGuard adg{ref, alloc};
842✔
1018
        m_cf_recip_hist_refs.set(m_remote_file_index, ref); // Throws
842✔
1019
        adg.release();                                      // Ref ownership transferred to parent array
842✔
1020
        init(ref);                                          // Throws
842✔
1021
    }
842✔
1022

1023
    // The reciprocal history must have been instantiated (see
1024
    // ensure_instantiated()).
1025
    bool get(version_type server_version, ChunkedBinaryData& transform) const noexcept
1026
    {
52,982✔
1027
        REALM_ASSERT(m_changesets);
52,982✔
1028
        REALM_ASSERT(server_version > m_base_version);
52,982✔
1029

33,376✔
1030
        std::size_t i = std::size_t(server_version - m_base_version - 1);
52,982✔
1031
        if (i < m_size) {
52,982✔
1032
            ChunkedBinaryData transform_2{*m_changesets, i};
7,074✔
1033
            if (!transform_2.is_null()) {
7,074✔
1034
                transform = transform_2;
2,256✔
1035
                return true;
2,256✔
1036
            }
2,256✔
1037
        }
50,726✔
1038
        return false;
50,726✔
1039
    }
50,726✔
1040

1041
    // The reciprocal history must have been instantiated (see
1042
    // ensure_instantiated()).
1043
    void set(version_type server_version, BinaryData transform)
1044
    {
2,526✔
1045
        REALM_ASSERT(m_changesets);
2,526✔
1046
        REALM_ASSERT(server_version > m_base_version);
2,526✔
1047
        std::size_t i = std::size_t(server_version - m_base_version - 1);
2,526✔
1048
        while (m_size <= i) {
13,342✔
1049
            m_changesets->add({}); // Throws
10,816✔
1050
            m_size++;
10,816✔
1051
        }
10,816✔
1052
        // FIXME: BinaryColumn::set() currently interprets BinaryData(0,0) as
1,322✔
1053
        // null. It should probably be changed such that BinaryData(0,0) is
1,322✔
1054
        // always interpreted as the empty string. For the purpose of setting
1,322✔
1055
        // null values, BinaryColumn::set() should accept values of type
1,322✔
1056
        // util::Optional<BinaryData>().
1,322✔
1057
        BinaryData transform_2 = (transform.is_null() ? BinaryData{"", 0} : transform);
2,414✔
1058
        m_changesets->set(i, transform_2); // Throws
2,526✔
1059
    }
2,526✔
1060

1061
    // Requires that new_base_version > base_version()
1062
    void trim(version_type new_base_version)
1063
    {
27,590✔
1064
        REALM_ASSERT(new_base_version > m_base_version);
27,590✔
1065
        std::size_t n = std::size_t(new_base_version - m_base_version);
27,590✔
1066
        if (n >= m_size) {
27,590✔
1067
            if (m_changesets)
27,260✔
1068
                m_changesets->clear(); // Throws
22,796✔
1069
            m_base_version = new_base_version;
27,260✔
1070
            m_size = 0;
27,260✔
1071
            return;
27,260✔
1072
        }
27,260✔
1073
        REALM_ASSERT(m_changesets);
330✔
1074
        while (n) {
7,830✔
1075
            m_changesets->erase(0);
7,500✔
1076
            --n;
7,500✔
1077
        }
7,500✔
1078
        m_base_version = new_base_version;
330✔
1079
        m_size -= n;
330✔
1080
    }
330✔
1081

1082
    void update_child_ref(std::size_t child_ndx, ref_type new_ref) override final
1083
    {
508✔
1084
        m_cf_recip_hist_refs.set(child_ndx, new_ref); // Throws
508✔
1085
    }
508✔
1086

1087
    ref_type get_child_ref(std::size_t child_ndx) const noexcept override final
1088
    {
×
1089
        return m_cf_recip_hist_refs.get(child_ndx);
×
1090
    }
×
1091

1092
private:
1093
    BPlusTree<ref_type>& m_cf_recip_hist_refs;
1094
    const std::size_t m_remote_file_index;
1095
    version_type m_base_version;
1096
    std::size_t m_size = 0;
1097
    util::Optional<BinaryColumn> m_changesets;
1098

1099
    void init(ref_type ref)
1100
    {
33,060✔
1101
        Allocator& alloc = m_cf_recip_hist_refs.get_alloc();
33,060✔
1102
        m_changesets.emplace(alloc); // Throws
33,060✔
1103
        m_changesets->init_from_ref(ref);
33,060✔
1104
        m_changesets->set_parent(this, m_remote_file_index);
33,060✔
1105
    }
33,060✔
1106
};
1107

1108

1109
class ServerHistory::TransformHistoryImpl : public TransformHistory {
1110
public:
1111
    TransformHistoryImpl(file_ident_type remote_file_ident, ServerHistory& history,
1112
                         ReciprocalHistory& recip_hist) noexcept
1113
        : m_remote_file_ident{remote_file_ident}
1114
        , m_history{history}
1115
        , m_recip_hist{recip_hist}
1116
    {
18,764✔
1117
    }
18,764✔
1118

1119
    version_type find_history_entry(version_type begin_version, version_type end_version,
1120
                                    HistoryEntry& entry) const noexcept override final
1121
    {
101,642✔
1122
        return m_history.find_history_entry(m_remote_file_ident, begin_version, end_version, entry);
101,642✔
1123
    }
101,642✔
1124

1125
    ChunkedBinaryData get_reciprocal_transform(version_type server_version,
1126
                                               bool& is_compressed) const noexcept override final
1127
    {
52,980✔
1128
        is_compressed = false;
52,980✔
1129
        ChunkedBinaryData transform;
52,980✔
1130
        if (m_recip_hist.get(server_version, transform))
52,980✔
1131
            return transform;
2,256✔
1132
        HistoryEntry entry = m_history.get_history_entry(server_version);
50,724✔
1133
        return entry.changeset;
50,724✔
1134
    }
50,724✔
1135

1136
    void set_reciprocal_transform(version_type server_version, BinaryData transform) override final
1137
    {
2,526✔
1138
        m_recip_hist.set(server_version, transform); // Throws
2,526✔
1139
    }
2,526✔
1140

1141
private:
1142
    const file_ident_type m_remote_file_ident; // Zero for server
1143
    ServerHistory& m_history;
1144
    ReciprocalHistory& m_recip_hist;
1145
};
1146

1147

1148
bool ServerHistory::integrate_remote_changesets(file_ident_type remote_file_ident, UploadCursor upload_progress,
1149
                                                version_type locked_server_version, const RemoteChangeset* changesets,
1150
                                                std::size_t num_changesets, util::Logger& logger)
1151
{
38,618✔
1152
    std::size_t remote_file_index = std::size_t(remote_file_ident);
38,618✔
1153
    REALM_ASSERT(remote_file_index < m_num_client_files);
38,618✔
1154
    bool from_downstream = (remote_file_ident != 0);
38,618✔
1155
    if (from_downstream) {
38,618✔
1156
        auto client_type = ClientType(m_acc->cf_client_types.get(remote_file_index));
38,618✔
1157
        REALM_ASSERT_RELEASE(is_direct_client(client_type));
38,618✔
1158
        std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(remote_file_index);
38,618✔
1159
        bool expired = (last_seen_timestamp == 0);
38,618✔
1160
        REALM_ASSERT_RELEASE(!expired);
38,618✔
1161
    }
38,618✔
1162
    version_type orig_client_version = version_type(m_acc->cf_client_versions.get(remote_file_index));
38,618✔
1163
    version_type recip_hist_base_version = version_type(m_acc->cf_rh_base_versions.get(remote_file_index));
38,618✔
1164
    ReciprocalHistory recip_hist(m_acc->cf_recip_hist_refs, remote_file_index,
38,618✔
1165
                                 recip_hist_base_version); // Throws
38,618✔
1166

20,058✔
1167
    {
38,618✔
1168
        UploadCursor prev_upload_cursor = {orig_client_version, recip_hist_base_version};
38,618✔
1169
        for (std::size_t i = 0; i < num_changesets; ++i) {
72,996✔
1170
            // Note: remote_file_ident may be different from
16,770✔
1171
            // changeset.origin_file_ident in a cluster setup.
16,770✔
1172
            REALM_ASSERT(changesets[i].origin_file_ident > 0);
34,378✔
1173
            UploadCursor upload_cursor = {changesets[i].remote_version, changesets[i].last_integrated_local_version};
34,378✔
1174
            REALM_ASSERT(upload_cursor.client_version > prev_upload_cursor.client_version);
34,378✔
1175
            REALM_ASSERT(are_mutually_consistent(upload_cursor, prev_upload_cursor));
34,378✔
1176
            prev_upload_cursor = upload_cursor;
34,378✔
1177
        }
34,378✔
1178
    }
38,618✔
1179

20,058✔
1180
    if (num_changesets > 0) {
38,618✔
1181
        recip_hist.ensure_instantiated(); // Throws
18,768✔
1182

10,314✔
1183
        // Parse the changesets
10,314✔
1184
        std::vector<Changeset> parsed_transformed_changesets;
18,768✔
1185
        parsed_transformed_changesets.resize(num_changesets);
18,768✔
1186
        for (std::size_t i = 0; i < num_changesets; ++i)
53,146✔
1187
            parse_remote_changeset(changesets[i], parsed_transformed_changesets[i]); // Throws
34,378✔
1188

10,314✔
1189
        // Transform the changesets
10,314✔
1190
        version_type current_server_version = get_server_version();
18,768✔
1191
        Group& group = *m_group;
18,768✔
1192
        Transaction& transaction = dynamic_cast<Transaction&>(group);
18,768✔
1193
        auto apply = [&](const Changeset* c) -> bool {
34,358✔
1194
            TempShortCircuitReplication tdr{*this}; // Short-circuit while integrating changes
34,358✔
1195
            InstructionApplier applier{transaction};
34,358✔
1196
            applier.apply(*c, &logger);
34,358✔
1197
            reset(); // Reset the instruction encoder
34,358✔
1198
            return true;
34,358✔
1199
        };
34,358✔
1200
        // Merge with causally unrelated changesets, and resolve the
10,314✔
1201
        // conflicts if there are any.
10,314✔
1202
        TransformHistoryImpl transform_hist{remote_file_ident, *this, recip_hist};
18,768✔
1203
        Transformer& transformer = m_context.get_transformer(); // Throws
18,768✔
1204
        transformer.transform_remote_changesets(transform_hist, m_local_file_ident, current_server_version,
18,768✔
1205
                                                parsed_transformed_changesets, std::move(apply),
18,768✔
1206
                                                logger); // Throws
18,768✔
1207

10,314✔
1208
        for (std::size_t i = 0; i < num_changesets; ++i) {
53,126✔
1209
            REALM_ASSERT(get_instruction_encoder().buffer().size() == 0);
34,358✔
1210
            const Changeset& changeset = parsed_transformed_changesets[i];
34,358✔
1211

16,760✔
1212
            HistoryEntry entry;
34,358✔
1213
            entry.origin_timestamp = changeset.origin_timestamp;
34,358✔
1214
            entry.origin_file_ident = changeset.origin_file_ident;
34,358✔
1215
            entry.remote_version = changeset.version;
34,358✔
1216

16,760✔
1217
            ChangesetEncoder::Buffer changeset_buffer;
34,358✔
1218

16,760✔
1219
            encode_changeset(parsed_transformed_changesets[i], changeset_buffer); // Throws
34,358✔
1220
            entry.changeset = BinaryData{changeset_buffer.data(), changeset_buffer.size()};
34,358✔
1221

16,760✔
1222
            add_sync_history_entry(entry); // Throws
34,358✔
1223
        }
34,358✔
1224
    }
18,768✔
1225

20,058✔
1226
    bool dirty = (num_changesets > 0);
38,618✔
1227

20,058✔
1228
    if (update_upload_progress(orig_client_version, recip_hist, upload_progress)) // Throws
38,618✔
1229
        dirty = true;
38,600✔
1230

20,058✔
1231
    if (from_downstream) {
38,618✔
1232
        version_type orig_version = version_type(m_acc->cf_locked_server_versions.get(remote_file_index));
38,600✔
1233
        if (locked_server_version > orig_version) {
38,600✔
1234
            m_acc->cf_locked_server_versions.set(remote_file_index,
27,294✔
1235
                                                 std::int_fast64_t(locked_server_version)); // Throws
27,294✔
1236
            dirty = true;
27,294✔
1237
        }
27,294✔
1238
    }
38,600✔
1239

20,058✔
1240
    if (from_downstream && dirty) {
38,618✔
1241
        m_acc->cf_last_seen_timestamps.set(remote_file_index, 1);
38,600✔
1242
    }
38,600✔
1243

20,058✔
1244
    return dirty;
38,618✔
1245
}
38,618✔
1246

1247

1248
bool ServerHistory::update_upload_progress(version_type orig_client_version, ReciprocalHistory& recip_hist,
1249
                                           UploadCursor upload_progress)
1250
{
38,598✔
1251
    UploadCursor orig_upload_progress = {orig_client_version, recip_hist.base_version()};
38,598✔
1252
    REALM_ASSERT(upload_progress.client_version >= orig_upload_progress.client_version);
38,598✔
1253
    REALM_ASSERT(are_mutually_consistent(upload_progress, orig_upload_progress));
38,598✔
1254
    std::size_t client_file_index = recip_hist.remote_file_index();
38,598✔
1255
    bool update_client_version = (upload_progress.client_version > orig_upload_progress.client_version);
38,598✔
1256
    if (update_client_version) {
38,598✔
1257
        auto value_1 = std::int_fast64_t(upload_progress.client_version);
38,598✔
1258
        m_acc->cf_client_versions.set(client_file_index, value_1); // Throws
38,598✔
1259
        bool update_server_version =
38,598✔
1260
            (upload_progress.last_integrated_server_version > orig_upload_progress.last_integrated_server_version);
38,598✔
1261
        if (update_server_version) {
38,598✔
1262
            recip_hist.trim(upload_progress.last_integrated_server_version); // Throws
27,590✔
1263
            auto value_2 = std::int_fast64_t(upload_progress.last_integrated_server_version);
27,590✔
1264
            m_acc->cf_rh_base_versions.set(client_file_index, value_2); // Throws
27,590✔
1265
        }
27,590✔
1266
        return true;
38,598✔
1267
    }
38,598✔
UNCOV
1268
    return false;
×
UNCOV
1269
}
×
1270

1271

1272
// Overriding member in Replication
1273
void ServerHistory::initialize(DB& sg)
1274
{
2,184✔
1275
    REALM_ASSERT(!m_db);
2,184✔
1276
    SyncReplication::initialize(sg); // Throws
2,184✔
1277
    m_db = &sg;
2,184✔
1278
}
2,184✔
1279

1280

1281
// Overriding member in Replication
1282
auto ServerHistory::get_history_type() const noexcept -> HistoryType
1283
{
3,976✔
1284
    return hist_SyncServer;
3,976✔
1285
}
3,976✔
1286

1287

1288
// Overriding member in Replication
1289
int ServerHistory::get_history_schema_version() const noexcept
1290
{
2,168✔
1291
    return get_server_history_schema_version();
2,168✔
1292
}
2,168✔
1293

1294

1295
// Overriding member in Replication
1296
bool ServerHistory::is_upgradable_history_schema(int stored_schema_version) const noexcept
1297
{
×
1298
    if (stored_schema_version >= 20) {
×
1299
        return true;
×
1300
    }
×
1301
    return false;
×
1302
}
×
1303

1304

1305
// Overriding member in Replication
1306
void ServerHistory::upgrade_history_schema(int stored_schema_version)
1307
{
×
1308
    // upgrade_history_schema() is called only when there is a need to upgrade
1309
    // (`stored_schema_version < get_server_history_schema_version()`), and only
1310
    // when is_upgradable_history_schema() returned true (`stored_schema_version
1311
    // >= 1`).
1312
    REALM_ASSERT(stored_schema_version < get_server_history_schema_version());
×
1313
    REALM_ASSERT(stored_schema_version >= 1);
×
1314
    int orig_schema_version = stored_schema_version;
×
1315
    int schema_version = orig_schema_version;
×
1316
    // NOTE: Future migration steps go here.
1317

1318
    REALM_ASSERT(schema_version == get_server_history_schema_version());
×
1319

1320
    // Record migration event
1321
    record_current_schema_version(); // Throws
×
1322
}
×
1323

1324

1325
// Overriding member in Replication
1326
_impl::History* ServerHistory::_get_history_write()
1327
{
83,908✔
1328
    return this;
83,908✔
1329
}
83,908✔
1330

1331
// Overriding member in Replication
1332
std::unique_ptr<_impl::History> ServerHistory::_create_history_read()
1333
{
16✔
1334
    auto server_hist = std::make_unique<ServerHistory>(m_context);                     // Throws
16✔
1335
    server_hist->initialize(*m_db);                                                    // Throws
16✔
1336
    return std::unique_ptr<_impl::History>(server_hist.release());
16✔
1337
}
16✔
1338

1339

1340
// Overriding member in Replication
1341
auto ServerHistory::prepare_changeset(const char* data, std::size_t size, version_type realm_version) -> version_type
1342
{
41,936✔
1343
    ensure_updated(realm_version);
41,936✔
1344
    prepare_for_write(); // Throws
41,936✔
1345

21,586✔
1346
    bool nonempty_changeset_of_local_origin = (m_is_local_changeset && size != 0);
41,936✔
1347

21,586✔
1348
    if (nonempty_changeset_of_local_origin) {
41,936✔
1349
        auto& buffer = get_instruction_encoder().buffer();
4,812✔
1350
        BinaryData changeset{buffer.data(), buffer.size()};
4,812✔
1351
        HistoryEntry entry;
4,812✔
1352
        entry.origin_timestamp = sync::generate_changeset_timestamp();
4,812✔
1353
        entry.origin_file_ident = 0; // Of local origin
4,812✔
1354
        entry.remote_version = 0;    // Of local origin on server-side
4,812✔
1355
        entry.changeset = changeset;
4,812✔
1356

2,406✔
1357
        add_sync_history_entry(entry); // Throws
4,812✔
1358
    }
4,812✔
1359

21,586✔
1360
    // Add the standard ct changeset.
21,586✔
1361
    // This is done for changes of both local and remote origin.
21,586✔
1362
    BinaryData core_changeset{data, size};
41,936✔
1363
    add_core_history_entry(core_changeset); // Thows
41,936✔
1364

21,586✔
1365
    return m_ct_base_version + m_ct_history_size; // New snapshot number
41,936✔
1366
}
41,936✔
1367

1368

1369
// Overriding member in _impl::History
1370
void ServerHistory::update_from_parent(version_type realm_version)
1371
{
90,274✔
1372
    using gf = _impl::GroupFriend;
90,274✔
1373
    ref_type ref = gf::get_history_ref(*m_group);
90,274✔
1374
    update_from_ref_and_version(ref, realm_version); // Throws
90,274✔
1375
}
90,274✔
1376

1377

1378
// Overriding member in _impl::History
1379
void ServerHistory::get_changesets(version_type begin_version, version_type end_version,
1380
                                   BinaryIterator* iterators) const noexcept
1381
{
×
1382
    REALM_ASSERT(begin_version <= end_version);
×
1383
    REALM_ASSERT(begin_version >= m_ct_base_version);
×
1384
    REALM_ASSERT(end_version <= m_ct_base_version + m_ct_history_size);
×
1385
    std::size_t n = to_size_t(end_version - begin_version);
×
1386
    REALM_ASSERT(n == 0 || m_acc);
×
1387
    std::size_t offset = to_size_t(begin_version - m_ct_base_version);
×
1388
    for (std::size_t i = 0; i < n; ++i) {
×
1389
        iterators[i] = BinaryIterator(&m_acc->ct_history, offset + i);
×
1390
    }
×
1391
}
×
1392

1393

1394
// Overriding member in _impl::History
1395
void ServerHistory::set_oldest_bound_version(version_type realm_version)
1396
{
41,936✔
1397
    REALM_ASSERT(realm_version >= m_version_of_oldest_bound_snapshot);
41,936✔
1398
    if (realm_version > m_version_of_oldest_bound_snapshot) {
41,936✔
1399
        m_version_of_oldest_bound_snapshot = realm_version;
41,936✔
1400
        trim_cont_transact_history(); // Throws
41,936✔
1401
    }
41,936✔
1402
}
41,936✔
1403

1404

1405
// Overriding member in _impl::History
1406
void ServerHistory::verify() const
1407
{
16✔
1408
#ifdef REALM_DEBUG
16✔
1409
    // The size of the continuous transactions history can only be zero when the
8✔
1410
    // Realm is in the initial empty state where top-ref is null.
8✔
1411
    version_type initial_realm_version = 1;
16✔
1412
    REALM_ASSERT(m_ct_history_size != 0 || m_ct_base_version == initial_realm_version);
16!
1413

8✔
1414
    if (!m_acc) {
16✔
1415
        REALM_ASSERT(m_local_file_ident == g_root_node_file_ident);
×
1416
        REALM_ASSERT(m_num_client_files == 0);
×
1417
        REALM_ASSERT(m_history_size == 0);
×
1418
        REALM_ASSERT(m_server_version_salt == 0);
×
1419
        REALM_ASSERT(m_history_base_version == 0);
×
1420
        REALM_ASSERT(m_ct_history_size == 0);
×
1421
        return;
×
1422
    }
×
1423

8✔
1424
    m_acc->root.verify();
16✔
1425
    m_acc->client_files.verify();
16✔
1426
    m_acc->sync_history.verify();
16✔
1427
    if (m_acc->upstream_status.is_attached())
16✔
1428
        m_acc->upstream_status.verify();
×
1429
    if (m_acc->partial_sync.is_attached())
16✔
1430
        m_acc->partial_sync.verify();
×
1431
    m_acc->cf_ident_salts.verify();
16✔
1432
    m_acc->cf_client_versions.verify();
16✔
1433
    m_acc->cf_rh_base_versions.verify();
16✔
1434
    m_acc->cf_recip_hist_refs.verify();
16✔
1435
    m_acc->cf_proxy_files.verify();
16✔
1436
    m_acc->cf_client_types.verify();
16✔
1437
    m_acc->cf_last_seen_timestamps.verify();
16✔
1438
    m_acc->cf_locked_server_versions.verify();
16✔
1439
    m_acc->sh_version_salts.verify();
16✔
1440
    m_acc->sh_origin_files.verify();
16✔
1441
    m_acc->sh_client_versions.verify();
16✔
1442
    m_acc->sh_timestamps.verify();
16✔
1443
    m_acc->sh_changesets.verify();
16✔
1444
    m_acc->sh_cumul_byte_sizes.verify();
16✔
1445
    m_acc->ct_history.verify();
16✔
1446

8✔
1447
    REALM_ASSERT(m_history_base_version == m_acc->root.get_as_ref_or_tagged(s_history_base_version_iip).get_as_int());
16✔
1448
    salt_type base_version_salt = m_acc->root.get_as_ref_or_tagged(s_base_version_salt_iip).get_as_int();
16✔
1449
    REALM_ASSERT((m_history_base_version == 0) == (base_version_salt == 0));
16✔
1450

8✔
1451
    REALM_ASSERT(m_acc->cf_ident_salts.size() == m_num_client_files);
16✔
1452
    REALM_ASSERT(m_acc->cf_client_versions.size() == m_num_client_files);
16✔
1453
    REALM_ASSERT(m_acc->cf_rh_base_versions.size() == m_num_client_files);
16✔
1454
    REALM_ASSERT(m_acc->cf_recip_hist_refs.size() == m_num_client_files);
16✔
1455
    REALM_ASSERT(m_acc->cf_proxy_files.size() == m_num_client_files);
16✔
1456
    REALM_ASSERT(m_acc->cf_client_types.size() == m_num_client_files);
16✔
1457
    REALM_ASSERT(m_acc->cf_last_seen_timestamps.size() == m_num_client_files);
16✔
1458
    REALM_ASSERT(m_acc->cf_locked_server_versions.size() == m_num_client_files);
16✔
1459

8✔
1460
    REALM_ASSERT(m_acc->sh_version_salts.size() == m_history_size);
16✔
1461
    REALM_ASSERT(m_acc->sh_origin_files.size() == m_history_size);
16✔
1462
    REALM_ASSERT(m_acc->sh_client_versions.size() == m_history_size);
16✔
1463
    REALM_ASSERT(m_acc->sh_timestamps.size() == m_history_size);
16✔
1464
    REALM_ASSERT(m_acc->sh_changesets.size() == m_history_size);
16✔
1465
    REALM_ASSERT(m_acc->sh_cumul_byte_sizes.size() == m_history_size);
16✔
1466

8✔
1467
    salt_type server_version_salt =
16✔
1468
        (m_history_size == 0 ? base_version_salt : salt_type(m_acc->sh_version_salts.get(m_history_size - 1)));
16✔
1469
    REALM_ASSERT(m_server_version_salt == server_version_salt);
16✔
1470

8✔
1471
    REALM_ASSERT(m_local_file_ident > 0 && std::uint_fast64_t(m_local_file_ident) < m_num_client_files);
16✔
1472

8✔
1473
    // Check history entries
8✔
1474
    std::int_fast64_t accum_byte_size = 0;
16✔
1475
    struct ClientFile {
16✔
1476
        version_type last_integrated_client_version;
16✔
1477
    };
16✔
1478
    std::unordered_map<file_ident_type, ClientFile> client_files;
16✔
1479
    for (std::size_t i = 0; i < m_history_size; ++i) {
44✔
1480
        auto salt = m_acc->sh_version_salts.get(i);
28✔
1481
        REALM_ASSERT(salt > 0 && salt <= 0x0'7FFF'FFFF'FFFF'FFFF);
28✔
1482
        file_ident_type origin_file_ident = 0;
28✔
1483
        REALM_ASSERT(!util::int_cast_with_overflow_detect(m_acc->sh_origin_files.get(i), origin_file_ident));
28✔
1484
        REALM_ASSERT(origin_file_ident != m_local_file_ident);
28✔
1485
        std::size_t origin_file_index = 0;
28✔
1486
        REALM_ASSERT(!util::int_cast_with_overflow_detect(origin_file_ident, origin_file_index));
28✔
1487
        REALM_ASSERT(origin_file_index < m_num_client_files);
28✔
1488
        version_type client_version = 0;
28✔
1489
        REALM_ASSERT(!util::int_cast_with_overflow_detect(m_acc->sh_client_versions.get(i), client_version));
28✔
1490
        bool of_local_origin = (origin_file_ident == 0);
28✔
1491
        if (of_local_origin) {
28✔
1492
            REALM_ASSERT(client_version == 0);
×
1493
        }
×
1494
        else {
28✔
1495
            file_ident_type client_file_ident = 0;
28✔
1496
            bool from_reference_file = (origin_file_ident == m_local_file_ident);
28✔
1497
            if (!from_reference_file) {
28✔
1498
                auto client_type = m_acc->cf_client_types.get(origin_file_index);
28✔
1499
                bool good_client_type = false;
28✔
1500
                switch (ClientType(client_type)) {
28✔
1501
                    case ClientType::upstream:
✔
1502
                        good_client_type = true;
×
1503
                        break;
×
1504
                    case ClientType::indirect: {
✔
1505
                        auto proxy_file = m_acc->cf_proxy_files.get(origin_file_index);
×
1506
                        REALM_ASSERT(!util::int_cast_with_overflow_detect(proxy_file, client_file_ident));
×
1507
                        good_client_type = true;
×
1508
                        break;
×
1509
                    }
×
1510
                    case ClientType::self:
✔
1511
                        break;
×
1512
                    case ClientType::legacy:
14✔
1513
                    case ClientType::regular:
28✔
1514
                    case ClientType::subserver:
28✔
1515
                        client_file_ident = origin_file_ident;
28✔
1516
                        good_client_type = true;
28✔
1517
                        break;
28✔
1518
                }
28✔
1519
                REALM_ASSERT(good_client_type);
28✔
1520
            }
28✔
1521
            ClientFile& client_file = client_files[client_file_ident];
28✔
1522
            if (from_reference_file) {
28✔
1523
                REALM_ASSERT(client_version >= client_file.last_integrated_client_version);
×
1524
            }
×
1525
            else {
28✔
1526
                REALM_ASSERT(client_version > client_file.last_integrated_client_version);
28✔
1527
            }
28✔
1528
            client_file.last_integrated_client_version = client_version;
28✔
1529
        }
28✔
1530

14✔
1531
        std::size_t changeset_size = ChunkedBinaryData(m_acc->sh_changesets, i).size();
28✔
1532
        accum_byte_size += changeset_size;
28✔
1533
        REALM_ASSERT(m_acc->sh_cumul_byte_sizes.get(i) == accum_byte_size);
28✔
1534
    }
28✔
1535

8✔
1536
    // Check client file entries
8✔
1537
    version_type current_server_version = m_history_base_version + m_history_size;
16✔
1538
    REALM_ASSERT(m_num_client_files >= 2);
16✔
1539
    bool found_self = false;
16✔
1540
    for (std::size_t i = 0; i < m_num_client_files; ++i) {
88✔
1541
        file_ident_type client_file_ident = file_ident_type(i);
72✔
1542
        auto j = client_files.find(client_file_ident);
72✔
1543
        ClientFile* client_file = (j == client_files.end() ? nullptr : &j->second);
58✔
1544
        version_type last_integrated_client_version = 0;
72✔
1545
        if (client_file)
72✔
1546
            last_integrated_client_version = client_file->last_integrated_client_version;
28✔
1547
        auto ident_salt = m_acc->cf_ident_salts.get(i);
72✔
1548
        auto client_version = m_acc->cf_client_versions.get(i);
72✔
1549
        auto rh_base_version = m_acc->cf_rh_base_versions.get(i);
72✔
1550
        auto recip_hist_ref = m_acc->cf_recip_hist_refs.get(i);
72✔
1551
        auto proxy_file = m_acc->cf_proxy_files.get(i);
72✔
1552
        auto client_type = m_acc->cf_client_types.get(i);
72✔
1553
        auto last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(i);
72✔
1554
        auto locked_server_version = m_acc->cf_locked_server_versions.get(i);
72✔
1555
        version_type client_version_2 = 0;
72✔
1556
        REALM_ASSERT(!util::int_cast_with_overflow_detect(client_version, client_version_2));
72✔
1557
        file_ident_type proxy_file_2 = 0;
72✔
1558
        REALM_ASSERT(!util::int_cast_with_overflow_detect(proxy_file, proxy_file_2));
72✔
1559
        version_type locked_server_version_2 = 0;
72✔
1560
        REALM_ASSERT(!util::int_cast_with_overflow_detect(locked_server_version, locked_server_version_2));
72✔
1561
        if (client_file_ident == 0) {
72✔
1562
            // Special entry
8✔
1563
            REALM_ASSERT(ident_salt == 0);
16✔
1564
            REALM_ASSERT(proxy_file_2 == 0);
16✔
1565
            REALM_ASSERT(client_type == 0);
16✔
1566
            REALM_ASSERT(last_seen_timestamp == 0);
16✔
1567
            REALM_ASSERT(locked_server_version_2 == 0);
16✔
1568
            // Upstream server
8✔
1569
            REALM_ASSERT(client_version_2 >= last_integrated_client_version);
16✔
1570
        }
16✔
1571
        else if (client_file_ident == g_root_node_file_ident) {
56✔
1572
            // Root node's entry
8✔
1573
            REALM_ASSERT(ident_salt == 0);
16✔
1574
            REALM_ASSERT(client_version_2 == 0);
16✔
1575
            REALM_ASSERT(rh_base_version == 0);
16✔
1576
            REALM_ASSERT(recip_hist_ref == 0);
16✔
1577
            REALM_ASSERT(proxy_file_2 == 0);
16✔
1578
            REALM_ASSERT(client_type == 0);
16✔
1579
            REALM_ASSERT(last_seen_timestamp == 0);
16✔
1580
            REALM_ASSERT(locked_server_version_2 == 0);
16✔
1581
            REALM_ASSERT(!client_file);
16✔
1582
            if (m_local_file_ident == g_root_node_file_ident)
16✔
1583
                found_self = true;
16✔
1584
        }
16✔
1585
        else if (client_file_ident == m_local_file_ident) {
40✔
1586
            // Entry representing the Realm file itself
1587
            REALM_ASSERT(ident_salt == 0);
×
1588
            REALM_ASSERT(client_version_2 == 0);
×
1589
            REALM_ASSERT(rh_base_version == 0);
×
1590
            REALM_ASSERT(recip_hist_ref == 0);
×
1591
            REALM_ASSERT(proxy_file_2 == 0);
×
1592
            REALM_ASSERT(client_type == int(ClientType::self));
×
1593
            REALM_ASSERT(last_seen_timestamp == 0);
×
1594
            REALM_ASSERT(locked_server_version_2 == 0);
×
1595
            REALM_ASSERT(!client_file);
×
1596
            found_self = true;
×
1597
        }
×
1598
        else if (ident_salt == 0) {
40✔
1599
            if (proxy_file_2 == 0) {
×
1600
                // This entry represents a file reachable via the upstream
1601
                // server.
1602
                REALM_ASSERT(client_version_2 == 0);
×
1603
                REALM_ASSERT(rh_base_version == 0);
×
1604
                REALM_ASSERT(recip_hist_ref == 0);
×
1605
                REALM_ASSERT(client_type == int(ClientType::upstream));
×
1606
                REALM_ASSERT(last_seen_timestamp == 0);
×
1607
                REALM_ASSERT(locked_server_version_2 == 0);
×
1608
                REALM_ASSERT(!client_file);
×
1609
            }
×
1610
            else {
×
1611
                // This entry represents a client of a direct client, such as
1612
                // client of a partial view, or a client of a subserver.
1613
                REALM_ASSERT(client_version_2 == 0);
×
1614
                REALM_ASSERT(rh_base_version == 0);
×
1615
                REALM_ASSERT(recip_hist_ref == 0);
×
1616
                REALM_ASSERT(client_type == int(ClientType::indirect));
×
1617
                REALM_ASSERT(last_seen_timestamp == 0);
×
1618
                REALM_ASSERT(locked_server_version_2 == 0);
×
1619
                REALM_ASSERT(is_valid_proxy_file_ident(proxy_file_2));
×
1620
                REALM_ASSERT(!client_file);
×
1621
            }
×
1622
        }
×
1623
        else {
40✔
1624
            // This entry represents a direct client, which can be a regular
20✔
1625
            // client, a subserver, or a partial view.
20✔
1626
            bool expired = (last_seen_timestamp == 0);
40✔
1627
            REALM_ASSERT(ident_salt > 0 && ident_salt <= 0x0'7FFF'FFFF'FFFF'FFFF);
40✔
1628
            REALM_ASSERT(client_version_2 >= last_integrated_client_version);
40✔
1629
            REALM_ASSERT(!expired || (recip_hist_ref == 0));
40!
1630
            REALM_ASSERT(proxy_file_2 == 0);
40✔
1631
            REALM_ASSERT(is_direct_client(ClientType(client_type)));
40✔
1632
            REALM_ASSERT(locked_server_version_2 <= current_server_version);
40✔
1633
        }
40✔
1634
    }
72✔
1635
    REALM_ASSERT(found_self);
16✔
1636

8✔
1637
    REALM_ASSERT(m_ct_history_size >= 1); // See comment above
16✔
1638
    REALM_ASSERT(m_acc->ct_history.size() == m_ct_history_size);
16✔
1639
#endif // REALM_DEBUG
16✔
1640
}
16✔
1641

1642
void ServerHistory::discard_accessors() const noexcept
1643
{
1,748✔
1644
    m_acc = util::none;
1,748✔
1645
}
1,748✔
1646

1647

1648
class ServerHistory::DiscardAccessorsGuard {
1649
public:
1650
    DiscardAccessorsGuard(const ServerHistory& sh) noexcept
1651
        : m_server_history{&sh}
1652
    {
2,038✔
1653
    }
2,038✔
1654
    ~DiscardAccessorsGuard() noexcept
1655
    {
2,038✔
1656
        if (REALM_UNLIKELY(m_server_history))
2,038✔
1657
            m_server_history->discard_accessors();
840✔
1658
    }
2,038✔
1659
    void release() noexcept
1660
    {
2,038✔
1661
        m_server_history = nullptr;
2,038✔
1662
    }
2,038✔
1663

1664
private:
1665
    const ServerHistory* m_server_history;
1666
};
1667

1668

1669
// Overriding member in _impl::History
1670
void ServerHistory::update_from_ref_and_version(ref_type ref, version_type realm_version)
1671
{
90,292✔
1672
    if (ref == 0) {
90,292✔
1673
        // No history schema yet
662✔
1674
        m_local_file_ident = g_root_node_file_ident;
1,748✔
1675
        m_num_client_files = 0;
1,748✔
1676
        m_history_base_version = 0;
1,748✔
1677
        m_history_size = 0;
1,748✔
1678
        m_server_version_salt = 0;
1,748✔
1679
        m_ct_base_version = realm_version;
1,748✔
1680
        m_ct_history_size = 0;
1,748✔
1681
        discard_accessors();
1,748✔
1682
        return;
1,748✔
1683
    }
1,748✔
1684
    if (REALM_LIKELY(m_acc)) {
88,544✔
1685
        m_acc->init_from_ref(ref); // Throws
87,380✔
1686
    }
87,380✔
1687
    else {
1,164✔
1688
        Allocator& alloc = _impl::GroupFriend::get_alloc(*m_group);
1,164✔
1689
        m_acc.emplace(alloc);
1,164✔
1690
        DiscardAccessorsGuard dag{*this};
1,164✔
1691
        m_acc->init_from_ref(ref);
1,164✔
1692
        _impl::GroupFriend::set_history_parent(*m_group, m_acc->root);
1,164✔
1693

504✔
1694
        if (m_acc->upstream_status.is_attached()) {
1,164✔
1695
            REALM_ASSERT(m_acc->upstream_status.size() == s_upstream_status_size);
×
1696
        }
×
1697
        if (m_acc->partial_sync.is_attached()) {
1,164✔
1698
            REALM_ASSERT(m_acc->partial_sync.size() == s_partial_sync_size);
×
1699
        }
×
1700
        dag.release();
1,164✔
1701
    }
1,164✔
1702

47,040✔
1703
    if (m_acc->upstream_status.is_attached()) {
88,544✔
1704
        file_ident_type file_ident = m_group->get_sync_file_id();
×
1705
        m_local_file_ident = (file_ident == 0 ? g_root_node_file_ident : file_ident);
×
1706
    }
×
1707
    else {
88,544✔
1708
        m_local_file_ident = g_root_node_file_ident;
88,544✔
1709
    }
88,544✔
1710

47,040✔
1711
    m_num_client_files = m_acc->cf_ident_salts.size();
88,544✔
1712
    REALM_ASSERT(m_acc->cf_client_versions.size() == m_num_client_files);
88,544✔
1713
    REALM_ASSERT(m_acc->cf_rh_base_versions.size() == m_num_client_files);
88,544✔
1714
    REALM_ASSERT(m_acc->cf_recip_hist_refs.size() == m_num_client_files);
88,544✔
1715
    REALM_ASSERT(m_acc->cf_proxy_files.size() == m_num_client_files);
88,544✔
1716
    REALM_ASSERT(m_acc->cf_client_types.size() == m_num_client_files);
88,544✔
1717
    REALM_ASSERT(m_acc->cf_last_seen_timestamps.size() == m_num_client_files);
88,544✔
1718
    REALM_ASSERT(m_acc->cf_locked_server_versions.size() == m_num_client_files);
88,544✔
1719

47,040✔
1720
    m_history_base_version = version_type(m_acc->root.get_as_ref_or_tagged(s_history_base_version_iip).get_as_int());
88,544✔
1721
    m_history_size = m_acc->sh_changesets.size();
88,544✔
1722
    REALM_ASSERT(m_acc->sh_version_salts.size() == m_history_size);
88,544✔
1723
    REALM_ASSERT(m_acc->sh_origin_files.size() == m_history_size);
88,544✔
1724
    REALM_ASSERT(m_acc->sh_client_versions.size() == m_history_size);
88,544✔
1725
    REALM_ASSERT(m_acc->sh_timestamps.size() == m_history_size);
88,544✔
1726
    REALM_ASSERT(m_acc->sh_cumul_byte_sizes.size() == m_history_size);
88,544✔
1727

47,040✔
1728
    m_server_version_salt =
88,544✔
1729
        (m_history_size > 0 ? salt_type(m_acc->sh_version_salts.get(m_history_size - 1))
85,528✔
1730
                            : salt_type(m_acc->root.get_as_ref_or_tagged(s_base_version_salt_iip).get_as_int()));
50,056✔
1731

47,040✔
1732
    m_ct_history_size = m_acc->ct_history.size();
88,544✔
1733
    m_ct_base_version = realm_version - m_ct_history_size;
88,544✔
1734
}
88,544✔
1735

1736

1737
void ServerHistory::Accessors::init_from_ref(ref_type ref)
1738
{
88,538✔
1739
    root.init_from_ref(ref);
88,538✔
1740
    client_files.init_from_parent();
88,538✔
1741
    sync_history.init_from_parent();
88,538✔
1742

47,038✔
1743
    {
88,538✔
1744
        ref_type ref_2 = upstream_status.get_ref_from_parent();
88,538✔
1745
        if (ref_2 != 0) {
88,538✔
1746
            upstream_status.init_from_ref(ref_2);
×
1747
        }
×
1748
        else {
88,538✔
1749
            upstream_status.detach();
88,538✔
1750
        }
88,538✔
1751
    }
88,538✔
1752

47,038✔
1753
    cf_ident_salts.init_from_parent();            // Throws
88,538✔
1754
    cf_client_versions.init_from_parent();        // Throws
88,538✔
1755
    cf_rh_base_versions.init_from_parent();       // Throws
88,538✔
1756
    cf_recip_hist_refs.init_from_parent();        // Throws
88,538✔
1757
    cf_proxy_files.init_from_parent();            // Throws
88,538✔
1758
    cf_client_types.init_from_parent();           // Throws
88,538✔
1759
    cf_last_seen_timestamps.init_from_parent();   // Throws
88,538✔
1760
    cf_locked_server_versions.init_from_parent(); // Throws
88,538✔
1761
    sh_version_salts.init_from_parent();          // Throws
88,538✔
1762
    sh_origin_files.init_from_parent();           // Throws
88,538✔
1763
    sh_client_versions.init_from_parent();        // Throws
88,538✔
1764
    sh_timestamps.init_from_parent();             // Throws
88,538✔
1765
    sh_changesets.init_from_parent();             // Throws
88,538✔
1766
    sh_cumul_byte_sizes.init_from_parent();       // Throws
88,538✔
1767
    ct_history.init_from_parent();                // Throws
88,538✔
1768

47,038✔
1769
    // Note: If anything throws above, then accessors will be left in an
47,038✔
1770
    // undefined state. However, all IntegerBpTree accessors will still have
47,038✔
1771
    // a root array, and all optional BinaryColumn accessors will still
47,038✔
1772
    // exist, so it will be safe to call update_from_ref() again.
47,038✔
1773
}
88,538✔
1774

1775

1776
void ServerHistory::create_empty_history()
1777
{
874✔
1778
    using gf = _impl::GroupFriend;
874✔
1779

332✔
1780
    REALM_ASSERT(m_local_file_ident == g_root_node_file_ident);
874✔
1781
    REALM_ASSERT(m_num_client_files == 0);
874✔
1782
    REALM_ASSERT(m_history_base_version == 0);
874✔
1783
    REALM_ASSERT(m_history_size == 0);
874✔
1784
    REALM_ASSERT(m_server_version_salt == 0);
874✔
1785
    REALM_ASSERT(m_ct_history_size == 0);
874✔
1786
    REALM_ASSERT(!m_acc);
874✔
1787
    Allocator& alloc = m_db->get_alloc();
874✔
1788
    m_acc.emplace(alloc);
874✔
1789
    DiscardAccessorsGuard dag{*this};
874✔
1790
    gf::prepare_history_parent(*m_group, m_acc->root, Replication::hist_SyncServer,
874✔
1791
                               get_server_history_schema_version(), m_local_file_ident); // Throws
874✔
1792
    m_acc->create();                                                                     // Throws
874✔
1793
    dag.release();
874✔
1794

332✔
1795
    // Add the special client file entry (index = 0), and the root servers entry
332✔
1796
    // (index = 1).
332✔
1797
    static_assert(g_root_node_file_ident == 1, "");
874✔
1798
    REALM_ASSERT(m_num_client_files == 0);
874✔
1799
    for (int i = 0; i < 2; ++i) {
2,622✔
1800
        m_acc->cf_ident_salts.insert(realm::npos, 0);            // Throws
1,748✔
1801
        m_acc->cf_client_versions.insert(realm::npos, 0);        // Throws
1,748✔
1802
        m_acc->cf_rh_base_versions.insert(realm::npos, 0);       // Throws
1,748✔
1803
        m_acc->cf_recip_hist_refs.insert(realm::npos, 0);        // Throws
1,748✔
1804
        m_acc->cf_proxy_files.insert(realm::npos, 0);            // Throws
1,748✔
1805
        m_acc->cf_client_types.insert(realm::npos, 0);           // Throws
1,748✔
1806
        m_acc->cf_last_seen_timestamps.insert(realm::npos, 0);   // Throws
1,748✔
1807
        m_acc->cf_locked_server_versions.insert(realm::npos, 0); // Throws
1,748✔
1808
        ++m_num_client_files;
1,748✔
1809
    }
1,748✔
1810
}
874✔
1811

1812
void ServerHistory::Accessors::create()
1813
{
874✔
1814
    // Note: `Array::create()` does *NOT* call `Node::update_parent()`, while
332✔
1815
    // `BPlusTree<T>::create()` *DOES* update its parent in an exception-safe
332✔
1816
    // way. This means that we need destruction guards for arrays, but not
332✔
1817
    // BPlusTrees/BinaryColumns.
332✔
1818

332✔
1819
    // Note: The arrays `upstream_status` and `partial_sync` are created
332✔
1820
    // on-demand instead of here.
332✔
1821

332✔
1822
    bool context_flag_no = false;
874✔
1823
    root.create(Array::type_HasRefs, context_flag_no, s_root_size); // Throws
874✔
1824
    _impl::DeepArrayDestroyGuard destroy_guard(&root);
874✔
1825

332✔
1826
    client_files.create(Array::type_HasRefs, context_flag_no, s_client_files_size); // Throws
874✔
1827
    client_files.update_parent();                                                   // Throws
874✔
1828

332✔
1829
    sync_history.create(Array::type_HasRefs, context_flag_no, s_sync_history_size); // Throws
874✔
1830
    sync_history.update_parent();                                                   // Throws
874✔
1831

332✔
1832
    schema_versions.create(Array::type_HasRefs, context_flag_no, s_schema_versions_size); // Throws
874✔
1833
    schema_versions.update_parent();
874✔
1834
    Allocator& alloc = schema_versions.get_alloc();
874✔
1835
    for (int i = 0; i < s_schema_versions_size; i++) {
4,370✔
1836
        MemRef mem = Array::create_empty_array(Array::type_Normal, context_flag_no, alloc);
3,496✔
1837
        ref_type ref = mem.get_ref();
3,496✔
1838
        schema_versions.set_as_ref(i, ref);
3,496✔
1839
    }
3,496✔
1840

332✔
1841
    cf_ident_salts.create();            // Throws
874✔
1842
    cf_client_versions.create();        // Throws
874✔
1843
    cf_rh_base_versions.create();       // Throws
874✔
1844
    cf_recip_hist_refs.create();        // Throws
874✔
1845
    cf_proxy_files.create();            // Throws
874✔
1846
    cf_client_types.create();           // Throws
874✔
1847
    cf_last_seen_timestamps.create();   // Throws
874✔
1848
    cf_locked_server_versions.create(); // Throws
874✔
1849

332✔
1850
    sh_version_salts.create();    // Throws
874✔
1851
    sh_origin_files.create();     // Throws
874✔
1852
    sh_client_versions.create();  // Throws
874✔
1853
    sh_timestamps.create();       // Throws
874✔
1854
    sh_changesets.create();       // Throws
874✔
1855
    sh_cumul_byte_sizes.create(); // Throws
874✔
1856

332✔
1857
    ct_history.create(); // Throws
874✔
1858

332✔
1859
    destroy_guard.release();
874✔
1860
    root.update_parent(); // Throws
874✔
1861
}
874✔
1862

1863

1864
auto ServerHistory::get_server_version_salt(version_type server_version) const noexcept -> salt_type
1865
{
4,360✔
1866
    REALM_ASSERT(server_version >= m_history_base_version);
4,360✔
1867
    if (server_version == m_history_base_version)
4,360✔
1868
        return salt_type(m_acc->root.get(s_base_version_salt_iip));
1,588✔
1869
    std::size_t history_entry_index = to_size_t(server_version - m_history_base_version) - 1;
2,772✔
1870
    REALM_ASSERT(history_entry_index < m_history_size);
2,772✔
1871
    return salt_type(m_acc->sh_version_salts.get(history_entry_index));
2,772✔
1872
}
2,772✔
1873

1874

1875
bool ServerHistory::is_valid_proxy_file_ident(file_ident_type file_ident) const noexcept
1876
{
×
1877
    static_assert(g_root_node_file_ident == 1, "");
×
1878
    REALM_ASSERT(file_ident >= 2);
×
1879
    REALM_ASSERT(std::uint_fast64_t(file_ident) < m_num_client_files);
×
1880
    std::size_t i = std::size_t(file_ident);
×
1881
    auto client_type = m_acc->cf_client_types.get(i);
×
1882
    return is_direct_client(ClientType(client_type));
×
1883
}
×
1884

1885

1886
void ServerHistory::add_core_history_entry(BinaryData changeset)
1887
{
41,936✔
1888
    REALM_ASSERT(m_acc->ct_history.size() == m_ct_history_size);
41,936✔
1889

21,586✔
1890
    if (changeset.is_null())
41,936✔
1891
        changeset = BinaryData("", 0);
2,394✔
1892

21,586✔
1893
    m_acc->ct_history.add(changeset); // Throws
41,936✔
1894
    ++m_ct_history_size;
41,936✔
1895
}
41,936✔
1896

1897

1898
void ServerHistory::add_sync_history_entry(const HistoryEntry& entry)
1899
{
39,170✔
1900
    REALM_ASSERT(m_acc->sh_version_salts.size() == m_history_size);
39,170✔
1901
    REALM_ASSERT(m_acc->sh_origin_files.size() == m_history_size);
39,170✔
1902
    REALM_ASSERT(m_acc->sh_client_versions.size() == m_history_size);
39,170✔
1903
    REALM_ASSERT(m_acc->sh_timestamps.size() == m_history_size);
39,170✔
1904
    REALM_ASSERT(m_acc->sh_changesets.size() == m_history_size);
39,170✔
1905
    REALM_ASSERT(m_acc->sh_cumul_byte_sizes.size() == m_history_size);
39,170✔
1906

19,166✔
1907
    std::int_fast64_t client_file = std::int_fast64_t(entry.origin_file_ident);
39,170✔
1908
    std::int_fast64_t client_version = std::int_fast64_t(entry.remote_version);
39,170✔
1909
    std::int_fast64_t timestamp = std::int_fast64_t(entry.origin_timestamp);
39,170✔
1910

19,166✔
1911
    // FIXME: BinaryColumn::set() currently interprets BinaryData(0,0) as
19,166✔
1912
    // null. It should probably be changed such that BinaryData(0,0) is
19,166✔
1913
    // always interpreted as the empty string. For the purpose of setting
19,166✔
1914
    // null values, BinaryColumn::set() should accept values of type
19,166✔
1915
    // Optional<BinaryData>().
19,166✔
1916
    BinaryData changeset("", 0);
39,170✔
1917
    if (!entry.changeset.is_null())
39,170✔
1918
        changeset = entry.changeset.get_first_chunk();
38,450✔
1919

19,166✔
1920
    m_acc->sh_version_salts.insert(realm::npos, m_salt_for_new_server_versions); // Throws
39,170✔
1921
    m_acc->sh_origin_files.insert(realm::npos, client_file);                     // Throws
39,170✔
1922
    m_acc->sh_client_versions.insert(realm::npos, client_version);               // Throws
39,170✔
1923
    m_acc->sh_timestamps.insert(realm::npos, timestamp);                         // Throws
39,170✔
1924
    m_acc->sh_changesets.add(changeset);                                         // Throws
39,170✔
1925

19,166✔
1926
    // Update the cumulative byte size.
19,166✔
1927
    std::int_fast64_t previous_history_byte_size =
39,170✔
1928
        (m_history_size == 0 ? 0 : m_acc->sh_cumul_byte_sizes.get(m_history_size - 1));
38,804✔
1929
    std::int_fast64_t history_byte_size = previous_history_byte_size + changeset.size();
39,170✔
1930
    m_acc->sh_cumul_byte_sizes.insert(realm::npos, history_byte_size);
39,170✔
1931

19,166✔
1932
    ++m_history_size;
39,170✔
1933
    m_server_version_salt = m_salt_for_new_server_versions;
39,170✔
1934
}
39,170✔
1935

1936

1937
void ServerHistory::trim_cont_transact_history()
1938
{
41,936✔
1939
    REALM_ASSERT(m_acc->ct_history.size() == m_ct_history_size);
41,936✔
1940

21,586✔
1941
    // `m_version_of_oldest_bound_snapshot` is not updated by transactions
21,586✔
1942
    // occuring through other DB objects than the one associated with
21,586✔
1943
    // this history object. For that reason, it can sometimes happen that it
21,586✔
1944
    // precedes the beginning of the history, even though it seems
21,586✔
1945
    // nonsensical. It would happen if the history was already trimmed via one
21,586✔
1946
    // of the other DB objects. In such a case, no trimming can be done
21,586✔
1947
    // yet.
21,586✔
1948
    if (m_version_of_oldest_bound_snapshot > m_ct_base_version) {
41,936✔
1949
        std::size_t num_entries_to_erase = std::size_t(m_version_of_oldest_bound_snapshot - m_ct_base_version);
41,064✔
1950
        // The new changeset is always added before
21,256✔
1951
        // set_oldest_bound_version() is called. Therefore, the trimming
21,256✔
1952
        // operation can never leave the history empty.
21,256✔
1953
        REALM_ASSERT(num_entries_to_erase < m_ct_history_size);
41,064✔
1954
        for (std::size_t i = 0; i < num_entries_to_erase; ++i) {
82,128✔
1955
            std::size_t j = num_entries_to_erase - i - 1;
41,064✔
1956
            m_acc->ct_history.erase(j);
41,064✔
1957
        }
41,064✔
1958
        m_ct_base_version += num_entries_to_erase;
41,064✔
1959
        m_ct_history_size -= num_entries_to_erase;
41,064✔
1960
    }
41,064✔
1961
}
41,936✔
1962

1963

1964
ChunkedBinaryData ServerHistory::get_changeset(version_type server_version) const noexcept
1965
{
×
1966
    REALM_ASSERT(server_version > m_history_base_version && server_version <= get_server_version());
×
1967
    std::size_t history_entry_ndx = to_size_t(server_version - m_history_base_version) - 1;
×
1968
    return ChunkedBinaryData(m_acc->sh_changesets, history_entry_ndx);
×
1969
}
×
1970

1971

1972
// Skips history entries with empty changesets, and history entries produced by
1973
// integration of changes received from the specified remote file.
1974
//
1975
// Pass zero for `remote_file_ident` if the remote file is on the upstream
1976
// server, or the reference file.
1977
//
1978
// Returns zero if no history entry was found. Otherwise it returns the version
1979
// produced by the changeset of the located history entry.
1980
auto ServerHistory::find_history_entry(file_ident_type remote_file_ident, version_type begin_version,
1981
                                       version_type end_version, HistoryEntry& entry,
1982
                                       version_type& last_integrated_remote_version) const noexcept -> version_type
1983
{
180,550✔
1984
    REALM_ASSERT(remote_file_ident != g_root_node_file_ident);
180,550✔
1985
    REALM_ASSERT(begin_version >= m_history_base_version);
180,550✔
1986
    REALM_ASSERT(begin_version <= end_version);
180,550✔
1987
    auto server_version = begin_version;
180,550✔
1988
    while (server_version < end_version) {
274,144✔
1989
        ++server_version;
213,004✔
1990
        // FIXME: Find a way to avoid dynamically allocating a buffer for, and
114,646✔
1991
        // copying the changeset for all the skipped history entries.
114,646✔
1992
        HistoryEntry entry_2 = get_history_entry(server_version);
213,004✔
1993
        bool received_from_client = received_from(entry_2, remote_file_ident);
213,004✔
1994
        if (received_from_client) {
213,004✔
1995
            last_integrated_remote_version = entry_2.remote_version;
79,398✔
1996
            continue;
79,398✔
1997
        }
79,398✔
1998
        if (entry_2.changeset.size() == 0)
133,606✔
1999
            continue; // Empty
14,196✔
2000
        // These changes were not received from the specified client, and the
67,582✔
2001
        // changeset was not empty.
67,582✔
2002
        entry = entry_2;
119,410✔
2003
        return server_version;
119,410✔
2004
    }
119,410✔
2005
    return 0;
128,722✔
2006
}
180,550✔
2007

2008

2009
auto ServerHistory::get_history_entry(version_type server_version) const noexcept -> HistoryEntry
2010
{
263,694✔
2011
    REALM_ASSERT(server_version > m_history_base_version && server_version <= get_server_version());
263,694✔
2012
    std::size_t history_entry_ndx = to_size_t(server_version - m_history_base_version) - 1;
263,694✔
2013
    auto origin_file = m_acc->sh_origin_files.get(history_entry_ndx);
263,694✔
2014
    auto client_version = m_acc->sh_client_versions.get(history_entry_ndx);
263,694✔
2015
    auto timestamp = m_acc->sh_timestamps.get(history_entry_ndx);
263,694✔
2016
    ChunkedBinaryData chunked_changeset(m_acc->sh_changesets, history_entry_ndx);
263,694✔
2017
    HistoryEntry entry;
263,694✔
2018
    entry.origin_file_ident = file_ident_type(origin_file);
263,694✔
2019
    entry.remote_version = version_type(client_version);
263,694✔
2020
    entry.origin_timestamp = timestamp_type(timestamp);
263,694✔
2021
    entry.changeset = chunked_changeset;
263,694✔
2022
    return entry;
263,694✔
2023
}
263,694✔
2024

2025

2026
// Returns true if, and only if the specified history entry was produced by
2027
// integratrion of a changeset that was received from the specified remote
2028
// file. Use `remote_file_ident = 0` to specify the upstream server when on a
2029
// subtier node of a star topology server cluster, or to specify the reference
2030
// file when in a partial view.
2031
bool ServerHistory::received_from(const HistoryEntry& entry, file_ident_type remote_file_ident) const noexcept
2032
{
213,042✔
2033
    file_ident_type origin_file_ident = entry.origin_file_ident;
213,042✔
2034
    std::size_t origin_file_index = std::size_t(origin_file_ident);
213,042✔
2035
    bool from_upstream_server = (remote_file_ident == 0);
213,042✔
2036
    if (!from_upstream_server) {
213,050✔
2037
        std::size_t remote_file_index = std::size_t(remote_file_ident);
213,050✔
2038
        REALM_ASSERT(is_direct_client(ClientType(m_acc->cf_client_types.get(remote_file_index))));
213,050✔
2039
        if (origin_file_ident == remote_file_ident)
213,050✔
2040
            return true;
79,398✔
2041
        file_ident_type proxy_file = file_ident_type(m_acc->cf_proxy_files.get(origin_file_index));
133,652✔
2042
        return (proxy_file == remote_file_ident);
133,652✔
2043
    }
133,652✔
2044
    bool of_local_origin = (origin_file_ident == 0);
4,294,967,294✔
2045
    if (of_local_origin)
4,294,967,294✔
2046
        return false;
×
2047
    ClientType client_type = ClientType(m_acc->cf_client_types.get(origin_file_index));
4,294,967,294✔
2048
    return (client_type == ClientType::upstream);
4,294,967,294✔
2049
}
4,294,967,294✔
2050

2051

2052
auto ServerHistory::get_history_contents() const -> HistoryContents
2053
{
×
2054
    HistoryContents hc;
×
2055

2056
    TransactionRef tr = m_db->start_read(); // Throws
×
2057
    version_type realm_version = tr->get_version();
×
2058
    const_cast<ServerHistory*>(this)->set_group(tr.get());
×
2059
    ensure_updated(realm_version); // Throws
×
2060

2061
    util::AppendBuffer<char> buffer;
×
2062
    hc.client_files = {};
×
2063
    for (std::size_t i = 0; i < m_num_client_files; ++i) {
×
2064
        HistoryContents::ClientFile cf;
×
2065
        cf.ident_salt = m_acc->cf_ident_salts.get(i);
×
2066
        cf.client_version = m_acc->cf_client_versions.get(i);
×
2067
        cf.rh_base_version = m_acc->cf_rh_base_versions.get(i);
×
2068
        cf.proxy_file = m_acc->cf_proxy_files.get(i);
×
2069
        cf.client_type = m_acc->cf_client_types.get(i);
×
2070
        cf.locked_server_version = m_acc->cf_locked_server_versions.get(i);
×
2071
        cf.reciprocal_history = {};
×
2072
        version_type recip_hist_base_version = version_type(cf.rh_base_version);
×
2073
        ReciprocalHistory recip_hist(m_acc->cf_recip_hist_refs, i, recip_hist_base_version); // Throws
×
2074
        std::size_t recip_hist_size = recip_hist.size();
×
2075
        for (std::size_t j = 0; j < recip_hist_size; ++j) {
×
2076
            version_type version = recip_hist_base_version + i + 1;
×
2077
            ChunkedBinaryData transform;
×
2078
            if (recip_hist.get(version, transform)) {
×
2079
                transform.copy_to(buffer);
×
2080
                cf.reciprocal_history.push_back(std::string{buffer.data(), buffer.size()});
×
2081
            }
×
2082
            else {
×
2083
                cf.reciprocal_history.push_back(util::none);
×
2084
            }
×
2085
        }
×
2086
        hc.client_files.push_back(cf);
×
2087
    }
×
2088

2089
    hc.history_base_version = m_acc->root.get_as_ref_or_tagged(s_history_base_version_iip).get_as_int();
×
2090
    hc.base_version_salt = m_acc->root.get_as_ref_or_tagged(s_base_version_salt_iip).get_as_int();
×
2091

2092
    hc.sync_history = {};
×
2093
    for (size_t i = 0; i < m_history_size; ++i) {
×
2094
        HistoryContents::HistoryEntry he;
×
2095
        he.version_salt = m_acc->sh_version_salts.get(i);
×
2096
        he.client_file_ident = m_acc->sh_origin_files.get(i);
×
2097
        he.client_version = m_acc->sh_client_versions.get(i);
×
2098
        he.timestamp = m_acc->sh_timestamps.get(i);
×
2099
        he.cumul_byte_size = m_acc->sh_cumul_byte_sizes.get(i);
×
2100
        ChunkedBinaryData chunked_changeset(m_acc->sh_changesets, i);
×
2101
        chunked_changeset.copy_to(buffer);
×
2102
        he.changeset = std::string(buffer.data(), buffer.size());
×
2103
        hc.sync_history.push_back(he);
×
2104
    }
×
2105

2106
    hc.servers_client_file_ident = m_local_file_ident;
×
2107

2108
    return hc;
×
2109
}
×
2110

2111

2112
void ServerHistory::fixup_state_and_changesets_for_assigned_file_ident(Transaction& group, file_ident_type file_ident)
2113
{
×
2114
    // Must be in write transaction!
2115

2116
    REALM_ASSERT(file_ident != 0);
×
2117
    REALM_ASSERT(file_ident != g_root_node_file_ident);
×
2118
    REALM_ASSERT(m_acc->upstream_status.is_attached());
×
2119
    REALM_ASSERT(m_local_file_ident == g_root_node_file_ident);
×
2120
    using Instruction = realm::sync::Instruction;
×
2121

2122
    auto promote_global_key = [&](GlobalKey& oid) {
×
2123
        REALM_ASSERT(oid.hi() == 0); // client_file_ident == 0
×
2124
        oid = GlobalKey{uint64_t(file_ident), oid.lo()};
×
2125
    };
×
2126

2127
    auto promote_primary_key = [&](Instruction::PrimaryKey& pk) {
×
2128
        mpark::visit(overload{[&](GlobalKey& key) {
×
2129
                                  promote_global_key(key);
×
2130
                              },
×
2131
                              [](auto&&) {}},
×
2132
                     pk);
×
2133
    };
×
2134

2135
    auto get_table_for_class = [&](StringData class_name) -> ConstTableRef {
×
2136
        REALM_ASSERT(class_name.size() < Group::max_table_name_length - 6);
×
2137
        Group::TableNameBuffer buffer;
×
2138
        return group.get_table(Group::class_name_to_table_name(class_name, buffer));
×
2139
    };
×
2140

2141
    // Fix up changesets in history. We know that all of these are of our own
2142
    // creation.
2143
    for (std::size_t i = 0; i < m_acc->sh_changesets.size(); ++i) {
×
2144
        ChunkedBinaryData changeset{m_acc->sh_changesets, i};
×
2145
        ChunkedBinaryInputStream in{changeset};
×
2146
        Changeset log;
×
2147
        parse_changeset(in, log);
×
2148

2149
        auto last_class_name = sync::InternString::npos;
×
2150
        ConstTableRef selected_table;
×
2151
        for (auto instr : log) {
×
2152
            if (!instr)
×
2153
                continue;
×
2154

2155
            if (auto obj_instr = instr->get_if<Instruction::ObjectInstruction>()) {
×
2156
                // Cache the TableRef
2157
                if (obj_instr->table != last_class_name) {
×
2158
                    StringData class_name = log.get_string(obj_instr->table);
×
2159
                    last_class_name = obj_instr->table;
×
2160
                    selected_table = get_table_for_class(class_name);
×
2161
                }
×
2162

2163
                // Fix up instructions using GlobalKey to identify objects.
2164
                promote_primary_key(obj_instr->object);
×
2165

2166
                // Fix up the payload for Set and ArrayInsert.
2167
                Instruction::Payload* payload = nullptr;
×
2168
                if (auto set_instr = instr->get_if<Instruction::Update>()) {
×
2169
                    payload = &set_instr->value;
×
2170
                }
×
2171
                else if (auto list_insert_instr = instr->get_if<Instruction::ArrayInsert>()) {
×
2172
                    payload = &list_insert_instr->value;
×
2173
                }
×
2174

2175
                if (payload && payload->type == Instruction::Payload::Type::Link) {
×
2176
                    promote_primary_key(payload->data.link.target);
×
2177
                }
×
2178
            }
×
2179
        }
×
2180

2181
        ChangesetEncoder::Buffer modified;
×
2182
        encode_changeset(log, modified);
×
2183
        BinaryData result = BinaryData{modified.data(), modified.size()};
×
2184
        m_acc->sh_changesets.set(i, result);
×
2185
    }
×
2186
}
×
2187

2188
void ServerHistory::record_current_schema_version()
2189
{
×
2190
    using gf = _impl::GroupFriend;
×
2191
    Allocator& alloc = gf::get_alloc(*m_group);
×
2192
    auto ref = gf::get_history_ref(*m_group);
×
2193
    REALM_ASSERT(ref != 0);
×
2194
    Array root{alloc};
×
2195
    gf::set_history_parent(*m_group, root);
×
2196
    root.init_from_ref(ref);
×
2197
    Array schema_versions{alloc};
×
2198
    schema_versions.set_parent(&root, s_schema_versions_iip);
×
2199
    schema_versions.init_from_parent();
×
2200
    version_type snapshot_version = m_db->get_version_of_latest_snapshot();
×
2201
    record_current_schema_version(schema_versions, snapshot_version); // Throws
×
2202
}
×
2203

2204

2205
void ServerHistory::record_current_schema_version(Array& schema_versions, version_type snapshot_version)
2206
{
×
2207
    static_assert(s_schema_versions_size == 4, "");
×
2208
    REALM_ASSERT(schema_versions.size() == s_schema_versions_size);
×
2209

2210
    Allocator& alloc = schema_versions.get_alloc();
×
2211
    {
×
2212
        Array sv_schema_versions{alloc};
×
2213
        sv_schema_versions.set_parent(&schema_versions, s_sv_schema_versions_iip);
×
2214
        sv_schema_versions.init_from_parent();
×
2215
        int schema_version = get_server_history_schema_version();
×
2216
        sv_schema_versions.add(schema_version); // Throws
×
2217
    }
×
2218
    {
×
2219
        Array sv_library_versions{alloc};
×
2220
        sv_library_versions.set_parent(&schema_versions, s_sv_library_versions_iip);
×
2221
        sv_library_versions.init_from_parent();
×
2222
        const char* library_version = REALM_VERSION_STRING;
×
2223
        std::size_t size = std::strlen(library_version);
×
2224
        Array value{alloc};
×
2225
        bool context_flag = false;
×
2226
        value.create(Array::type_Normal, context_flag, size); // Throws
×
2227
        _impl::ShallowArrayDestroyGuard adg{&value};
×
2228
        using uchar = unsigned char;
×
2229
        for (std::size_t i = 0; i < size; ++i)
×
2230
            value.set(i, std::int_fast64_t(uchar(library_version[i]))); // Throws
×
2231
        sv_library_versions.add(std::int_fast64_t(value.get_ref()));    // Throws
×
2232
        adg.release();                                                  // Ownership transferred to parent array
×
2233
    }
×
2234
    {
×
2235
        Array sv_snapshot_versions{alloc};
×
2236
        sv_snapshot_versions.set_parent(&schema_versions, s_sv_snapshot_versions_iip);
×
2237
        sv_snapshot_versions.init_from_parent();
×
2238
        sv_snapshot_versions.add(std::int_fast64_t(snapshot_version)); // Throws
×
2239
    }
×
2240
    {
×
2241
        Array sv_timestamps{alloc};
×
2242
        sv_timestamps.set_parent(&schema_versions, s_sv_timestamps_iip);
×
2243
        sv_timestamps.init_from_parent();
×
2244
        std::time_t timestamp = std::time(nullptr);
×
2245
        sv_timestamps.add(std::int_fast64_t(timestamp)); // Throws
×
2246
    }
×
2247
}
×
2248

2249

2250
Transformer& ServerHistory::Context::get_transformer()
2251
{
×
2252
    throw util::runtime_error("Not supported");
×
2253
}
×
2254

2255

2256
util::Buffer<char>& ServerHistory::Context::get_transform_buffer()
2257
{
×
2258
    throw util::runtime_error("Not supported");
×
2259
}
×
2260

2261

2262
std::ostream& _impl::operator<<(std::ostream& out, const ServerHistory::HistoryContents& hc)
2263
{
×
2264
    out << "client files:\n";
×
2265
    for (std::size_t i = 0; i < hc.client_files.size(); ++i) {
×
2266
        out << "\n";
×
2267
        out << "  client_file_ident = " << i << "\n";
×
2268
        out << "  ident_salt = " << hc.client_files[i].ident_salt << "\n";
×
2269
        out << "  client_version = " << hc.client_files[i].client_version << "\n";
×
2270
        out << "  rh_base_version = " << hc.client_files[i].rh_base_version << "\n";
×
2271
        out << "  proxy_file = " << hc.client_files[i].proxy_file << "\n";
×
2272
        out << "  client_type = " << hc.client_files[i].client_type << "\n";
×
2273
        out << "  locked_server_version = " << hc.client_files[i].locked_server_version << "\n";
×
2274
        out << "  reciprocal history:\n";
×
2275
        for (const util::Optional<std::string>& transform : hc.client_files[i].reciprocal_history) {
×
2276
            if (transform) {
×
2277
                out << "    " << util::hex_dump((*transform).data(), (*transform).size()) << "\n";
×
2278
            }
×
2279
            else {
×
2280
                out << "    NULL\n";
×
2281
            }
×
2282
        }
×
2283
        out << "\n";
×
2284
    }
×
2285
    out << "\n";
×
2286

2287
    out << "history_base_version = " << hc.history_base_version << "\n";
×
2288
    out << "base_version_salt = " << hc.base_version_salt << "\n";
×
2289
    out << "\n";
×
2290

2291
    out << "history entries:\n";
×
2292
    for (std::size_t i = 0; i < hc.sync_history.size(); ++i) {
×
2293
        out << "\n";
×
2294
        out << "  version_salt = " << hc.sync_history[i].version_salt << "\n";
×
2295
        out << "  client_file_ident = " << hc.sync_history[i].client_file_ident << "\n";
×
2296
        out << "  client_version = " << hc.sync_history[i].client_version << "\n";
×
2297
        out << "  timestamp = " << hc.sync_history[i].timestamp << "\n";
×
2298
        out << "  cumul_byte_size = " << hc.sync_history[i].cumul_byte_size << "\n";
×
2299
        const std::string& changeset = hc.sync_history[i].changeset;
×
2300
        out << "  changeset = " << util::hex_dump(changeset.data(), changeset.size()) << "\n";
×
2301
        out << "\n";
×
2302
    }
×
2303
    out << "\n";
×
2304

2305
    out << "servers_client_file_ident = " << hc.servers_client_file_ident << "\n";
×
2306

2307
    return out;
×
2308
}
×
2309

2310
bool _impl::operator==(const ServerHistory::HistoryContents& hc_1, const ServerHistory::HistoryContents& hc_2)
2311
{
×
2312
    if (hc_1.client_files.size() != hc_2.client_files.size())
×
2313
        return false;
×
2314

2315
    for (std::size_t i = 0; i < hc_1.client_files.size(); ++i) {
×
2316
        ServerHistory::HistoryContents::ClientFile cf_1 = hc_1.client_files[i];
×
2317
        ServerHistory::HistoryContents::ClientFile cf_2 = hc_2.client_files[i];
×
2318

2319
        bool partially_equal =
×
2320
            (cf_1.ident_salt == cf_2.ident_salt && cf_1.client_version == cf_2.client_version &&
×
2321
             cf_1.rh_base_version == cf_2.rh_base_version && cf_1.proxy_file == cf_2.proxy_file &&
×
2322
             cf_1.client_type == cf_2.client_type && cf_1.locked_server_version == cf_2.locked_server_version &&
×
2323
             cf_1.reciprocal_history.size() == cf_2.reciprocal_history.size());
×
2324
        if (!partially_equal)
×
2325
            return false;
×
2326

2327
        for (std::size_t j = 0; j < cf_1.reciprocal_history.size(); ++j) {
×
2328
            if (cf_1.reciprocal_history[j] != cf_2.reciprocal_history[j])
×
2329
                return false;
×
2330
        }
×
2331
    }
×
2332

2333
    bool same_base_version =
×
2334
        (hc_1.history_base_version == hc_2.history_base_version && hc_1.base_version_salt == hc_2.base_version_salt);
×
2335
    if (!same_base_version)
×
2336
        return false;
×
2337

2338
    if (hc_1.sync_history.size() != hc_2.sync_history.size())
×
2339
        return false;
×
2340

2341
    for (std::size_t i = 0; i < hc_1.sync_history.size(); ++i) {
×
2342
        ServerHistory::HistoryContents::HistoryEntry sh_1 = hc_1.sync_history[i];
×
2343
        ServerHistory::HistoryContents::HistoryEntry sh_2 = hc_2.sync_history[i];
×
2344
        bool equal = (sh_1.version_salt == sh_2.version_salt && sh_1.client_file_ident == sh_2.client_file_ident &&
×
2345
                      sh_1.client_version == sh_2.client_version && sh_1.timestamp == sh_2.timestamp &&
×
2346
                      sh_1.cumul_byte_size == sh_2.cumul_byte_size);
×
2347
        if (!equal)
×
2348
            return false;
×
2349
    }
×
2350

2351
    if (hc_1.servers_client_file_ident != hc_2.servers_client_file_ident)
×
2352
        return false;
×
2353

2354
    return true;
×
2355
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc