Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 1 | // Copyright 2013 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 | |
| 5 | #include "components/sync/engine_impl/process_updates_util.h" |
| 6 | |
| 7 | #include <stddef.h> |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 8 | |
| 9 | #include <string> |
| 10 | |
| 11 | #include "base/location.h" |
nikunjb | ddaa36a | 2016-10-19 04:12:55 | [diff] [blame] | 12 | #include "base/metrics/histogram_macros.h" |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 13 | #include "components/sync/base/cryptographer.h" |
| 14 | #include "components/sync/base/data_type_histogram.h" |
maxbogue | 3067278a | 2016-08-19 23:16:18 | [diff] [blame] | 15 | #include "components/sync/engine/cycle/update_counters.h" |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 16 | #include "components/sync/engine_impl/syncer_proto_util.h" |
| 17 | #include "components/sync/engine_impl/syncer_types.h" |
| 18 | #include "components/sync/engine_impl/syncer_util.h" |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 19 | #include "components/sync/syncable/directory.h" |
| 20 | #include "components/sync/syncable/model_neutral_mutable_entry.h" |
| 21 | #include "components/sync/syncable/syncable_model_neutral_write_transaction.h" |
| 22 | #include "components/sync/syncable/syncable_proto_util.h" |
| 23 | #include "components/sync/syncable/syncable_util.h" |
| 24 | |
| 25 | namespace syncer { |
| 26 | |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 27 | using syncable::GET_BY_ID; |
| 28 | |
| 29 | namespace { |
| 30 | |
| 31 | // This function attempts to determine whether or not this update is genuinely |
| 32 | // new, or if it is a reflection of one of our own commits. |
| 33 | // |
| 34 | // There is a known inaccuracy in its implementation. If this update ends up |
| 35 | // being applied to a local item with a different ID, we will count the change |
| 36 | // as being a non-reflection update. Fortunately, the server usually updates |
| 37 | // our IDs correctly in its commit response, so a new ID during GetUpdate should |
| 38 | // be rare. |
| 39 | // |
| 40 | // The only scenarios I can think of where this might happen are: |
| 41 | // - We commit a new item to the server, but we don't persist the |
| 42 | // server-returned new ID to the database before we shut down. On the GetUpdate |
| 43 | // following the next restart, we will receive an update from the server that |
| 44 | // updates its local ID. |
| 45 | // - When two attempts to create an item with identical UNIQUE_CLIENT_TAG values |
| 46 | // collide at the server. I have seen this in testing. When it happens, the |
| 47 | // test server will send one of the clients a response to upate its local ID so |
| 48 | // that both clients will refer to the item using the same ID going forward. In |
| 49 | // this case, we're right to assume that the update is not a reflection. |
| 50 | // |
| 51 | // For more information, see FindLocalIdToUpdate(). |
| 52 | bool UpdateContainsNewVersion(syncable::BaseTransaction* trans, |
| 53 | const sync_pb::SyncEntity& update) { |
| 54 | int64_t existing_version = -1; // The server always sends positive versions. |
| 55 | syncable::Entry existing_entry(trans, GET_BY_ID, |
| 56 | SyncableIdFromProto(update.id_string())); |
| 57 | if (existing_entry.good()) |
| 58 | existing_version = existing_entry.GetBaseVersion(); |
| 59 | |
| 60 | if (!existing_entry.good() && update.deleted()) { |
| 61 | // There are several possible explanations for this. The most common cases |
| 62 | // will be first time sync and the redelivery of deletions we've already |
| 63 | // synced, accepted, and purged from our database. In either case, the |
| 64 | // update is useless to us. Let's count them all as "not new", even though |
| 65 | // that may not always be entirely accurate. |
| 66 | return false; |
| 67 | } |
| 68 | |
| 69 | if (existing_entry.good() && !existing_entry.GetUniqueClientTag().empty() && |
| 70 | existing_entry.GetIsDel() && update.deleted()) { |
| 71 | // Unique client tags will have their version set to zero when they're |
| 72 | // deleted. The usual version comparison logic won't be able to detect |
| 73 | // reflections of these items. Instead, we assume any received tombstones |
| 74 | // are reflections. That should be correct most of the time. |
| 75 | return false; |
| 76 | } |
| 77 | |
| 78 | return existing_version < update.version(); |
| 79 | } |
| 80 | |
| 81 | // In the event that IDs match, but tags differ AttemptReuniteClient tag |
| 82 | // will have refused to unify the update. |
| 83 | // We should not attempt to apply it at all since it violates consistency |
| 84 | // rules. |
| 85 | VerifyResult VerifyTagConsistency( |
| 86 | const sync_pb::SyncEntity& entry, |
| 87 | const syncable::ModelNeutralMutableEntry& same_id) { |
| 88 | if (entry.has_client_defined_unique_tag() && |
| 89 | entry.client_defined_unique_tag() != same_id.GetUniqueClientTag()) { |
| 90 | return VERIFY_FAIL; |
| 91 | } |
| 92 | return VERIFY_UNDECIDED; |
| 93 | } |
| 94 | |
| 95 | // Checks whether or not an update is fit for processing. |
| 96 | // |
| 97 | // The answer may be "no" if the update appears invalid, or it's not releveant |
| 98 | // (ie. a delete for an item we've never heard of), or other reasons. |
| 99 | VerifyResult VerifyUpdate(syncable::ModelNeutralWriteTransaction* trans, |
| 100 | const sync_pb::SyncEntity& entry, |
| 101 | ModelType requested_type) { |
| 102 | syncable::Id id = SyncableIdFromProto(entry.id_string()); |
| 103 | VerifyResult result = VERIFY_FAIL; |
| 104 | |
| 105 | const bool deleted = entry.has_deleted() && entry.deleted(); |
| 106 | const bool is_directory = IsFolder(entry); |
| 107 | const ModelType model_type = GetModelType(entry); |
| 108 | |
| 109 | if (!id.ServerKnows()) { |
| 110 | LOG(ERROR) << "Illegal negative id in received updates"; |
| 111 | return result; |
| 112 | } |
| 113 | { |
| 114 | const std::string name = SyncerProtoUtil::NameFromSyncEntity(entry); |
| 115 | if (name.empty() && !deleted) { |
| 116 | LOG(ERROR) << "Zero length name in non-deleted update"; |
| 117 | return result; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | syncable::ModelNeutralMutableEntry same_id(trans, GET_BY_ID, id); |
| 122 | result = VerifyNewEntry(entry, &same_id, deleted); |
| 123 | |
| 124 | ModelType placement_type = |
| 125 | !deleted ? GetModelType(entry) : same_id.good() ? same_id.GetModelType() |
| 126 | : UNSPECIFIED; |
| 127 | |
| 128 | if (VERIFY_UNDECIDED == result) { |
| 129 | result = VerifyTagConsistency(entry, same_id); |
| 130 | } |
| 131 | |
| 132 | if (VERIFY_UNDECIDED == result) { |
| 133 | if (deleted) { |
| 134 | // For deletes the server could send tombostones for items that |
| 135 | // the client did not request. If so ignore those items. |
| 136 | if (IsRealDataType(placement_type) && requested_type != placement_type) { |
| 137 | result = VERIFY_SKIP; |
| 138 | } else { |
| 139 | result = VERIFY_SUCCESS; |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | // If we have an existing entry, we check here for updates that break |
| 145 | // consistency rules. |
| 146 | if (VERIFY_UNDECIDED == result) { |
| 147 | result = VerifyUpdateConsistency(trans, entry, deleted, is_directory, |
| 148 | model_type, &same_id); |
| 149 | } |
| 150 | |
| 151 | if (VERIFY_UNDECIDED == result) |
| 152 | result = VERIFY_SUCCESS; // No news is good news. |
| 153 | |
| 154 | return result; // This might be VERIFY_SUCCESS as well |
| 155 | } |
| 156 | |
| 157 | // Returns true if the entry is still ok to process. |
| 158 | bool ReverifyEntry(syncable::ModelNeutralWriteTransaction* trans, |
| 159 | const sync_pb::SyncEntity& entry, |
| 160 | syncable::ModelNeutralMutableEntry* same_id) { |
| 161 | const bool deleted = entry.has_deleted() && entry.deleted(); |
| 162 | const bool is_directory = IsFolder(entry); |
| 163 | const ModelType model_type = GetModelType(entry); |
| 164 | |
| 165 | return VERIFY_SUCCESS == VerifyUpdateConsistency(trans, entry, deleted, |
| 166 | is_directory, model_type, |
| 167 | same_id); |
| 168 | } |
| 169 | |
| 170 | // Process a single update. Will avoid touching global state. |
| 171 | // |
| 172 | // If the update passes a series of checks, this function will copy |
| 173 | // the SyncEntity's data into the SERVER side of the syncable::Directory. |
| 174 | void ProcessUpdate(const sync_pb::SyncEntity& update, |
| 175 | const Cryptographer* cryptographer, |
| 176 | syncable::ModelNeutralWriteTransaction* const trans) { |
| 177 | const syncable::Id& server_id = SyncableIdFromProto(update.id_string()); |
| 178 | const std::string name = SyncerProtoUtil::NameFromSyncEntity(update); |
| 179 | |
| 180 | // Look to see if there's a local item that should recieve this update, |
| 181 | // maybe due to a duplicate client tag or a lost commit response. |
| 182 | syncable::Id local_id = FindLocalIdToUpdate(trans, update); |
| 183 | |
| 184 | // FindLocalEntryToUpdate has veto power. |
| 185 | if (local_id.IsNull()) { |
| 186 | return; // The entry has become irrelevant. |
| 187 | } |
| 188 | |
| 189 | CreateNewEntry(trans, local_id); |
| 190 | |
| 191 | // We take a two step approach. First we store the entries data in the |
| 192 | // server fields of a local entry and then move the data to the local fields |
| 193 | syncable::ModelNeutralMutableEntry target_entry(trans, GET_BY_ID, local_id); |
| 194 | |
| 195 | // We need to run the Verify checks again; the world could have changed |
| 196 | // since we last verified. |
| 197 | if (!ReverifyEntry(trans, update, &target_entry)) { |
| 198 | return; // The entry has become irrelevant. |
| 199 | } |
| 200 | |
| 201 | // If we're repurposing an existing local entry with a new server ID, |
| 202 | // change the ID now, after we're sure that the update can succeed. |
| 203 | if (local_id != server_id) { |
| 204 | DCHECK(!update.deleted()); |
| 205 | ChangeEntryIDAndUpdateChildren(trans, &target_entry, server_id); |
| 206 | // When IDs change, versions become irrelevant. Forcing BASE_VERSION |
| 207 | // to zero would ensure that this update gets applied, but would indicate |
| 208 | // creation or undeletion if it were committed that way. Instead, prefer |
| 209 | // forcing BASE_VERSION to entry.version() while also forcing |
| 210 | // IS_UNAPPLIED_UPDATE to true. If the item is UNSYNCED, it's committable |
| 211 | // from the new state; it may commit before the conflict resolver gets |
| 212 | // a crack at it. |
| 213 | if (target_entry.GetIsUnsynced() || target_entry.GetBaseVersion() > 0) { |
| 214 | // If either of these conditions are met, then we can expect valid client |
| 215 | // fields for this entry. When BASE_VERSION is positive, consistency is |
| 216 | // enforced on the client fields at update-application time. Otherwise, |
| 217 | // we leave the BASE_VERSION field alone; it'll get updated the first time |
| 218 | // we successfully apply this update. |
| 219 | target_entry.PutBaseVersion(update.version()); |
| 220 | } |
| 221 | // Force application of this update, no matter what. |
| 222 | target_entry.PutIsUnappliedUpdate(true); |
| 223 | } |
| 224 | |
| 225 | // If this is a newly received undecryptable update, and the only thing that |
| 226 | // has changed are the specifics, store the original decryptable specifics, |
| 227 | // (on which any current or future local changes are based) before we |
| 228 | // overwrite SERVER_SPECIFICS. |
| 229 | // MTIME, CTIME, and NON_UNIQUE_NAME are not enforced. |
| 230 | |
| 231 | bool position_matches = false; |
| 232 | if (target_entry.ShouldMaintainPosition() && !update.deleted()) { |
| 233 | std::string update_tag = GetUniqueBookmarkTagFromUpdate(update); |
| 234 | if (UniquePosition::IsValidSuffix(update_tag)) { |
| 235 | position_matches = GetUpdatePosition(update, update_tag) |
| 236 | .Equals(target_entry.GetServerUniquePosition()); |
| 237 | } else { |
| 238 | NOTREACHED(); |
| 239 | } |
| 240 | } else { |
| 241 | // If this item doesn't care about positions, then set this flag to true. |
| 242 | position_matches = true; |
| 243 | } |
| 244 | |
| 245 | if (!update.deleted() && !target_entry.GetServerIsDel() && |
| 246 | (SyncableIdFromProto(update.parent_id_string()) == |
| 247 | target_entry.GetServerParentId()) && |
| 248 | position_matches && update.has_specifics() && |
| 249 | update.specifics().has_encrypted() && |
| 250 | !cryptographer->CanDecrypt(update.specifics().encrypted())) { |
| 251 | sync_pb::EntitySpecifics prev_specifics = target_entry.GetServerSpecifics(); |
| 252 | // We only store the old specifics if they were decryptable and applied and |
| 253 | // there is no BASE_SERVER_SPECIFICS already. Else do nothing. |
| 254 | if (!target_entry.GetIsUnappliedUpdate() && |
| 255 | !IsRealDataType( |
| 256 | GetModelTypeFromSpecifics(target_entry.GetBaseServerSpecifics())) && |
| 257 | (!prev_specifics.has_encrypted() || |
| 258 | cryptographer->CanDecrypt(prev_specifics.encrypted()))) { |
| 259 | DVLOG(2) << "Storing previous server specifcs: " |
| 260 | << prev_specifics.SerializeAsString(); |
| 261 | target_entry.PutBaseServerSpecifics(prev_specifics); |
| 262 | } |
| 263 | } else if (IsRealDataType(GetModelTypeFromSpecifics( |
| 264 | target_entry.GetBaseServerSpecifics()))) { |
| 265 | // We have a BASE_SERVER_SPECIFICS, but a subsequent non-specifics-only |
| 266 | // change arrived. As a result, we can't use the specifics alone to detect |
| 267 | // changes, so we clear BASE_SERVER_SPECIFICS. |
| 268 | target_entry.PutBaseServerSpecifics(sync_pb::EntitySpecifics()); |
| 269 | } |
| 270 | |
| 271 | UpdateServerFieldsFromUpdate(&target_entry, update, name); |
| 272 | |
| 273 | return; |
| 274 | } |
| 275 | |
| 276 | } // namespace |
| 277 | |
| 278 | void ProcessDownloadedUpdates(syncable::Directory* dir, |
| 279 | syncable::ModelNeutralWriteTransaction* trans, |
| 280 | ModelType type, |
| 281 | const SyncEntityList& applicable_updates, |
maxbogue | 3067278a | 2016-08-19 23:16:18 | [diff] [blame] | 282 | StatusController* status, |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 283 | UpdateCounters* counters) { |
Pavel Yatsuk | a0f2f3b5 | 2017-07-10 21:34:33 | [diff] [blame^] | 284 | for (const auto* update : applicable_updates) { |
| 285 | DCHECK_EQ(type, GetModelType(*update)); |
| 286 | if (!UpdateContainsNewVersion(trans, *update)) { |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 287 | status->increment_num_reflected_updates_downloaded_by(1); |
| 288 | counters->num_reflected_updates_received++; |
| 289 | } |
Pavel Yatsuk | a0f2f3b5 | 2017-07-10 21:34:33 | [diff] [blame^] | 290 | if (update->deleted()) { |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 291 | status->increment_num_tombstone_updates_downloaded_by(1); |
| 292 | counters->num_tombstone_updates_received++; |
| 293 | } |
Pavel Yatsuk | a0f2f3b5 | 2017-07-10 21:34:33 | [diff] [blame^] | 294 | VerifyResult verify_result = VerifyUpdate(trans, *update, type); |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 295 | if (verify_result != VERIFY_SUCCESS && verify_result != VERIFY_UNDELETE) |
| 296 | continue; |
Pavel Yatsuk | a0f2f3b5 | 2017-07-10 21:34:33 | [diff] [blame^] | 297 | ProcessUpdate(*update, dir->GetCryptographer(trans), trans); |
| 298 | if (update->ByteSize() > 0) { |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 299 | SyncRecordDatatypeBin("DataUse.Sync.Download.Bytes", |
Pavel Yatsuk | a0f2f3b5 | 2017-07-10 21:34:33 | [diff] [blame^] | 300 | ModelTypeToHistogramInt(type), update->ByteSize()); |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 301 | } |
| 302 | UMA_HISTOGRAM_SPARSE_SLOWLY("DataUse.Sync.Download.Count", |
| 303 | ModelTypeToHistogramInt(type)); |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | void ExpireEntriesByVersion(syncable::Directory* dir, |
| 308 | syncable::ModelNeutralWriteTransaction* trans, |
| 309 | ModelType type, |
| 310 | int64_t version_watermark) { |
| 311 | syncable::Directory::Metahandles handles; |
| 312 | dir->GetMetaHandlesOfType(trans, type, &handles); |
| 313 | for (size_t i = 0; i < handles.size(); ++i) { |
| 314 | syncable::ModelNeutralMutableEntry entry(trans, syncable::GET_BY_HANDLE, |
| 315 | handles[i]); |
| 316 | if (!entry.good() || !entry.GetId().ServerKnows() || |
| 317 | entry.GetUniqueServerTag() == ModelTypeToRootTag(type) || |
| 318 | entry.GetIsUnappliedUpdate() || entry.GetIsUnsynced() || |
| 319 | entry.GetIsDel() || entry.GetServerIsDel() || |
| 320 | entry.GetBaseVersion() >= version_watermark) { |
| 321 | continue; |
| 322 | } |
| 323 | |
| 324 | // Mark entry as unapplied update first to ensure journaling the deletion. |
| 325 | entry.PutIsUnappliedUpdate(true); |
| 326 | // Mark entry as deleted by server. |
| 327 | entry.PutServerIsDel(true); |
| 328 | entry.PutServerVersion(version_watermark); |
| 329 | } |
| 330 | } |
| 331 | |
gangwu | 47b9bf77 | 2017-07-05 23:36:24 | [diff] [blame] | 332 | void ExpireEntriesByAge(syncable::Directory* dir, |
| 333 | syncable::ModelNeutralWriteTransaction* trans, |
| 334 | ModelType type, |
| 335 | int32_t age_watermark_in_days) { |
| 336 | syncable::Directory::Metahandles handles; |
| 337 | base::Time to_be_expired = |
| 338 | base::Time::Now() - base::TimeDelta::FromDays(age_watermark_in_days); |
| 339 | dir->GetMetaHandlesOfType(trans, type, &handles); |
| 340 | for (size_t i = 0; i < handles.size(); ++i) { |
| 341 | syncable::ModelNeutralMutableEntry entry(trans, syncable::GET_BY_HANDLE, |
| 342 | handles[i]); |
| 343 | if (!entry.good() || !entry.GetId().ServerKnows() || |
| 344 | entry.GetUniqueServerTag() == ModelTypeToRootTag(type) || |
| 345 | entry.GetIsUnappliedUpdate() || entry.GetIsUnsynced() || |
| 346 | entry.GetIsDel() || entry.GetServerIsDel() || |
| 347 | entry.GetMtime() > to_be_expired) { |
| 348 | continue; |
| 349 | } |
| 350 | |
| 351 | // Mark entry as unapplied update first to ensure journaling the deletion. |
| 352 | entry.PutIsUnappliedUpdate(true); |
| 353 | // Mark entry as deleted by server. |
| 354 | entry.PutServerIsDel(true); |
| 355 | } |
| 356 | } |
| 357 | |
Max Bogue | fef332d | 2016-07-28 22:09:09 | [diff] [blame] | 358 | } // namespace syncer |