| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 "chrome/browser/sync/engine/conflict_resolver.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <list> | |
| 9 #include <map> | |
| 10 #include <set> | |
| 11 | |
| 12 #include "base/location.h" | |
| 13 #include "base/metrics/histogram.h" | |
| 14 #include "chrome/browser/sync/engine/syncer.h" | |
| 15 #include "chrome/browser/sync/engine/syncer_util.h" | |
| 16 #include "chrome/browser/sync/protocol/service_constants.h" | |
| 17 #include "chrome/browser/sync/sessions/status_controller.h" | |
| 18 #include "chrome/browser/sync/syncable/syncable.h" | |
| 19 #include "chrome/browser/sync/util/cryptographer.h" | |
| 20 #include "sync/protocol/nigori_specifics.pb.h" | |
| 21 | |
| 22 using std::list; | |
| 23 using std::map; | |
| 24 using std::set; | |
| 25 using syncable::BaseTransaction; | |
| 26 using syncable::Directory; | |
| 27 using syncable::Entry; | |
| 28 using syncable::GetModelTypeFromSpecifics; | |
| 29 using syncable::Id; | |
| 30 using syncable::IsRealDataType; | |
| 31 using syncable::MutableEntry; | |
| 32 using syncable::WriteTransaction; | |
| 33 | |
| 34 namespace browser_sync { | |
| 35 | |
| 36 using sessions::ConflictProgress; | |
| 37 using sessions::StatusController; | |
| 38 | |
| 39 namespace { | |
| 40 | |
| 41 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; | |
| 42 | |
| 43 } // namespace | |
| 44 | |
| 45 ConflictResolver::ConflictResolver() { | |
| 46 } | |
| 47 | |
| 48 ConflictResolver::~ConflictResolver() { | |
| 49 } | |
| 50 | |
| 51 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { | |
| 52 // An update matches local actions, merge the changes. | |
| 53 // This is a little fishy because we don't actually merge them. | |
| 54 // In the future we should do a 3-way merge. | |
| 55 // With IS_UNSYNCED false, changes should be merged. | |
| 56 entry->Put(syncable::IS_UNSYNCED, false); | |
| 57 } | |
| 58 | |
| 59 void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans, | |
| 60 MutableEntry * entry) { | |
| 61 // This is similar to an overwrite from the old client. | |
| 62 // This is equivalent to a scenario where we got the update before we'd | |
| 63 // made our local client changes. | |
| 64 // TODO(chron): This is really a general property clobber. We clobber | |
| 65 // the server side property. Perhaps we should actually do property merging. | |
| 66 entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION)); | |
| 67 entry->Put(syncable::IS_UNAPPLIED_UPDATE, false); | |
| 68 } | |
| 69 | |
| 70 ConflictResolver::ProcessSimpleConflictResult | |
| 71 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, | |
| 72 const Id& id, | |
| 73 const Cryptographer* cryptographer, | |
| 74 StatusController* status) { | |
| 75 MutableEntry entry(trans, syncable::GET_BY_ID, id); | |
| 76 // Must be good as the entry won't have been cleaned up. | |
| 77 CHECK(entry.good()); | |
| 78 | |
| 79 // This function can only resolve simple conflicts. Simple conflicts have | |
| 80 // both IS_UNSYNCED and IS_UNAPPLIED_UDPATE set. | |
| 81 if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE) || | |
| 82 !entry.Get(syncable::IS_UNSYNCED)) { | |
| 83 // This is very unusual, but it can happen in tests. We may be able to | |
| 84 // assert NOTREACHED() here when those tests are updated. | |
| 85 return NO_SYNC_PROGRESS; | |
| 86 } | |
| 87 | |
| 88 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { | |
| 89 // we've both deleted it, so lets just drop the need to commit/update this | |
| 90 // entry. | |
| 91 entry.Put(syncable::IS_UNSYNCED, false); | |
| 92 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); | |
| 93 // we've made changes, but they won't help syncing progress. | |
| 94 // METRIC simple conflict resolved by merge. | |
| 95 return NO_SYNC_PROGRESS; | |
| 96 } | |
| 97 | |
| 98 // This logic determines "client wins" vs. "server wins" strategy picking. | |
| 99 // By the time we get to this point, we rely on the following to be true: | |
| 100 // a) We can decrypt both the local and server data (else we'd be in | |
| 101 // conflict encryption and not attempting to resolve). | |
| 102 // b) All unsynced changes have been re-encrypted with the default key ( | |
| 103 // occurs either in AttemptToUpdateEntry, SetPassphrase, or | |
| 104 // RefreshEncryption). | |
| 105 // c) Base_server_specifics having a valid datatype means that we received | |
| 106 // an undecryptable update that only changed specifics, and since then have | |
| 107 // not received any further non-specifics-only or decryptable updates. | |
| 108 // d) If the server_specifics match specifics, server_specifics are | |
| 109 // encrypted with the default key, and all other visible properties match, | |
| 110 // then we can safely ignore the local changes as redundant. | |
| 111 // e) Otherwise if the base_server_specifics match the server_specifics, no | |
| 112 // functional change must have been made server-side (else | |
| 113 // base_server_specifics would have been cleared), and we can therefore | |
| 114 // safely ignore the server changes as redundant. | |
| 115 // f) Otherwise, it's in general safer to ignore local changes, with the | |
| 116 // exception of deletion conflicts (choose to undelete) and conflicts | |
| 117 // where the non_unique_name or parent don't match. | |
| 118 if (!entry.Get(syncable::SERVER_IS_DEL)) { | |
| 119 // TODO(nick): The current logic is arbitrary; instead, it ought to be made | |
| 120 // consistent with the ModelAssociator behavior for a datatype. It would | |
| 121 // be nice if we could route this back to ModelAssociator code to pick one | |
| 122 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) | |
| 123 // are easily mergeable. | |
| 124 // See http://crbug.com/77339. | |
| 125 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == | |
| 126 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); | |
| 127 bool parent_matches = entry.Get(syncable::PARENT_ID) == | |
| 128 entry.Get(syncable::SERVER_PARENT_ID); | |
| 129 bool entry_deleted = entry.Get(syncable::IS_DEL); | |
| 130 | |
| 131 // This positional check is meant to be necessary but not sufficient. As a | |
| 132 // result, it may be false even when the position hasn't changed, possibly | |
| 133 // resulting in unnecessary commits, but if it's true the position has | |
| 134 // definitely not changed. The check works by verifying that the prev id | |
| 135 // as calculated from the server position (which will ignore any | |
| 136 // unsynced/unapplied predecessors and be root for non-bookmark datatypes) | |
| 137 // matches the client prev id. Because we traverse chains of conflicting | |
| 138 // items in predecessor -> successor order, we don't need to also verify the | |
| 139 // successor matches (If it's in conflict, we'll verify it next. If it's | |
| 140 // not, then it should be taken into account already in the | |
| 141 // ComputePrevIdFromServerPosition calculation). This works even when there | |
| 142 // are chains of conflicting items. | |
| 143 // | |
| 144 // Example: Original sequence was abcde. Server changes to aCDbe, while | |
| 145 // client changes to aDCbe (C and D are in conflict). Locally, D's prev id | |
| 146 // is a, while C's prev id is D. On the other hand, the server prev id will | |
| 147 // ignore unsynced/unapplied items, so D's server prev id will also be a, | |
| 148 // just like C's. Because we traverse in client predecessor->successor | |
| 149 // order, we evaluate D first. Since prev id and server id match, we | |
| 150 // consider the position to have remained the same for D, and will unset | |
| 151 // it's UNSYNCED/UNAPPLIED bits. When we evaluate C though, we'll see that | |
| 152 // the prev id is D locally while the server's prev id is a. C will | |
| 153 // therefore count as a positional conflict (and the local data will be | |
| 154 // overwritten by the server data typically). The final result will be | |
| 155 // aCDbe (the same as the server's view). Even though both C and D were | |
| 156 // modified, only one counted as being in actual conflict and was resolved | |
| 157 // with local/server wins. | |
| 158 // | |
| 159 // In general, when there are chains of positional conflicts, only the first | |
| 160 // item in chain (based on the clients point of view) will have both it's | |
| 161 // server prev id and local prev id match. For all the rest the server prev | |
| 162 // id will be the predecessor of the first item in the chain, and therefore | |
| 163 // not match the local prev id. | |
| 164 // | |
| 165 // Similarly, chains of conflicts where the server and client info are the | |
| 166 // same are supported due to the predecessor->successor ordering. In this | |
| 167 // case, from the first item onward, we unset the UNSYNCED/UNAPPLIED bits as | |
| 168 // we decide that nothing changed. The subsequent item's server prev id will | |
| 169 // accurately match the local prev id because the predecessor is no longer | |
| 170 // UNSYNCED/UNAPPLIED. | |
| 171 // TODO(zea): simplify all this once we can directly compare server position | |
| 172 // to client position. | |
| 173 syncable::Id server_prev_id = entry.ComputePrevIdFromServerPosition( | |
| 174 entry.Get(syncable::SERVER_PARENT_ID)); | |
| 175 bool needs_reinsertion = !parent_matches || | |
| 176 server_prev_id != entry.Get(syncable::PREV_ID); | |
| 177 DVLOG_IF(1, needs_reinsertion) << "Insertion needed, server prev id " | |
| 178 << " is " << server_prev_id << ", local prev id is " | |
| 179 << entry.Get(syncable::PREV_ID); | |
| 180 const sync_pb::EntitySpecifics& specifics = | |
| 181 entry.Get(syncable::SPECIFICS); | |
| 182 const sync_pb::EntitySpecifics& server_specifics = | |
| 183 entry.Get(syncable::SERVER_SPECIFICS); | |
| 184 const sync_pb::EntitySpecifics& base_server_specifics = | |
| 185 entry.Get(syncable::BASE_SERVER_SPECIFICS); | |
| 186 std::string decrypted_specifics, decrypted_server_specifics; | |
| 187 bool specifics_match = false; | |
| 188 bool server_encrypted_with_default_key = false; | |
| 189 if (specifics.has_encrypted()) { | |
| 190 DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())); | |
| 191 decrypted_specifics = cryptographer->DecryptToString( | |
| 192 specifics.encrypted()); | |
| 193 } else { | |
| 194 decrypted_specifics = specifics.SerializeAsString(); | |
| 195 } | |
| 196 if (server_specifics.has_encrypted()) { | |
| 197 server_encrypted_with_default_key = | |
| 198 cryptographer->CanDecryptUsingDefaultKey( | |
| 199 server_specifics.encrypted()); | |
| 200 decrypted_server_specifics = cryptographer->DecryptToString( | |
| 201 server_specifics.encrypted()); | |
| 202 } else { | |
| 203 decrypted_server_specifics = server_specifics.SerializeAsString(); | |
| 204 } | |
| 205 if (decrypted_server_specifics == decrypted_specifics && | |
| 206 server_encrypted_with_default_key == specifics.has_encrypted()) { | |
| 207 specifics_match = true; | |
| 208 } | |
| 209 bool base_server_specifics_match = false; | |
| 210 if (server_specifics.has_encrypted() && | |
| 211 IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) { | |
| 212 std::string decrypted_base_server_specifics; | |
| 213 if (!base_server_specifics.has_encrypted()) { | |
| 214 decrypted_base_server_specifics = | |
| 215 base_server_specifics.SerializeAsString(); | |
| 216 } else { | |
| 217 decrypted_base_server_specifics = cryptographer->DecryptToString( | |
| 218 base_server_specifics.encrypted()); | |
| 219 } | |
| 220 if (decrypted_server_specifics == decrypted_base_server_specifics) | |
| 221 base_server_specifics_match = true; | |
| 222 } | |
| 223 | |
| 224 // We manually merge nigori data. | |
| 225 if (entry.GetModelType() == syncable::NIGORI) { | |
| 226 // Create a new set of specifics based on the server specifics (which | |
| 227 // preserves their encryption keys). | |
| 228 sync_pb::EntitySpecifics specifics = | |
| 229 entry.Get(syncable::SERVER_SPECIFICS); | |
| 230 sync_pb::NigoriSpecifics* server_nigori = specifics.mutable_nigori(); | |
| 231 // Store the merged set of encrypted types (cryptographer->Update(..) will | |
| 232 // have merged the local types already). | |
| 233 cryptographer->UpdateNigoriFromEncryptedTypes(server_nigori); | |
| 234 // The local set of keys is already merged with the server's set within | |
| 235 // the cryptographer. If we don't have pending keys we can store the | |
| 236 // merged set back immediately. Else we preserve the server keys and will | |
| 237 // update the nigori when the user provides the pending passphrase via | |
| 238 // SetPassphrase(..). | |
| 239 if (cryptographer->is_ready()) { | |
| 240 cryptographer->GetKeys(server_nigori->mutable_encrypted()); | |
| 241 } | |
| 242 // TODO(zea): Find a better way of doing this. As it stands, we have to | |
| 243 // update this code whenever we add a new non-cryptographer related field | |
| 244 // to the nigori node. | |
| 245 if (entry.Get(syncable::SPECIFICS).nigori().sync_tabs()) { | |
| 246 server_nigori->set_sync_tabs(true); | |
| 247 } | |
| 248 // We deliberately leave the server's device information. This client will | |
| 249 // add it's own device information on restart. | |
| 250 entry.Put(syncable::SPECIFICS, specifics); | |
| 251 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; | |
| 252 status->increment_num_server_overwrites(); | |
| 253 OverwriteServerChanges(trans, &entry); | |
| 254 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
| 255 NIGORI_MERGE, | |
| 256 CONFLICT_RESOLUTION_SIZE); | |
| 257 } else if (!entry_deleted && name_matches && parent_matches && | |
| 258 specifics_match && !needs_reinsertion) { | |
| 259 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " | |
| 260 << "changes for: " << entry; | |
| 261 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the | |
| 262 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset | |
| 263 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data from | |
| 264 // adjacent entries when the server update gets applied and the item is | |
| 265 // re-inserted into the PREV_ID/NEXT_ID linked list. This is primarily | |
| 266 // an issue because we commit after applying updates, and is most | |
| 267 // commonly seen when positional changes are made while a passphrase | |
| 268 // is required (and hence there will be many encryption conflicts). | |
| 269 OverwriteServerChanges(trans, &entry); | |
| 270 IgnoreLocalChanges(&entry); | |
| 271 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
| 272 CHANGES_MATCH, | |
| 273 CONFLICT_RESOLUTION_SIZE); | |
| 274 } else if (base_server_specifics_match) { | |
| 275 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " | |
| 276 << " changes for: " << entry; | |
| 277 status->increment_num_server_overwrites(); | |
| 278 OverwriteServerChanges(trans, &entry); | |
| 279 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
| 280 IGNORE_ENCRYPTION, | |
| 281 CONFLICT_RESOLUTION_SIZE); | |
| 282 } else if (entry_deleted || !name_matches || !parent_matches) { | |
| 283 OverwriteServerChanges(trans, &entry); | |
| 284 status->increment_num_server_overwrites(); | |
| 285 DVLOG(1) << "Resolving simple conflict, overwriting server changes " | |
| 286 << "for: " << entry; | |
| 287 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
| 288 OVERWRITE_SERVER, | |
| 289 CONFLICT_RESOLUTION_SIZE); | |
| 290 } else { | |
| 291 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " | |
| 292 << entry; | |
| 293 IgnoreLocalChanges(&entry); | |
| 294 status->increment_num_local_overwrites(); | |
| 295 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
| 296 OVERWRITE_LOCAL, | |
| 297 CONFLICT_RESOLUTION_SIZE); | |
| 298 } | |
| 299 // Now that we've resolved the conflict, clear the prev server | |
| 300 // specifics. | |
| 301 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); | |
| 302 return SYNC_PROGRESS; | |
| 303 } else { // SERVER_IS_DEL is true | |
| 304 // If a server deleted folder has local contents it should be a hierarchy | |
| 305 // conflict. Hierarchy conflicts should not be processed by this function. | |
| 306 // We could end up here if a change was made since we last tried to detect | |
| 307 // conflicts, which was during update application. | |
| 308 if (entry.Get(syncable::IS_DIR)) { | |
| 309 Directory::ChildHandles children; | |
| 310 trans->directory()->GetChildHandlesById(trans, | |
| 311 entry.Get(syncable::ID), | |
| 312 &children); | |
| 313 if (0 != children.size()) { | |
| 314 DVLOG(1) << "Entry is a server deleted directory with local contents, " | |
| 315 << "should be a hierarchy conflict. (race condition)."; | |
| 316 return NO_SYNC_PROGRESS; | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 // The entry is deleted on the server but still exists locally. | |
| 321 if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { | |
| 322 // If we've got a client-unique tag, we can undelete while retaining | |
| 323 // our present ID. | |
| 324 DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to " | |
| 325 "know to re-create, client-tagged items should revert to version 0 " | |
| 326 "when server-deleted."; | |
| 327 OverwriteServerChanges(trans, &entry); | |
| 328 status->increment_num_server_overwrites(); | |
| 329 DVLOG(1) << "Resolving simple conflict, undeleting server entry: " | |
| 330 << entry; | |
| 331 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
| 332 OVERWRITE_SERVER, | |
| 333 CONFLICT_RESOLUTION_SIZE); | |
| 334 // Clobber the versions, just in case the above DCHECK is violated. | |
| 335 entry.Put(syncable::SERVER_VERSION, 0); | |
| 336 entry.Put(syncable::BASE_VERSION, 0); | |
| 337 } else { | |
| 338 // Otherwise, we've got to undelete by creating a new locally | |
| 339 // uncommitted entry. | |
| 340 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); | |
| 341 | |
| 342 MutableEntry server_update(trans, syncable::GET_BY_ID, id); | |
| 343 CHECK(server_update.good()); | |
| 344 CHECK(server_update.Get(syncable::META_HANDLE) != | |
| 345 entry.Get(syncable::META_HANDLE)) | |
| 346 << server_update << entry; | |
| 347 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
| 348 UNDELETE, | |
| 349 CONFLICT_RESOLUTION_SIZE); | |
| 350 } | |
| 351 return SYNC_PROGRESS; | |
| 352 } | |
| 353 } | |
| 354 | |
| 355 bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans, | |
| 356 const Cryptographer* cryptographer, | |
| 357 const ConflictProgress& progress, | |
| 358 sessions::StatusController* status) { | |
| 359 bool forward_progress = false; | |
| 360 // Iterate over simple conflict items. | |
| 361 set<Id>::const_iterator conflicting_item_it; | |
| 362 set<Id> processed_items; | |
| 363 for (conflicting_item_it = progress.SimpleConflictingItemsBegin(); | |
| 364 conflicting_item_it != progress.SimpleConflictingItemsEnd(); | |
| 365 ++conflicting_item_it) { | |
| 366 Id id = *conflicting_item_it; | |
| 367 if (processed_items.count(id) > 0) | |
| 368 continue; | |
| 369 | |
| 370 // We have a simple conflict. In order check if positions have changed, | |
| 371 // we need to process conflicting predecessors before successors. Traverse | |
| 372 // backwards through all continuous conflicting predecessors, building a | |
| 373 // stack of items to resolve in predecessor->successor order, then process | |
| 374 // each item individually. | |
| 375 list<Id> predecessors; | |
| 376 Id prev_id = id; | |
| 377 do { | |
| 378 predecessors.push_back(prev_id); | |
| 379 Entry entry(trans, syncable::GET_BY_ID, prev_id); | |
| 380 // Any entry in conflict must be valid. | |
| 381 CHECK(entry.good()); | |
| 382 Id new_prev_id = entry.Get(syncable::PREV_ID); | |
| 383 if (new_prev_id == prev_id) | |
| 384 break; | |
| 385 prev_id = new_prev_id; | |
| 386 } while (processed_items.count(prev_id) == 0 && | |
| 387 progress.HasSimpleConflictItem(prev_id)); // Excludes root. | |
| 388 while (!predecessors.empty()) { | |
| 389 id = predecessors.back(); | |
| 390 predecessors.pop_back(); | |
| 391 switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { | |
| 392 case NO_SYNC_PROGRESS: | |
| 393 break; | |
| 394 case SYNC_PROGRESS: | |
| 395 forward_progress = true; | |
| 396 break; | |
| 397 } | |
| 398 processed_items.insert(id); | |
| 399 } | |
| 400 } | |
| 401 return forward_progress; | |
| 402 } | |
| 403 | |
| 404 } // namespace browser_sync | |
| OLD | NEW |