commit_contribution_impl.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/sync/engine/commit_contribution_impl.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <utility>
  8. #include "base/guid.h"
  9. #include "base/logging.h"
  10. #include "base/values.h"
  11. #include "components/sync/base/features.h"
  12. #include "components/sync/base/time.h"
  13. #include "components/sync/base/unique_position.h"
  14. #include "components/sync/engine/commit_and_get_updates_types.h"
  15. #include "components/sync/engine/cycle/entity_change_metric_recording.h"
  16. #include "components/sync/engine/model_type_worker.h"
  17. #include "components/sync/protocol/entity_specifics.pb.h"
  18. #include "components/sync/protocol/proto_value_conversions.h"
  19. #include "components/sync/protocol/sync.pb.h"
  20. #include "components/sync/protocol/sync_entity.pb.h"
  21. namespace syncer {
  22. namespace {
  23. CommitResponseData BuildCommitResponseData(
  24. const CommitRequestData& commit_request,
  25. const sync_pb::CommitResponse_EntryResponse& entry_response) {
  26. CommitResponseData response_data;
  27. response_data.id = entry_response.id_string();
  28. response_data.response_version = entry_response.version();
  29. response_data.client_tag_hash = commit_request.entity->client_tag_hash;
  30. response_data.sequence_number = commit_request.sequence_number;
  31. response_data.specifics_hash = commit_request.specifics_hash;
  32. response_data.unsynced_time = commit_request.unsynced_time;
  33. return response_data;
  34. }
  35. FailedCommitResponseData BuildFailedCommitResponseData(
  36. const CommitRequestData& commit_request,
  37. const sync_pb::CommitResponse_EntryResponse& entry_response) {
  38. FailedCommitResponseData response_data;
  39. response_data.client_tag_hash = commit_request.entity->client_tag_hash;
  40. response_data.response_type = entry_response.response_type();
  41. response_data.datatype_specific_error =
  42. entry_response.datatype_specific_error();
  43. return response_data;
  44. }
  45. } // namespace
  46. CommitContributionImpl::CommitContributionImpl(
  47. ModelType type,
  48. const sync_pb::DataTypeContext& context,
  49. CommitRequestDataList commit_requests,
  50. base::OnceCallback<void(const CommitResponseDataList&,
  51. const FailedCommitResponseDataList&)>
  52. on_commit_response_callback,
  53. base::OnceCallback<void(SyncCommitError)> on_full_commit_failure_callback,
  54. Cryptographer* cryptographer,
  55. PassphraseType passphrase_type,
  56. bool only_commit_specifics)
  57. : type_(type),
  58. on_commit_response_callback_(std::move(on_commit_response_callback)),
  59. on_full_commit_failure_callback_(
  60. std::move(on_full_commit_failure_callback)),
  61. cryptographer_(cryptographer),
  62. passphrase_type_(passphrase_type),
  63. context_(context),
  64. commit_requests_(std::move(commit_requests)),
  65. only_commit_specifics_(only_commit_specifics) {}
  66. CommitContributionImpl::~CommitContributionImpl() = default;
  67. void CommitContributionImpl::AddToCommitMessage(
  68. sync_pb::ClientToServerMessage* msg) {
  69. sync_pb::CommitMessage* commit_message = msg->mutable_commit();
  70. entries_start_index_ = commit_message->entries_size();
  71. commit_message->mutable_entries()->Reserve(commit_message->entries_size() +
  72. commit_requests_.size());
  73. for (const std::unique_ptr<CommitRequestData>& commit_request :
  74. commit_requests_) {
  75. sync_pb::SyncEntity* sync_entity = commit_message->add_entries();
  76. if (only_commit_specifics_) {
  77. DCHECK(!commit_request->entity->is_deleted());
  78. DCHECK(!cryptographer_);
  79. // Only send specifics to server for commit-only types.
  80. sync_entity->mutable_specifics()->CopyFrom(
  81. commit_request->entity->specifics);
  82. } else {
  83. PopulateCommitProto(type_, *commit_request, sync_entity);
  84. AdjustCommitProto(sync_entity);
  85. }
  86. // Purposefully crash if we have client only data, as this could result in
  87. // sending password in plain text.
  88. CHECK(
  89. !sync_entity->specifics().password().has_client_only_encrypted_data());
  90. if (commit_request->entity->is_deleted()) {
  91. RecordEntityChangeMetrics(type_, ModelTypeEntityChange::kLocalDeletion);
  92. } else if (commit_request->base_version <= 0) {
  93. RecordEntityChangeMetrics(type_, ModelTypeEntityChange::kLocalCreation);
  94. } else {
  95. RecordEntityChangeMetrics(type_, ModelTypeEntityChange::kLocalUpdate);
  96. }
  97. }
  98. if (!context_.context().empty())
  99. commit_message->add_client_contexts()->CopyFrom(context_);
  100. }
  101. SyncerError CommitContributionImpl::ProcessCommitResponse(
  102. const sync_pb::ClientToServerResponse& response,
  103. StatusController* status) {
  104. CommitResponseDataList success_response_list;
  105. FailedCommitResponseDataList error_response_list;
  106. bool has_unknown_error = false;
  107. bool has_conflicting_commits = false;
  108. bool has_transient_error_commits = false;
  109. for (size_t i = 0; i < commit_requests_.size(); ++i) {
  110. // Fill |success_response_list| or |error_response_list|.
  111. const sync_pb::CommitResponse_EntryResponse& entry_response =
  112. response.commit().entryresponse(entries_start_index_ + i);
  113. if (entry_response.response_type() == sync_pb::CommitResponse::SUCCESS) {
  114. success_response_list.push_back(
  115. BuildCommitResponseData(*commit_requests_[i], entry_response));
  116. } else {
  117. error_response_list.push_back(
  118. BuildFailedCommitResponseData(*commit_requests_[i], entry_response));
  119. }
  120. // Update |status| and mark the presence of specific errors (e.g.
  121. // conflicting commits).
  122. switch (entry_response.response_type()) {
  123. case sync_pb::CommitResponse::SUCCESS:
  124. status->increment_num_successful_commits();
  125. if (type_ == BOOKMARKS) {
  126. status->increment_num_successful_bookmark_commits();
  127. }
  128. break;
  129. case sync_pb::CommitResponse::INVALID_MESSAGE:
  130. DLOG(ERROR) << "Server reports commit message is invalid.";
  131. has_unknown_error = true;
  132. break;
  133. case sync_pb::CommitResponse::CONFLICT:
  134. DVLOG(1) << "Server reports conflict for commit message.";
  135. status->increment_num_server_conflicts();
  136. has_conflicting_commits = true;
  137. break;
  138. case sync_pb::CommitResponse::OVER_QUOTA:
  139. case sync_pb::CommitResponse::RETRY:
  140. case sync_pb::CommitResponse::TRANSIENT_ERROR:
  141. DLOG(WARNING) << "Entity commit blocked by transient error.";
  142. has_transient_error_commits = true;
  143. break;
  144. }
  145. }
  146. // Send whatever successful and failed responses we did get back to our
  147. // parent. It's the schedulers job to handle the failures, but parent may
  148. // react to them as well.
  149. std::move(on_commit_response_callback_)
  150. .Run(success_response_list, error_response_list);
  151. // Commit was successfully processed. We do not want to call both
  152. // |on_commit_response_callback_| and |on_full_commit_failure_callback_|.
  153. on_full_commit_failure_callback_.Reset();
  154. // Let the scheduler know about the failures.
  155. if (has_unknown_error) {
  156. return SyncerError(SyncerError::SERVER_RETURN_UNKNOWN_ERROR);
  157. }
  158. if (has_transient_error_commits) {
  159. return SyncerError(SyncerError::SERVER_RETURN_TRANSIENT_ERROR);
  160. }
  161. if (has_conflicting_commits) {
  162. return SyncerError(SyncerError::SERVER_RETURN_CONFLICT);
  163. }
  164. return SyncerError(SyncerError::SYNCER_OK);
  165. }
  166. void CommitContributionImpl::ProcessCommitFailure(
  167. SyncCommitError commit_error) {
  168. std::move(on_full_commit_failure_callback_).Run(commit_error);
  169. on_commit_response_callback_.Reset();
  170. }
  171. size_t CommitContributionImpl::GetNumEntries() const {
  172. return commit_requests_.size();
  173. }
  174. // static
  175. void CommitContributionImpl::PopulateCommitProto(
  176. ModelType type,
  177. const CommitRequestData& commit_entity,
  178. sync_pb::SyncEntity* commit_proto) {
  179. const EntityData& entity_data = *commit_entity.entity;
  180. DCHECK(!entity_data.specifics.has_encrypted());
  181. commit_proto->set_id_string(entity_data.id);
  182. if (type == NIGORI) {
  183. // Client tags are irrelevant for NIGORI since it uses the root node. For
  184. // historical reasons (although it's unclear if this continues to be
  185. // needed), the root node is considered a folder.
  186. commit_proto->set_folder(true);
  187. } else if (type != BOOKMARKS ||
  188. !entity_data.client_tag_hash.value().empty()) {
  189. // The client tag is mandatory for all datatypes except bookmarks, and
  190. // experimental for bookmarks (behind feature toggle).
  191. commit_proto->set_client_defined_unique_tag(
  192. entity_data.client_tag_hash.value());
  193. }
  194. commit_proto->set_version(commit_entity.base_version);
  195. commit_proto->set_deleted(entity_data.is_deleted());
  196. commit_proto->set_name(entity_data.name);
  197. if (!entity_data.is_deleted()) {
  198. // Handle bookmarks separately.
  199. if (type == BOOKMARKS) {
  200. // Populate SyncEntity.folder for backward-compatibility.
  201. switch (entity_data.specifics.bookmark().type()) {
  202. case sync_pb::BookmarkSpecifics::UNSPECIFIED:
  203. NOTREACHED();
  204. break;
  205. case sync_pb::BookmarkSpecifics::URL:
  206. commit_proto->set_folder(false);
  207. break;
  208. case sync_pb::BookmarkSpecifics::FOLDER:
  209. commit_proto->set_folder(true);
  210. break;
  211. }
  212. const UniquePosition unique_position = UniquePosition::FromProto(
  213. entity_data.specifics.bookmark().unique_position());
  214. DCHECK(unique_position.IsValid());
  215. *commit_proto->mutable_unique_position() = unique_position.ToProto();
  216. // parent_id field is set only for legacy clients only, before M99.
  217. if (!entity_data.legacy_parent_id.empty()) {
  218. commit_proto->set_parent_id_string(entity_data.legacy_parent_id);
  219. }
  220. }
  221. commit_proto->set_ctime(TimeToProtoTime(entity_data.creation_time));
  222. commit_proto->set_mtime(TimeToProtoTime(entity_data.modification_time));
  223. commit_proto->mutable_specifics()->CopyFrom(entity_data.specifics);
  224. }
  225. }
  226. void CommitContributionImpl::AdjustCommitProto(
  227. sync_pb::SyncEntity* commit_proto) {
  228. if (commit_proto->version() == kUncommittedVersion) {
  229. commit_proto->set_version(0);
  230. // Initial commits need our help to generate a client ID if they don't have
  231. // any. Bookmarks create their own IDs on the frontend side to be able to
  232. // match them after commits. For other data types we generate one here. And
  233. // since bookmarks don't have client tags, their server id should be stable
  234. // across restarts in case of recommitting an item, it doesn't result in
  235. // creating a duplicate.
  236. if (commit_proto->id_string().empty()) {
  237. commit_proto->set_id_string(base::GenerateGUID());
  238. }
  239. }
  240. // Encrypt the specifics and hide the title if necessary.
  241. if (commit_proto->specifics().has_password()) {
  242. DCHECK(cryptographer_);
  243. const sync_pb::PasswordSpecifics& password_specifics =
  244. commit_proto->specifics().password();
  245. const sync_pb::PasswordSpecificsData& password_data =
  246. password_specifics.client_only_encrypted_data();
  247. sync_pb::EntitySpecifics encrypted_password;
  248. if (!IsExplicitPassphrase(passphrase_type_) &&
  249. password_specifics.unencrypted_metadata().url() !=
  250. password_data.signon_realm()) {
  251. encrypted_password.mutable_password()
  252. ->mutable_unencrypted_metadata()
  253. ->set_url(password_data.signon_realm());
  254. encrypted_password.mutable_password()
  255. ->mutable_unencrypted_metadata()
  256. ->set_blacklisted(password_data.blacklisted());
  257. }
  258. bool result = cryptographer_->Encrypt(
  259. password_data,
  260. encrypted_password.mutable_password()->mutable_encrypted());
  261. DCHECK(result);
  262. if (base::FeatureList::IsEnabled(
  263. syncer::kReadWritePasswordNotesBackupField)) {
  264. // `encrypted_notes_backup` field needs to be populated regardless of
  265. // whether or not there are any notes.
  266. result = cryptographer_->Encrypt(password_data.notes(),
  267. encrypted_password.mutable_password()
  268. ->mutable_encrypted_notes_backup());
  269. DCHECK(result);
  270. // When encrypting both blobs succeeds, both encrypted blobs must use the
  271. // key name.
  272. DCHECK_EQ(
  273. encrypted_password.password().encrypted().key_name(),
  274. encrypted_password.password().encrypted_notes_backup().key_name());
  275. }
  276. *commit_proto->mutable_specifics() = std::move(encrypted_password);
  277. commit_proto->set_name("encrypted");
  278. } else if (cryptographer_) {
  279. if (commit_proto->has_specifics()) {
  280. sync_pb::EntitySpecifics encrypted_specifics;
  281. bool result = cryptographer_->Encrypt(
  282. commit_proto->specifics(), encrypted_specifics.mutable_encrypted());
  283. DCHECK(result);
  284. commit_proto->mutable_specifics()->CopyFrom(encrypted_specifics);
  285. }
  286. commit_proto->set_name("encrypted");
  287. }
  288. // See crbug.com/915133: Certain versions of Chrome (e.g. M71) handle corrupt
  289. // SESSIONS data poorly. Let's guard against future versions from committing
  290. // problematic data that could cause crashes on other syncing devices.
  291. if (commit_proto->specifics().session().has_tab()) {
  292. CHECK_GE(commit_proto->specifics().session().tab_node_id(), 0);
  293. }
  294. // Always include enough specifics to identify the type. Do this even in
  295. // deletion requests, where the specifics are otherwise invalid.
  296. AddDefaultFieldValue(type_, commit_proto->mutable_specifics());
  297. }
  298. } // namespace syncer