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