1 // Copyright 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 "sync/engine/conflict_resolver.h" 6 7 #include <list> 8 #include <set> 9 #include <string> 10 11 #include "base/metrics/histogram.h" 12 #include "sync/engine/conflict_util.h" 13 #include "sync/engine/syncer_util.h" 14 #include "sync/sessions/status_controller.h" 15 #include "sync/syncable/directory.h" 16 #include "sync/syncable/mutable_entry.h" 17 #include "sync/syncable/syncable_write_transaction.h" 18 #include "sync/util/cryptographer.h" 19 20 using std::list; 21 using std::set; 22 23 namespace syncer { 24 25 using sessions::StatusController; 26 using syncable::Directory; 27 using syncable::Entry; 28 using syncable::Id; 29 using syncable::MutableEntry; 30 using syncable::WriteTransaction; 31 32 ConflictResolver::ConflictResolver() { 33 } 34 35 ConflictResolver::~ConflictResolver() { 36 } 37 38 void ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, 39 const Id& id, 40 const Cryptographer* cryptographer, 41 StatusController* status) { 42 MutableEntry entry(trans, syncable::GET_BY_ID, id); 43 // Must be good as the entry won't have been cleaned up. 44 CHECK(entry.good()); 45 46 // This function can only resolve simple conflicts. Simple conflicts have 47 // both IS_UNSYNCED and IS_UNAPPLIED_UDPATE set. 48 if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE) || 49 !entry.Get(syncable::IS_UNSYNCED)) { 50 // This is very unusual, but it can happen in tests. We may be able to 51 // assert NOTREACHED() here when those tests are updated. 52 return; 53 } 54 55 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { 56 // we've both deleted it, so lets just drop the need to commit/update this 57 // entry. 58 entry.Put(syncable::IS_UNSYNCED, false); 59 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); 60 // we've made changes, but they won't help syncing progress. 61 // METRIC simple conflict resolved by merge. 62 return; 63 } 64 65 // This logic determines "client wins" vs. "server wins" strategy picking. 66 // By the time we get to this point, we rely on the following to be true: 67 // a) We can decrypt both the local and server data (else we'd be in 68 // conflict encryption and not attempting to resolve). 69 // b) All unsynced changes have been re-encrypted with the default key ( 70 // occurs either in AttemptToUpdateEntry, SetEncryptionPassphrase, 71 // SetDecryptionPassphrase, or RefreshEncryption). 72 // c) Base_server_specifics having a valid datatype means that we received 73 // an undecryptable update that only changed specifics, and since then have 74 // not received any further non-specifics-only or decryptable updates. 75 // d) If the server_specifics match specifics, server_specifics are 76 // encrypted with the default key, and all other visible properties match, 77 // then we can safely ignore the local changes as redundant. 78 // e) Otherwise if the base_server_specifics match the server_specifics, no 79 // functional change must have been made server-side (else 80 // base_server_specifics would have been cleared), and we can therefore 81 // safely ignore the server changes as redundant. 82 // f) Otherwise, it's in general safer to ignore local changes, with the 83 // exception of deletion conflicts (choose to undelete) and conflicts 84 // where the non_unique_name or parent don't match. 85 if (!entry.Get(syncable::SERVER_IS_DEL)) { 86 // TODO(nick): The current logic is arbitrary; instead, it ought to be made 87 // consistent with the ModelAssociator behavior for a datatype. It would 88 // be nice if we could route this back to ModelAssociator code to pick one 89 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) 90 // are easily mergeable. 91 // See http://crbug.com/77339. 92 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == 93 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); 94 bool parent_matches = entry.Get(syncable::PARENT_ID) == 95 entry.Get(syncable::SERVER_PARENT_ID); 96 bool entry_deleted = entry.Get(syncable::IS_DEL); 97 98 // The position check might fail spuriously if one of the positions was 99 // based on a legacy random suffix, rather than a deterministic one based on 100 // originator_cache_guid and originator_item_id. If an item is being 101 // modified regularly, it shouldn't take long for the suffix and position to 102 // be updated, so such false failures shouldn't be a problem for long. 103 // 104 // Lucky for us, it's OK to be wrong here. The position_matches check is 105 // allowed to return false negatives, as long as it returns no false 106 // positives. 107 bool position_matches = parent_matches && 108 entry.Get(syncable::SERVER_UNIQUE_POSITION).Equals( 109 entry.Get(syncable::UNIQUE_POSITION)); 110 const sync_pb::EntitySpecifics& specifics = 111 entry.Get(syncable::SPECIFICS); 112 const sync_pb::EntitySpecifics& server_specifics = 113 entry.Get(syncable::SERVER_SPECIFICS); 114 const sync_pb::EntitySpecifics& base_server_specifics = 115 entry.Get(syncable::BASE_SERVER_SPECIFICS); 116 std::string decrypted_specifics, decrypted_server_specifics; 117 bool specifics_match = false; 118 bool server_encrypted_with_default_key = false; 119 if (specifics.has_encrypted()) { 120 DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())); 121 decrypted_specifics = cryptographer->DecryptToString( 122 specifics.encrypted()); 123 } else { 124 decrypted_specifics = specifics.SerializeAsString(); 125 } 126 if (server_specifics.has_encrypted()) { 127 server_encrypted_with_default_key = 128 cryptographer->CanDecryptUsingDefaultKey( 129 server_specifics.encrypted()); 130 decrypted_server_specifics = cryptographer->DecryptToString( 131 server_specifics.encrypted()); 132 } else { 133 decrypted_server_specifics = server_specifics.SerializeAsString(); 134 } 135 if (decrypted_server_specifics == decrypted_specifics && 136 server_encrypted_with_default_key == specifics.has_encrypted()) { 137 specifics_match = true; 138 } 139 bool base_server_specifics_match = false; 140 if (server_specifics.has_encrypted() && 141 IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) { 142 std::string decrypted_base_server_specifics; 143 if (!base_server_specifics.has_encrypted()) { 144 decrypted_base_server_specifics = 145 base_server_specifics.SerializeAsString(); 146 } else { 147 decrypted_base_server_specifics = cryptographer->DecryptToString( 148 base_server_specifics.encrypted()); 149 } 150 if (decrypted_server_specifics == decrypted_base_server_specifics) 151 base_server_specifics_match = true; 152 } 153 154 if (!entry_deleted && name_matches && parent_matches && specifics_match && 155 position_matches) { 156 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " 157 << "changes for: " << entry; 158 conflict_util::IgnoreConflict(&entry); 159 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 160 CHANGES_MATCH, 161 CONFLICT_RESOLUTION_SIZE); 162 } else if (base_server_specifics_match) { 163 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " 164 << " changes for: " << entry; 165 status->increment_num_server_overwrites(); 166 conflict_util::OverwriteServerChanges(&entry); 167 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 168 IGNORE_ENCRYPTION, 169 CONFLICT_RESOLUTION_SIZE); 170 } else if (entry_deleted || !name_matches || !parent_matches) { 171 // NOTE: The update application logic assumes that conflict resolution 172 // will never result in changes to the local hierarchy. The entry_deleted 173 // and !parent_matches cases here are critical to maintaining that 174 // assumption. 175 conflict_util::OverwriteServerChanges(&entry); 176 status->increment_num_server_overwrites(); 177 DVLOG(1) << "Resolving simple conflict, overwriting server changes " 178 << "for: " << entry; 179 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 180 OVERWRITE_SERVER, 181 CONFLICT_RESOLUTION_SIZE); 182 } else { 183 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " 184 << entry; 185 conflict_util::IgnoreLocalChanges(&entry); 186 status->increment_num_local_overwrites(); 187 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 188 OVERWRITE_LOCAL, 189 CONFLICT_RESOLUTION_SIZE); 190 } 191 // Now that we've resolved the conflict, clear the prev server 192 // specifics. 193 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); 194 } else { // SERVER_IS_DEL is true 195 if (entry.Get(syncable::IS_DIR)) { 196 Directory::Metahandles children; 197 trans->directory()->GetChildHandlesById(trans, 198 entry.Get(syncable::ID), 199 &children); 200 // If a server deleted folder has local contents it should be a hierarchy 201 // conflict. Hierarchy conflicts should not be processed by this 202 // function. 203 DCHECK(children.empty()); 204 } 205 206 // The entry is deleted on the server but still exists locally. 207 // We undelete it by overwriting the server's tombstone with the local 208 // data. 209 conflict_util::OverwriteServerChanges(&entry); 210 status->increment_num_server_overwrites(); 211 DVLOG(1) << "Resolving simple conflict, undeleting server entry: " 212 << entry; 213 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 214 UNDELETE, 215 CONFLICT_RESOLUTION_SIZE); 216 } 217 } 218 219 void ConflictResolver::ResolveConflicts( 220 syncable::WriteTransaction* trans, 221 const Cryptographer* cryptographer, 222 const std::set<syncable::Id>& simple_conflict_ids, 223 sessions::StatusController* status) { 224 // Iterate over simple conflict items. 225 set<Id>::const_iterator it; 226 for (it = simple_conflict_ids.begin(); 227 it != simple_conflict_ids.end(); 228 ++it) { 229 // We don't resolve conflicts for control types here. 230 Entry conflicting_node(trans, syncable::GET_BY_ID, *it); 231 CHECK(conflicting_node.good()); 232 if (IsControlType( 233 GetModelTypeFromSpecifics(conflicting_node.Get(syncable::SPECIFICS)))) { 234 continue; 235 } 236 237 ProcessSimpleConflict(trans, *it, cryptographer, status); 238 } 239 return; 240 } 241 242 } // namespace syncer 243