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

realm / realm-core / michael.wilkersonbarker_1147

04 Jun 2024 01:18PM UTC coverage: 90.835% (-0.008%) from 90.843%
michael.wilkersonbarker_1147

Pull #7542

Evergreen

michael-wb
Removed unused SyncClientHookEvent entries
Pull Request #7542: RCORE-2063 Fix some client resets potentially failing with AutoClientResetFailed if a new client reset condition occurred before the first one completed

101700 of 180094 branches covered (56.47%)

61 of 66 new or added lines in 5 files covered. (92.42%)

84 existing lines in 12 files now uncovered.

214628 of 236283 relevant lines covered (90.84%)

5386848.77 hits per line

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

63.51
/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,912✔
236
    TransactionRef rt = m_db->start_read(); // Throws
5,912✔
237
    version_type realm_version = rt->get_version();
5,912✔
238
    const_cast<ServerHistory*>(this)->set_group(rt.get());
5,912✔
239
    ensure_updated(realm_version); // Throws
5,912✔
240
    version_info.realm_version = realm_version;
5,912✔
241
    version_info.sync_version = get_salted_server_version();
5,912✔
242
    has_upstream_sync_status = (m_acc && m_acc->upstream_status.is_attached());
5,912✔
243
    bool is_initiated_as_partial_view = (m_acc && m_acc->partial_sync.is_attached());
5,912✔
244
    if (is_initiated_as_partial_view) {
5,912✔
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,912✔
251
        partial_file_ident = 0;
5,912✔
252
        partial_progress_reference_version = 0;
5,912✔
253
    }
5,912✔
254
}
5,912✔
255

256

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

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

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

272
    version_type new_realm_version = tr->commit(); // Throws
1,494✔
273
    version_info.realm_version = new_realm_version;
1,494✔
274
    version_info.sync_version = get_salted_server_version();
1,494✔
275
}
1,494✔
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
{
36,964✔
305
    REALM_ASSERT(!integratable_changesets.empty());
36,964✔
306

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

325
    result = {};
36,964✔
326
    for (;;) {
36,984✔
327
        if (has_changesets) {
36,984✔
328
            result.partial_clear();
18,520✔
329
        }
18,520✔
330

331
        bool anything_to_do = (integratable_changesets.size() > result.excluded_client_files.size());
36,984✔
332
        if (REALM_UNLIKELY(!anything_to_do))
36,984✔
333
            return false;
22✔
334

335
        file_ident_type current_client_file_ident = 0;
36,962✔
336
        ExtendedIntegrationError current_error_potential = {};
36,962✔
337
        std::size_t num_changesets_to_dump = 0;
36,962✔
338
        bool dump_changeset_info = false;
36,962✔
339

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

346
            bool dirty = false;
36,962✔
347
            bool backup_whole_realm_2 = false;
36,962✔
348
            for (file_ident_type client_file_ident : client_file_order) {
39,686✔
349
                REALM_ASSERT(client_file_ident > 0);
39,686✔
350
                REALM_ASSERT(client_file_ident != g_root_node_file_ident);
39,686✔
351
                REALM_ASSERT(client_file_ident != m_local_file_ident);
39,686✔
352
                bool excluded =
39,686✔
353
                    (result.excluded_client_files.find(client_file_ident) != result.excluded_client_files.end());
39,686✔
354
                if (REALM_UNLIKELY(excluded))
39,686✔
355
                    continue;
×
356
                current_client_file_ident = client_file_ident;
39,686✔
357
                // Verify that the client file entry has not expired
358
                current_error_potential = ExtendedIntegrationError::client_file_expired;
39,686✔
359
                std::size_t client_file_index = std::size_t(client_file_ident);
39,686✔
360
                std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(client_file_index);
39,686✔
361
                bool expired = (last_seen_timestamp == 0);
39,686✔
362
                if (REALM_UNLIKELY(expired))
39,686✔
363
                    goto error;
×
364
                const IntegratableChangesetList& list = integratable_changesets.find(client_file_ident)->second;
39,686✔
365
                std::vector<RemoteChangeset> changesets;
39,686✔
366
                current_error_potential = ExtendedIntegrationError::bad_origin_file_ident;
39,686✔
367
                for (const IntegratableChangeset& ic : list.changesets) {
39,686✔
368
                    REALM_ASSERT(ic.client_file_ident == client_file_ident);
34,460✔
369
                    // Verify that the origin file identifier either is the
370
                    // client's file identifier, or a file identifier of a
371
                    // subordinate client for which the sending client acts as a
372
                    // proxy.
373
                    file_ident_type origin_file_ident = ic.origin_file_ident;
34,460✔
374
                    if (origin_file_ident != 0) {
34,460✔
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
                    }
×
385
                    RemoteChangeset changeset{ic};
34,460✔
386
                    changesets.push_back(changeset);             // Throws
34,460✔
387
                    result.integrated_changesets.push_back(&ic); // Throws
34,460✔
388
                }
34,460✔
389

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

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

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

417
                integrate(0, num_changesets, list.upload_progress); // Throws
39,686✔
418
            }
39,686✔
419

420
            if (dirty) {
36,962✔
421
                auto ta = util::make_temp_assign(m_is_local_changeset, false, true);
36,946✔
422
                version_info.realm_version = tr->commit(); // Throws
36,946✔
423
                version_info.sync_version = get_salted_server_version();
36,946✔
424
                if (backup_whole_realm_2)
36,946✔
425
                    backup_whole_realm = true;
27,716✔
426
                return true;
36,946✔
427
            }
36,946✔
428
            return false;
16✔
429
        }
36,962✔
430
        catch (BadChangesetError& e) {
36,962✔
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) {
36,962✔
436
            logger.error("Failed to transform changeset received from client: %1",
18✔
437
                         e.what()); // Throws
18✔
438
            dump_changeset_info = true;
18✔
439
        }
18✔
440

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

461
    error:
22✔
462
        REALM_ASSERT(current_client_file_ident != 0);
22✔
463
        result.excluded_client_files[current_client_file_ident] = current_error_potential; // Throws
22✔
464
    }
22✔
465
}
36,964✔
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,552✔
533
    REALM_ASSERT(!m_acc->upstream_status.is_attached());
1,552✔
534

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

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

541
    file_ident_type ident = file_ident_type(file_index);
1,552✔
542
    return {ident, salt};
1,552✔
543
}
1,552✔
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,552✔
578
    REALM_ASSERT(file_index >= m_num_client_files);
1,552✔
579
    REALM_ASSERT(proxy_file_ident == 0 || is_valid_proxy_file_ident(proxy_file_ident));
1,552✔
580
    bool generate_salt = is_direct_client(client_type);
1,552✔
581
    salt_type salt = 0;
1,552✔
582
    if (generate_salt) {
1,552✔
583
        auto max_salt = 0x0'7FFF'FFFF'FFFF'FFFF;
1,552✔
584
        std::mt19937_64& random = m_context.server_history_get_random();
1,552✔
585
        salt = std::uniform_int_distribution<salt_type>(1, max_salt)(random);
1,552✔
586
    }
1,552✔
587
    while (file_index > m_num_client_files)
1,552✔
588
        add_client_file(0, 0, ClientType::upstream);      // Throws
×
589
    add_client_file(salt, proxy_file_ident, client_type); // Throws
1,552✔
590
    return salt;
1,552✔
591
}
1,552✔
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,552✔
623
    switch (client_type) {
1,552✔
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,552✔
634
        case ClientType::subserver:
1,552✔
635
            REALM_ASSERT(file_ident_salt != 0);
1,552✔
636
            REALM_ASSERT(proxy_file_ident == 0);
1,552✔
637
            break;
1,552✔
638
        case ClientType::legacy:
✔
639
            REALM_ASSERT(false);
×
640
            break;
×
641
    }
1,552✔
642
    std::int_fast64_t client_version = 0;
1,552✔
643
    std::int_fast64_t recip_hist_base_version = 0;
1,552✔
644
    ref_type recip_hist_ref = 0;
1,552✔
645
    std::int_fast64_t last_seen_timestamp = 0;
1,552✔
646
    std::int_fast64_t locked_server_version = 0;
1,552✔
647
    if (is_direct_client(client_type)) {
1,552✔
648
        last_seen_timestamp = 1;
1,552✔
649
    }
1,552✔
650
    m_acc->cf_ident_salts.insert(realm::npos, std::int_fast64_t(file_ident_salt));  // Throws
1,552✔
651
    m_acc->cf_client_versions.insert(realm::npos, client_version);                  // Throws
1,552✔
652
    m_acc->cf_rh_base_versions.insert(realm::npos, recip_hist_base_version);        // Throws
1,552✔
653
    m_acc->cf_recip_hist_refs.insert(realm::npos, recip_hist_ref);                  // Throws
1,552✔
654
    m_acc->cf_proxy_files.insert(realm::npos, std::int_fast64_t(proxy_file_ident)); // Throws
1,552✔
655
    m_acc->cf_client_types.insert(realm::npos, std::int_fast64_t(client_type));     // Throws
1,552✔
656
    m_acc->cf_last_seen_timestamps.insert(realm::npos, last_seen_timestamp);        // Throws
1,552✔
657
    m_acc->cf_locked_server_versions.insert(realm::npos, locked_server_version);    // Throws
1,552✔
658
    std::size_t max_size = std::numeric_limits<std::size_t>::max();
1,552✔
659
    if (m_num_client_files == max_size)
1,552✔
660
        throw util::overflow_error{"Client file index"};
×
661
    ++m_num_client_files;
1,552✔
662
}
1,552✔
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,218✔
688
    REALM_ASSERT(is_direct_client(client_type));
4,218✔
689
    REALM_ASSERT(client_type != ClientType::legacy);
4,218✔
690

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

709
    // Besides being superfluous, it is also a protocol violation if a client
710
    // asks to download from a point before the base of its reciprocal history.
711
    auto recip_hist_base_version = version_type(m_acc->cf_rh_base_versions.get(client_file_index));
4,214✔
712
    if (download_progress.server_version < recip_hist_base_version) {
4,214✔
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

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

733
    REALM_ASSERT_RELEASE(recip_hist_base_version >= m_history_base_version);
4,214✔
734

735
    // Validate `download_progress`
736
    version_type current_server_version = get_server_version();
4,214✔
737
    if (download_progress.server_version > current_server_version)
4,214✔
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,214✔
740
    if (download_progress.last_integrated_client_version > last_integrated_client_version)
4,214✔
741
        return BootstrapError::bad_download_client_version;
4✔
742

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

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

765
    upload_progress.client_version = last_integrated_client_version;
4,206✔
766
    upload_progress.last_integrated_server_version = recip_hist_base_version;
4,206✔
767
    locked_server_version = version_type(m_acc->cf_locked_server_versions.get(client_file_index));
4,206✔
768
    return BootstrapError::no_error;
4,206✔
769
}
4,206✔
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,218✔
777
    TransactionRef tr = m_db->start_read(); // Throws
4,218✔
778
    auto realm_version = tr->get_version();
4,218✔
779
    const_cast<ServerHistory*>(this)->set_group(tr.get());
4,218✔
780
    ensure_updated(realm_version); // Throws
4,218✔
781

782
    BootstrapError error = do_bootstrap_client_session(client_file_ident, download_progress, server_version,
4,218✔
783
                                                       client_type, upload_progress, locked_server_version, logger);
4,218✔
784
    return error;
4,218✔
785
}
4,218✔
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
{
41,950✔
796
    REALM_ASSERT(client_file_ident != 0);
41,950✔
797
    REALM_ASSERT(download_progress.server_version <= end_version);
41,950✔
798

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

804
    REALM_ASSERT(download_progress.server_version >= m_history_base_version);
41,950✔
805

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

816
    std::size_t accum_byte_size = 0;
41,950✔
817
    DownloadCursor download_progress_2 = download_progress;
41,950✔
818

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

827
    for (;;) {
84,008✔
828
        version_type begin_version = download_progress_2.server_version;
84,008✔
829
        HistoryEntry entry;
84,008✔
830
        version_type version = find_history_entry(client_file_ident, begin_version, end_version, entry,
84,008✔
831
                                                  download_progress_2.last_integrated_client_version);
84,008✔
832
        if (version == 0) {
84,008✔
833
            // End of history reached
834
            download_progress_2.server_version = end_version;
41,912✔
835
            break;
41,912✔
836
        }
41,912✔
837

838
        download_progress_2.server_version = version;
42,096✔
839

840
        entry.remote_version = download_progress_2.last_integrated_client_version;
42,096✔
841

842
        if (entry.origin_file_ident == 0)
42,096✔
843
            entry.origin_file_ident = m_local_file_ident;
4,800✔
844

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

860
        accum_byte_size += entry.changeset.size();
42,096✔
861

862
        if (accum_byte_size > accum_byte_size_soft_limit)
42,096✔
863
            break;
36✔
864
    }
42,096✔
865

866
    if (!disable_download_compaction) {
41,950✔
867
        compact_changesets(changesets.data(), changesets.size());
41,948✔
868

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

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

897
    version_type upload_client_version = version_type(m_acc->cf_client_versions.get(client_file_index));
41,950✔
898
    version_type upload_server_version = version_type(m_acc->cf_rh_base_versions.get(client_file_index));
41,950✔
899

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

905
    return true;
41,950✔
906
}
41,950✔
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}
18,878✔
977
        , m_remote_file_index{remote_file_index}
18,878✔
978
        , m_base_version{base_version}
18,878✔
979
    {
39,686✔
980
        if (ref_type ref = to_ref(cf_recip_hist_refs.get(remote_file_index))) {
39,686✔
981
            init(ref);                     // Throws
33,516✔
982
            m_size = m_changesets->size(); // Relatively expensive
33,516✔
983
        }
33,516✔
984
    }
39,686✔
985

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

991
    version_type base_version() const noexcept
992
    {
39,668✔
993
        return m_base_version;
39,668✔
994
    }
39,668✔
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
    {
20,536✔
1009
        if (m_changesets)
20,536✔
1010
            return;
19,674✔
1011

1012
        // Instantiate the reciprocal history
1013
        Allocator& alloc = m_cf_recip_hist_refs.get_alloc();
862✔
1014
        BinaryColumn recip_hist(alloc);
862✔
1015
        recip_hist.create();
862✔
1016
        auto ref = recip_hist.get_ref();
862✔
1017
        DeepArrayRefDestroyGuard adg{ref, alloc};
862✔
1018
        m_cf_recip_hist_refs.set(m_remote_file_index, ref); // Throws
862✔
1019
        adg.release();                                      // Ref ownership transferred to parent array
862✔
1020
        init(ref);                                          // Throws
862✔
1021
    }
862✔
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
    {
69,910✔
1027
        REALM_ASSERT(m_changesets);
69,910✔
1028
        REALM_ASSERT(server_version > m_base_version);
69,910✔
1029

1030
        std::size_t i = std::size_t(server_version - m_base_version - 1);
69,910✔
1031
        if (i < m_size) {
69,910✔
1032
            ChunkedBinaryData transform_2{*m_changesets, i};
17,928✔
1033
            if (!transform_2.is_null()) {
17,928✔
1034
                transform = transform_2;
6,384✔
1035
                return true;
6,384✔
1036
            }
6,384✔
1037
        }
17,928✔
1038
        return false;
63,526✔
1039
    }
69,910✔
1040

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

1061
    // Requires that new_base_version > base_version()
1062
    void trim(version_type new_base_version)
1063
    {
29,340✔
1064
        REALM_ASSERT(new_base_version > m_base_version);
29,340✔
1065
        std::size_t n = std::size_t(new_base_version - m_base_version);
29,340✔
1066
        if (n >= m_size) {
29,340✔
1067
            if (m_changesets)
28,714✔
1068
                m_changesets->clear(); // Throws
24,208✔
1069
            m_base_version = new_base_version;
28,714✔
1070
            m_size = 0;
28,714✔
1071
            return;
28,714✔
1072
        }
28,714✔
1073
        REALM_ASSERT(m_changesets);
626✔
1074
        while (n) {
13,912✔
1075
            m_changesets->erase(0);
13,286✔
1076
            --n;
13,286✔
1077
        }
13,286✔
1078
        m_base_version = new_base_version;
626✔
1079
        m_size -= n;
626✔
1080
    }
626✔
1081

1082
    void update_child_ref(std::size_t child_ndx, ref_type new_ref) override final
1083
    {
792✔
1084
        m_cf_recip_hist_refs.set(child_ndx, new_ref); // Throws
792✔
1085
    }
792✔
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
    {
34,378✔
1101
        Allocator& alloc = m_cf_recip_hist_refs.get_alloc();
34,378✔
1102
        m_changesets.emplace(alloc); // Throws
34,378✔
1103
        m_changesets->init_from_ref(ref);
34,378✔
1104
        m_changesets->set_parent(this, m_remote_file_index);
34,378✔
1105
    }
34,378✔
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}
9,450✔
1114
        , m_history{history}
9,450✔
1115
        , m_recip_hist{recip_hist}
9,450✔
1116
    {
20,532✔
1117
    }
20,532✔
1118

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

1125
    ChunkedBinaryData get_reciprocal_transform(version_type server_version,
1126
                                               bool& is_compressed) const noexcept override final
1127
    {
69,908✔
1128
        is_compressed = false;
69,908✔
1129
        ChunkedBinaryData transform;
69,908✔
1130
        if (m_recip_hist.get(server_version, transform))
69,908✔
1131
            return transform;
6,384✔
1132
        HistoryEntry entry = m_history.get_history_entry(server_version);
63,524✔
1133
        return entry.changeset;
63,524✔
1134
    }
69,908✔
1135

1136
    void set_reciprocal_transform(version_type server_version, BinaryData transform) override final
1137
    {
3,466✔
1138
        m_recip_hist.set(server_version, transform); // Throws
3,466✔
1139
    }
3,466✔
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
{
39,690✔
1152
    std::size_t remote_file_index = std::size_t(remote_file_ident);
39,690✔
1153
    REALM_ASSERT(remote_file_index < m_num_client_files);
39,690✔
1154
    bool from_downstream = (remote_file_ident != 0);
39,690✔
1155
    if (from_downstream) {
39,690✔
1156
        auto client_type = ClientType(m_acc->cf_client_types.get(remote_file_index));
39,690✔
1157
        REALM_ASSERT_RELEASE(is_direct_client(client_type));
39,690✔
1158
        std::int_fast64_t last_seen_timestamp = m_acc->cf_last_seen_timestamps.get(remote_file_index);
39,690✔
1159
        bool expired = (last_seen_timestamp == 0);
39,690✔
1160
        REALM_ASSERT_RELEASE(!expired);
39,690✔
1161
    }
39,690✔
1162
    version_type orig_client_version = version_type(m_acc->cf_client_versions.get(remote_file_index));
39,690✔
1163
    version_type recip_hist_base_version = version_type(m_acc->cf_rh_base_versions.get(remote_file_index));
39,690✔
1164
    ReciprocalHistory recip_hist(m_acc->cf_recip_hist_refs, remote_file_index,
39,690✔
1165
                                 recip_hist_base_version); // Throws
39,690✔
1166

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

1180
    if (num_changesets > 0) {
39,690✔
1181
        recip_hist.ensure_instantiated(); // Throws
20,534✔
1182

1183
        // Parse the changesets
1184
        std::vector<Changeset> parsed_transformed_changesets;
20,534✔
1185
        parsed_transformed_changesets.resize(num_changesets);
20,534✔
1186
        for (std::size_t i = 0; i < num_changesets; ++i)
54,994✔
1187
            parse_remote_changeset(changesets[i], parsed_transformed_changesets[i]); // Throws
34,460✔
1188

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

1207
        for (std::size_t i = 0; i < num_changesets; ++i) {
54,976✔
1208
            REALM_ASSERT(get_instruction_encoder().buffer().size() == 0);
34,442✔
1209
            const Changeset& changeset = parsed_transformed_changesets[i];
34,442✔
1210

1211
            HistoryEntry entry;
34,442✔
1212
            entry.origin_timestamp = changeset.origin_timestamp;
34,442✔
1213
            entry.origin_file_ident = changeset.origin_file_ident;
34,442✔
1214
            entry.remote_version = changeset.version;
34,442✔
1215

1216
            ChangesetEncoder::Buffer changeset_buffer;
34,442✔
1217

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

1221
            add_sync_history_entry(entry); // Throws
34,442✔
1222
        }
34,442✔
1223
    }
20,534✔
1224

1225
    bool dirty = (num_changesets > 0);
39,690✔
1226

1227
    if (update_upload_progress(orig_client_version, recip_hist, upload_progress)) // Throws
39,690✔
1228
        dirty = true;
39,666✔
1229

1230
    if (from_downstream) {
39,690✔
1231
        version_type orig_version = version_type(m_acc->cf_locked_server_versions.get(remote_file_index));
39,666✔
1232
        if (locked_server_version > orig_version) {
39,666✔
1233
            m_acc->cf_locked_server_versions.set(remote_file_index,
29,104✔
1234
                                                 std::int_fast64_t(locked_server_version)); // Throws
29,104✔
1235
            dirty = true;
29,104✔
1236
        }
29,104✔
1237
    }
39,666✔
1238

1239
    if (from_downstream && dirty) {
39,690✔
1240
        m_acc->cf_last_seen_timestamps.set(remote_file_index, 1);
39,666✔
1241
    }
39,666✔
1242

1243
    return dirty;
39,690✔
1244
}
39,690✔
1245

1246

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

1270

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

1279

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

1286

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

1293

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

1303

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

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

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

1323

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

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

1338

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

1345
    bool nonempty_changeset_of_local_origin = (m_is_local_changeset && size != 0);
43,264✔
1346

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

1356
        add_sync_history_entry(entry); // Throws
4,812✔
1357
    }
4,812✔
1358

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

1364
    return m_ct_base_version + m_ct_history_size; // New snapshot number
43,264✔
1365
}
43,264✔
1366

1367

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

1376

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

1392

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

1403

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

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

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

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

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

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

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

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

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

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

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

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

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

1646

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

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

1667

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

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

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

1710
    m_num_client_files = m_acc->cf_ident_salts.size();
93,592✔
1711
    REALM_ASSERT(m_acc->cf_client_versions.size() == m_num_client_files);
93,592✔
1712
    REALM_ASSERT(m_acc->cf_rh_base_versions.size() == m_num_client_files);
93,592✔
1713
    REALM_ASSERT(m_acc->cf_recip_hist_refs.size() == m_num_client_files);
93,592✔
1714
    REALM_ASSERT(m_acc->cf_proxy_files.size() == m_num_client_files);
93,592✔
1715
    REALM_ASSERT(m_acc->cf_client_types.size() == m_num_client_files);
93,592✔
1716
    REALM_ASSERT(m_acc->cf_last_seen_timestamps.size() == m_num_client_files);
93,592✔
1717
    REALM_ASSERT(m_acc->cf_locked_server_versions.size() == m_num_client_files);
93,592✔
1718

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

1727
    m_server_version_salt =
93,592✔
1728
        (m_history_size > 0 ? salt_type(m_acc->sh_version_salts.get(m_history_size - 1))
93,592✔
1729
                            : salt_type(m_acc->root.get_as_ref_or_tagged(s_base_version_salt_iip).get_as_int()));
93,592✔
1730

1731
    m_ct_history_size = m_acc->ct_history.size();
93,592✔
1732
    m_ct_base_version = realm_version - m_ct_history_size;
93,592✔
1733
}
93,592✔
1734

1735

1736
void ServerHistory::Accessors::init_from_ref(ref_type ref)
1737
{
93,596✔
1738
    root.init_from_ref(ref);
93,596✔
1739
    client_files.init_from_parent();
93,596✔
1740
    sync_history.init_from_parent();
93,596✔
1741

1742
    {
93,596✔
1743
        ref_type ref_2 = upstream_status.get_ref_from_parent();
93,596✔
1744
        if (ref_2 != 0) {
93,596✔
1745
            upstream_status.init_from_ref(ref_2);
×
1746
        }
×
1747
        else {
93,596✔
1748
            upstream_status.detach();
93,596✔
1749
        }
93,596✔
1750
    }
93,596✔
1751

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

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

1774

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

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

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

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

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

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

1825
    client_files.create(Array::type_HasRefs, context_flag_no, s_client_files_size); // Throws
880✔
1826
    client_files.update_parent();                                                   // Throws
880✔
1827

1828
    sync_history.create(Array::type_HasRefs, context_flag_no, s_sync_history_size); // Throws
880✔
1829
    sync_history.update_parent();                                                   // Throws
880✔
1830

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

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

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

1856
    ct_history.create(); // Throws
880✔
1857

1858
    destroy_guard.release();
880✔
1859
    root.update_parent(); // Throws
880✔
1860
}
880✔
1861

1862

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

1873

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

1884

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

1889
    if (changeset.is_null())
43,262✔
1890
        changeset = BinaryData("", 0);
2,156✔
1891

1892
    m_acc->ct_history.add(changeset); // Throws
43,262✔
1893
    ++m_ct_history_size;
43,262✔
1894
}
43,262✔
1895

1896

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

1906
    std::int_fast64_t client_file = std::int_fast64_t(entry.origin_file_ident);
39,254✔
1907
    std::int_fast64_t client_version = std::int_fast64_t(entry.remote_version);
39,254✔
1908
    std::int_fast64_t timestamp = std::int_fast64_t(entry.origin_timestamp);
39,254✔
1909

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

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

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

1931
    ++m_history_size;
39,254✔
1932
    m_server_version_salt = m_salt_for_new_server_versions;
39,254✔
1933
}
39,254✔
1934

1935

1936
void ServerHistory::trim_cont_transact_history()
1937
{
43,262✔
1938
    REALM_ASSERT(m_acc->ct_history.size() == m_ct_history_size);
43,262✔
1939

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

1962

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

1970

1971
// Skips history entries with empty changesets, and history entries produced by
1972
// integration of changes received from the specified remote file.
1973
//
1974
// Pass zero for `remote_file_ident` if the remote file is on the upstream
1975
// server, or the reference file.
1976
//
1977
// Returns zero if no history entry was found. Otherwise it returns the version
1978
// produced by the changeset of the located history entry.
1979
auto ServerHistory::find_history_entry(file_ident_type remote_file_ident, version_type begin_version,
1980
                                       version_type end_version, HistoryEntry& entry,
1981
                                       version_type& last_integrated_remote_version) const noexcept -> version_type
1982
{
242,778✔
1983
    REALM_ASSERT(remote_file_ident != g_root_node_file_ident);
242,778✔
1984
    REALM_ASSERT(begin_version >= m_history_base_version);
242,778✔
1985
    REALM_ASSERT(begin_version <= end_version);
242,778✔
1986
    auto server_version = begin_version;
242,778✔
1987
    while (server_version < end_version) {
357,146✔
1988
        ++server_version;
288,726✔
1989
        // FIXME: Find a way to avoid dynamically allocating a buffer for, and
1990
        // copying the changeset for all the skipped history entries.
1991
        HistoryEntry entry_2 = get_history_entry(server_version);
288,726✔
1992
        bool received_from_client = received_from(entry_2, remote_file_ident);
288,726✔
1993
        if (received_from_client) {
288,726✔
1994
            last_integrated_remote_version = entry_2.remote_version;
90,028✔
1995
            continue;
90,028✔
1996
        }
90,028✔
1997
        if (entry_2.changeset.size() == 0)
198,698✔
1998
            continue; // Empty
24,340✔
1999
        // These changes were not received from the specified client, and the
2000
        // changeset was not empty.
2001
        entry = entry_2;
174,358✔
2002
        return server_version;
174,358✔
2003
    }
198,698✔
2004
    return 0;
68,420✔
2005
}
242,778✔
2006

2007

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

2024

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

2050

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

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

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

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

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

2105
    hc.servers_client_file_ident = m_local_file_ident;
×
2106

2107
    return hc;
×
2108
}
×
2109

2110

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

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

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

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

2134
    auto get_table_for_class = [&](StringData class_name) -> ConstTableRef {
×
2135
        Group::TableNameBuffer buffer;
×
2136
        return group.get_table(Group::class_name_to_table_name(class_name, buffer));
×
2137
    };
×
2138

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

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

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

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

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

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

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

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

2202

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

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

2247

2248
std::ostream& _impl::operator<<(std::ostream& out, const ServerHistory::HistoryContents& hc)
2249
{
×
2250
    out << "client files:\n";
×
2251
    for (std::size_t i = 0; i < hc.client_files.size(); ++i) {
×
2252
        out << "\n";
×
2253
        out << "  client_file_ident = " << i << "\n";
×
2254
        out << "  ident_salt = " << hc.client_files[i].ident_salt << "\n";
×
2255
        out << "  client_version = " << hc.client_files[i].client_version << "\n";
×
2256
        out << "  rh_base_version = " << hc.client_files[i].rh_base_version << "\n";
×
2257
        out << "  proxy_file = " << hc.client_files[i].proxy_file << "\n";
×
2258
        out << "  client_type = " << hc.client_files[i].client_type << "\n";
×
2259
        out << "  locked_server_version = " << hc.client_files[i].locked_server_version << "\n";
×
2260
        out << "  reciprocal history:\n";
×
2261
        for (const util::Optional<std::string>& transform : hc.client_files[i].reciprocal_history) {
×
2262
            if (transform) {
×
2263
                out << "    " << util::hex_dump((*transform).data(), (*transform).size()) << "\n";
×
2264
            }
×
2265
            else {
×
2266
                out << "    NULL\n";
×
2267
            }
×
2268
        }
×
2269
        out << "\n";
×
2270
    }
×
2271
    out << "\n";
×
2272

2273
    out << "history_base_version = " << hc.history_base_version << "\n";
×
2274
    out << "base_version_salt = " << hc.base_version_salt << "\n";
×
2275
    out << "\n";
×
2276

2277
    out << "history entries:\n";
×
2278
    for (std::size_t i = 0; i < hc.sync_history.size(); ++i) {
×
2279
        out << "\n";
×
2280
        out << "  version_salt = " << hc.sync_history[i].version_salt << "\n";
×
2281
        out << "  client_file_ident = " << hc.sync_history[i].client_file_ident << "\n";
×
2282
        out << "  client_version = " << hc.sync_history[i].client_version << "\n";
×
2283
        out << "  timestamp = " << hc.sync_history[i].timestamp << "\n";
×
2284
        out << "  cumul_byte_size = " << hc.sync_history[i].cumul_byte_size << "\n";
×
2285
        const std::string& changeset = hc.sync_history[i].changeset;
×
2286
        out << "  changeset = " << util::hex_dump(changeset.data(), changeset.size()) << "\n";
×
2287
        out << "\n";
×
2288
    }
×
2289
    out << "\n";
×
2290

2291
    out << "servers_client_file_ident = " << hc.servers_client_file_ident << "\n";
×
2292

2293
    return out;
×
2294
}
×
2295

2296
bool _impl::operator==(const ServerHistory::HistoryContents& hc_1, const ServerHistory::HistoryContents& hc_2)
2297
{
×
2298
    if (hc_1.client_files.size() != hc_2.client_files.size())
×
2299
        return false;
×
2300

2301
    for (std::size_t i = 0; i < hc_1.client_files.size(); ++i) {
×
2302
        ServerHistory::HistoryContents::ClientFile cf_1 = hc_1.client_files[i];
×
2303
        ServerHistory::HistoryContents::ClientFile cf_2 = hc_2.client_files[i];
×
2304

2305
        bool partially_equal =
×
2306
            (cf_1.ident_salt == cf_2.ident_salt && cf_1.client_version == cf_2.client_version &&
×
2307
             cf_1.rh_base_version == cf_2.rh_base_version && cf_1.proxy_file == cf_2.proxy_file &&
×
2308
             cf_1.client_type == cf_2.client_type && cf_1.locked_server_version == cf_2.locked_server_version &&
×
2309
             cf_1.reciprocal_history.size() == cf_2.reciprocal_history.size());
×
2310
        if (!partially_equal)
×
2311
            return false;
×
2312

2313
        for (std::size_t j = 0; j < cf_1.reciprocal_history.size(); ++j) {
×
2314
            if (cf_1.reciprocal_history[j] != cf_2.reciprocal_history[j])
×
2315
                return false;
×
2316
        }
×
2317
    }
×
2318

2319
    bool same_base_version =
×
2320
        (hc_1.history_base_version == hc_2.history_base_version && hc_1.base_version_salt == hc_2.base_version_salt);
×
2321
    if (!same_base_version)
×
2322
        return false;
×
2323

2324
    if (hc_1.sync_history.size() != hc_2.sync_history.size())
×
2325
        return false;
×
2326

2327
    for (std::size_t i = 0; i < hc_1.sync_history.size(); ++i) {
×
2328
        ServerHistory::HistoryContents::HistoryEntry sh_1 = hc_1.sync_history[i];
×
2329
        ServerHistory::HistoryContents::HistoryEntry sh_2 = hc_2.sync_history[i];
×
2330
        bool equal = (sh_1.version_salt == sh_2.version_salt && sh_1.client_file_ident == sh_2.client_file_ident &&
×
2331
                      sh_1.client_version == sh_2.client_version && sh_1.timestamp == sh_2.timestamp &&
×
2332
                      sh_1.cumul_byte_size == sh_2.cumul_byte_size);
×
2333
        if (!equal)
×
2334
            return false;
×
2335
    }
×
2336

2337
    if (hc_1.servers_client_file_ident != hc_2.servers_client_file_ident)
×
2338
        return false;
×
2339

2340
    return true;
×
2341
}
×
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