File: commit_contribution_impl.cc

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (314 lines) | stat: -rw-r--r-- 12,838 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/sync/engine/commit_contribution_impl.h"

#include <memory>
#include <string>
#include <utility>

#include "base/feature_list.h"
#include "base/logging.h"
#include "base/uuid.h"
#include "components/sync/base/data_type_histogram.h"
#include "components/sync/base/passphrase_enums.h"
#include "components/sync/base/time.h"
#include "components/sync/base/unique_position.h"
#include "components/sync/engine/commit_and_get_updates_types.h"
#include "components/sync/engine/cycle/entity_change_metric_recording.h"
#include "components/sync/engine/sync_protocol_error.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/proto_value_conversions.h"
#include "components/sync/protocol/sync.pb.h"
#include "components/sync/protocol/sync_entity.pb.h"

namespace syncer {

namespace {

// When enabled, all the fields for SyncEntity are populated for commit-only
// data types (otherwise, only `specifics` and `id_string` were populated).
BASE_FEATURE(kSyncPopulateAllFieldsForCommitOnlyTypes,
             "SyncPopulateAllFieldsForCommitOnlyTypes",
             base::FEATURE_ENABLED_BY_DEFAULT);

CommitResponseData BuildCommitResponseData(
    const CommitRequestData& commit_request,
    const sync_pb::CommitResponse_EntryResponse& entry_response) {
  CommitResponseData response_data;
  response_data.id = entry_response.id_string();
  response_data.response_version = entry_response.version();
  response_data.client_tag_hash = commit_request.entity->client_tag_hash;
  response_data.sequence_number = commit_request.sequence_number;
  response_data.specifics_hash = commit_request.specifics_hash;
  return response_data;
}

FailedCommitResponseData BuildFailedCommitResponseData(
    const CommitRequestData& commit_request,
    const sync_pb::CommitResponse_EntryResponse& entry_response) {
  FailedCommitResponseData response_data;
  response_data.client_tag_hash = commit_request.entity->client_tag_hash;
  response_data.response_type = entry_response.response_type();
  response_data.datatype_specific_error =
      entry_response.datatype_specific_error();
  return response_data;
}

}  // namespace

CommitContributionImpl::CommitContributionImpl(
    DataType type,
    const sync_pb::DataTypeContext& context,
    CommitRequestDataList commit_requests,
    base::OnceCallback<void(const CommitResponseDataList&,
                            const FailedCommitResponseDataList&)>
        on_commit_response_callback,
    base::OnceCallback<void(SyncCommitError)> on_full_commit_failure_callback,
    PassphraseType passphrase_type)
    : type_(type),
      on_commit_response_callback_(std::move(on_commit_response_callback)),
      on_full_commit_failure_callback_(
          std::move(on_full_commit_failure_callback)),
      passphrase_type_(passphrase_type),
      context_(context),
      commit_requests_(std::move(commit_requests)) {}

CommitContributionImpl::~CommitContributionImpl() = default;

void CommitContributionImpl::AddToCommitMessage(
    sync_pb::ClientToServerMessage* msg) {
  sync_pb::CommitMessage* commit_message = msg->mutable_commit();
  entries_start_index_ = commit_message->entries_size();

  commit_message->mutable_entries()->Reserve(commit_message->entries_size() +
                                             commit_requests_.size());

  for (const std::unique_ptr<CommitRequestData>& commit_request :
       commit_requests_) {
    sync_pb::SyncEntity* sync_entity = commit_message->add_entries();

    // Commit-only data types must never be encrypted or deleted.
    if (CommitOnlyTypes().Has(type_)) {
      CHECK(!commit_request->entity->specifics.has_encrypted());
      CHECK(!commit_request->entity->is_deleted());
    }

    if (CommitOnlyTypes().Has(type_) &&
        !base::FeatureList::IsEnabled(
            kSyncPopulateAllFieldsForCommitOnlyTypes)) {
      // Only send specifics to server for commit-only types.
      sync_entity->mutable_specifics()->CopyFrom(
          commit_request->entity->specifics);

      // Populate randomly-generated ID string similar to an uncommitted version
      // of normal data types.
      sync_entity->set_id_string(
          base::Uuid::GenerateRandomV4().AsLowercaseString());
    } else {
      PopulateCommitProto(type_, *commit_request, sync_entity);
      AdjustCommitProto(sync_entity);
    }

    // Purposefully crash if we have client only data, as this could result in
    // sending password in plain text.
    CHECK(
        !sync_entity->specifics().password().has_client_only_encrypted_data());
    CHECK(!sync_entity->specifics()
               .outgoing_password_sharing_invitation()
               .has_client_only_unencrypted_data());
    CHECK(!sync_entity->specifics()
               .incoming_password_sharing_invitation()
               .has_client_only_unencrypted_data());

    // Purposefully crash since no metadata should be uploaded if a custom
    // passphrase is set.
    CHECK(!IsExplicitPassphrase(passphrase_type_) ||
          !sync_entity->specifics().password().has_unencrypted_metadata());

    // Record the size of the sync entity being committed.
    syncer::SyncRecordDataTypeEntitySizeHistogram(
        type_, commit_request->entity->is_deleted(),
        sync_entity->specifics().ByteSizeLong(), sync_entity->ByteSizeLong());

    if (commit_request->entity->is_deleted()) {
      RecordEntityChangeMetrics(type_, DataTypeEntityChange::kLocalDeletion);
    } else if (commit_request->base_version <= 0) {
      RecordEntityChangeMetrics(type_, DataTypeEntityChange::kLocalCreation);
    } else {
      RecordEntityChangeMetrics(type_, DataTypeEntityChange::kLocalUpdate);
    }
  }

  if (!context_.context().empty()) {
    commit_message->add_client_contexts()->CopyFrom(context_);
  }
}

SyncerError CommitContributionImpl::ProcessCommitResponse(
    const sync_pb::ClientToServerResponse& response,
    StatusController* status) {
  CommitResponseDataList success_response_list;
  FailedCommitResponseDataList error_response_list;
  bool has_invalid_messages = false;
  bool has_conflicting_commits = false;
  bool has_transient_error_commits = false;

  for (size_t i = 0; i < commit_requests_.size(); ++i) {
    // Fill `success_response_list` or `error_response_list`.
    const sync_pb::CommitResponse_EntryResponse& entry_response =
        response.commit().entryresponse(entries_start_index_ + i);
    if (entry_response.response_type() == sync_pb::CommitResponse::SUCCESS) {
      success_response_list.push_back(
          BuildCommitResponseData(*commit_requests_[i], entry_response));
    } else {
      error_response_list.push_back(
          BuildFailedCommitResponseData(*commit_requests_[i], entry_response));
    }

    // Update `status` and mark the presence of specific errors (e.g.
    // conflicting commits).
    switch (entry_response.response_type()) {
      case sync_pb::CommitResponse::SUCCESS:
        status->increment_num_successful_commits();
        if (type_ == BOOKMARKS) {
          status->increment_num_successful_bookmark_commits();
        }
        break;
      case sync_pb::CommitResponse::INVALID_MESSAGE:
        DLOG(ERROR) << "Server reports commit message is invalid.";
        has_invalid_messages = true;
        break;
      case sync_pb::CommitResponse::CONFLICT:
        DVLOG(1) << "Server reports conflict for commit message.";
        status->increment_num_server_conflicts();
        has_conflicting_commits = true;
        break;
      case sync_pb::CommitResponse::OVER_QUOTA:
      case sync_pb::CommitResponse::RETRY:
      case sync_pb::CommitResponse::TRANSIENT_ERROR:
        DLOG(WARNING) << "Entity commit blocked by transient error.";
        has_transient_error_commits = true;
        break;
    }
  }

  // Send whatever successful and failed responses we did get back to our
  // parent. It's the schedulers job to handle the failures, but parent may
  // react to them as well.
  std::move(on_commit_response_callback_)
      .Run(success_response_list, error_response_list);

  // Commit was successfully processed. We do not want to call both
  // `on_commit_response_callback_` and `on_full_commit_failure_callback_`.
  on_full_commit_failure_callback_.Reset();

  // Let the scheduler know about the failures.
  if (has_invalid_messages) {
    return SyncerError::ProtocolError(SyncProtocolErrorType::INVALID_MESSAGE);
  }
  if (has_transient_error_commits) {
    return SyncerError::ProtocolError(SyncProtocolErrorType::TRANSIENT_ERROR);
  }
  if (has_conflicting_commits) {
    return SyncerError::ProtocolError(SyncProtocolErrorType::CONFLICT);
  }
  return SyncerError::Success();
}

void CommitContributionImpl::ProcessCommitFailure(
    SyncCommitError commit_error) {
  std::move(on_full_commit_failure_callback_).Run(commit_error);
  on_commit_response_callback_.Reset();
}

size_t CommitContributionImpl::GetNumEntries() const {
  return commit_requests_.size();
}

// static
void CommitContributionImpl::PopulateCommitProto(
    DataType type,
    const CommitRequestData& commit_entity,
    sync_pb::SyncEntity* commit_proto) {
  const EntityData& entity_data = *commit_entity.entity;

  commit_proto->set_id_string(entity_data.id);

  if (type == NIGORI) {
    // Client tags are irrelevant for NIGORI since it uses the root node. For
    // historical reasons (although it's unclear if this continues to be
    // needed), the root node is considered a folder.
    commit_proto->set_folder(true);
  } else if (type != BOOKMARKS ||
             !entity_data.client_tag_hash.value().empty()) {
    // The client tag is mandatory for all datatypes except bookmarks, and
    // for bookmarks it depends on the version of the browser that was used
    // to originally create the bookmark.
    commit_proto->set_client_tag_hash(entity_data.client_tag_hash.value());
  }

  commit_proto->set_version(commit_entity.base_version);
  commit_proto->set_deleted(entity_data.is_deleted());
  commit_proto->set_name(entity_data.name);
  commit_proto->set_mtime(TimeToProtoTime(entity_data.modification_time));
  if (entity_data.collaboration_metadata.has_value()) {
    // Only the collaboration ID is needed for the commit. Other fields are
    // populated by the server.
    commit_proto->mutable_collaboration()->set_collaboration_id(
        entity_data.collaboration_metadata->collaboration_id().value());
  }

  if (entity_data.is_deleted()) {
    if (entity_data.deletion_origin.has_value()) {
      *commit_proto->mutable_deletion_origin() = *entity_data.deletion_origin;
    }
  } else {
    // Handle bookmarks separately.
    if (type == BOOKMARKS) {
      // Populate SyncEntity.folder for backward-compatibility.
      commit_proto->set_folder(commit_entity.deprecated_bookmark_folder);
      CHECK(commit_entity.deprecated_bookmark_unique_position.IsValid());
      *commit_proto->mutable_unique_position() =
          commit_entity.deprecated_bookmark_unique_position.ToProto();

      // parent_id field is set only for legacy clients only, before M99.
      if (!entity_data.legacy_parent_id.empty()) {
        commit_proto->set_parent_id_string(entity_data.legacy_parent_id);
      }
    }
    commit_proto->set_ctime(TimeToProtoTime(entity_data.creation_time));
    commit_proto->mutable_specifics()->CopyFrom(entity_data.specifics);
  }
}

void CommitContributionImpl::AdjustCommitProto(
    sync_pb::SyncEntity* commit_proto) {
  if (commit_proto->version() == kUncommittedVersion) {
    commit_proto->set_version(0);
    // Initial commits need our help to generate a client ID if they don't have
    // any. Bookmarks create their own IDs on the frontend side to be able to
    // match them after commits. For other data types we generate one here. And
    // since bookmarks don't have client tags, their server id should be stable
    // across restarts in case of recommitting an item, it doesn't result in
    // creating a duplicate.
    if (commit_proto->id_string().empty()) {
      commit_proto->set_id_string(
          base::Uuid::GenerateRandomV4().AsLowercaseString());
    }
  }

  // See crbug.com/915133: Certain versions of Chrome (e.g. M71) handle corrupt
  // SESSIONS data poorly. Let's guard against future versions from committing
  // problematic data that could cause crashes on other syncing devices.
  if (commit_proto->specifics().session().has_tab()) {
    CHECK_GE(commit_proto->specifics().session().tab_node_id(), 0);
  }

  // Always include enough specifics to identify the type. Do this even in
  // deletion requests, where the specifics are otherwise invalid.
  AddDefaultFieldValue(type_, commit_proto->mutable_specifics());
}

}  // namespace syncer