OLD | NEW |
| (Empty) |
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/internal_api/public/sessions/update_counters.h" | |
15 #include "sync/sessions/status_controller.h" | |
16 #include "sync/syncable/directory.h" | |
17 #include "sync/syncable/mutable_entry.h" | |
18 #include "sync/syncable/syncable_write_transaction.h" | |
19 #include "sync/util/cryptographer.h" | |
20 | |
21 using std::list; | |
22 using std::set; | |
23 | |
24 namespace syncer { | |
25 | |
26 using sessions::StatusController; | |
27 using syncable::Directory; | |
28 using syncable::Entry; | |
29 using syncable::Id; | |
30 using syncable::MutableEntry; | |
31 using syncable::WriteTransaction; | |
32 | |
33 namespace { | |
34 | |
35 // Returns true iff the set of attachment ids contained in attachment_metadata | |
36 // matches the set of ids contained in server_attachment_metadata. | |
37 bool AttachmentMetadataMatches(const MutableEntry& entity) { | |
38 const sync_pb::AttachmentMetadata& local = entity.GetAttachmentMetadata(); | |
39 const sync_pb::AttachmentMetadata& server = | |
40 entity.GetServerAttachmentMetadata(); | |
41 if (local.record_size() != server.record_size()) { | |
42 return false; | |
43 } | |
44 | |
45 // The order of records in local and server may be different so use a std::set | |
46 // to determine if they are equivalent. | |
47 std::set<std::string> local_ids; | |
48 for (int i = 0; i < local.record_size(); ++i) { | |
49 const sync_pb::AttachmentMetadataRecord& record = local.record(i); | |
50 DCHECK(record.is_on_server()); | |
51 local_ids.insert(record.id().SerializeAsString()); | |
52 } | |
53 for (int i = 0; i < server.record_size(); ++i) { | |
54 const sync_pb::AttachmentMetadataRecord& record = server.record(i); | |
55 DCHECK(record.is_on_server()); | |
56 if (local_ids.find(record.id().SerializeAsString()) == local_ids.end()) { | |
57 return false; | |
58 } | |
59 } | |
60 | |
61 return true; | |
62 } | |
63 | |
64 } // namespace | |
65 | |
66 ConflictResolver::ConflictResolver() { | |
67 } | |
68 | |
69 ConflictResolver::~ConflictResolver() { | |
70 } | |
71 | |
72 void ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, | |
73 const Id& id, | |
74 const Cryptographer* cryptographer, | |
75 StatusController* status, | |
76 UpdateCounters* counters) { | |
77 MutableEntry entry(trans, syncable::GET_BY_ID, id); | |
78 // Must be good as the entry won't have been cleaned up. | |
79 CHECK(entry.good()); | |
80 | |
81 // This function can only resolve simple conflicts. Simple conflicts have | |
82 // both IS_UNSYNCED and IS_UNAPPLIED_UDPATE set. | |
83 if (!entry.GetIsUnappliedUpdate() || !entry.GetIsUnsynced()) { | |
84 // This is very unusual, but it can happen in tests. We may be able to | |
85 // assert NOTREACHED() here when those tests are updated. | |
86 return; | |
87 } | |
88 | |
89 if (entry.GetIsDel() && entry.GetServerIsDel()) { | |
90 // we've both deleted it, so lets just drop the need to commit/update this | |
91 // entry. | |
92 entry.PutIsUnsynced(false); | |
93 entry.PutIsUnappliedUpdate(false); | |
94 // we've made changes, but they won't help syncing progress. | |
95 // METRIC simple conflict resolved by merge. | |
96 return; | |
97 } | |
98 | |
99 // This logic determines "client wins" vs. "server wins" strategy picking. | |
100 // By the time we get to this point, we rely on the following to be true: | |
101 // a) We can decrypt both the local and server data (else we'd be in | |
102 // conflict encryption and not attempting to resolve). | |
103 // b) All unsynced changes have been re-encrypted with the default key ( | |
104 // occurs either in AttemptToUpdateEntry, SetEncryptionPassphrase, | |
105 // SetDecryptionPassphrase, or RefreshEncryption). | |
106 // c) Base_server_specifics having a valid datatype means that we received | |
107 // an undecryptable update that only changed specifics, and since then have | |
108 // not received any further non-specifics-only or decryptable updates. | |
109 // d) If the server_specifics match specifics, server_specifics are | |
110 // encrypted with the default key, and all other visible properties match, | |
111 // then we can safely ignore the local changes as redundant. | |
112 // e) Otherwise if the base_server_specifics match the server_specifics, no | |
113 // functional change must have been made server-side (else | |
114 // base_server_specifics would have been cleared), and we can therefore | |
115 // safely ignore the server changes as redundant. | |
116 // f) Otherwise, it's in general safer to ignore local changes, with the | |
117 // exception of deletion conflicts (choose to undelete) and conflicts | |
118 // where the non_unique_name or parent don't match. | |
119 // e) Except for the case of extensions and apps, where we want uninstalls to | |
120 // win over local modifications to avoid "back from the dead" reinstalls. | |
121 if (!entry.GetServerIsDel()) { | |
122 // TODO(nick): The current logic is arbitrary; instead, it ought to be made | |
123 // consistent with the ModelAssociator behavior for a datatype. It would | |
124 // be nice if we could route this back to ModelAssociator code to pick one | |
125 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) | |
126 // are easily mergeable. | |
127 // See http://crbug.com/77339. | |
128 bool name_matches = entry.GetNonUniqueName() == | |
129 entry.GetServerNonUniqueName(); | |
130 // The parent is implicit type root folder or the parent ID matches. | |
131 bool parent_matches = entry.GetServerParentId().IsNull() || | |
132 entry.GetParentId() == entry.GetServerParentId(); | |
133 bool entry_deleted = entry.GetIsDel(); | |
134 // The position check might fail spuriously if one of the positions was | |
135 // based on a legacy random suffix, rather than a deterministic one based on | |
136 // originator_cache_guid and originator_item_id. If an item is being | |
137 // modified regularly, it shouldn't take long for the suffix and position to | |
138 // be updated, so such false failures shouldn't be a problem for long. | |
139 // | |
140 // Lucky for us, it's OK to be wrong here. The position_matches check is | |
141 // allowed to return false negatives, as long as it returns no false | |
142 // positives. | |
143 bool position_matches = parent_matches && | |
144 entry.GetServerUniquePosition().Equals(entry.GetUniquePosition()); | |
145 const sync_pb::EntitySpecifics& specifics = entry.GetSpecifics(); | |
146 const sync_pb::EntitySpecifics& server_specifics = | |
147 entry.GetServerSpecifics(); | |
148 const sync_pb::EntitySpecifics& base_server_specifics = | |
149 entry.GetBaseServerSpecifics(); | |
150 std::string decrypted_specifics, decrypted_server_specifics; | |
151 bool specifics_match = false; | |
152 bool server_encrypted_with_default_key = false; | |
153 if (specifics.has_encrypted()) { | |
154 DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())); | |
155 decrypted_specifics = cryptographer->DecryptToString( | |
156 specifics.encrypted()); | |
157 } else { | |
158 decrypted_specifics = specifics.SerializeAsString(); | |
159 } | |
160 if (server_specifics.has_encrypted()) { | |
161 server_encrypted_with_default_key = | |
162 cryptographer->CanDecryptUsingDefaultKey( | |
163 server_specifics.encrypted()); | |
164 decrypted_server_specifics = cryptographer->DecryptToString( | |
165 server_specifics.encrypted()); | |
166 } else { | |
167 decrypted_server_specifics = server_specifics.SerializeAsString(); | |
168 } | |
169 if (decrypted_server_specifics == decrypted_specifics && | |
170 server_encrypted_with_default_key == specifics.has_encrypted()) { | |
171 specifics_match = true; | |
172 } | |
173 bool base_server_specifics_match = false; | |
174 if (server_specifics.has_encrypted() && | |
175 IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) { | |
176 std::string decrypted_base_server_specifics; | |
177 if (!base_server_specifics.has_encrypted()) { | |
178 decrypted_base_server_specifics = | |
179 base_server_specifics.SerializeAsString(); | |
180 } else { | |
181 decrypted_base_server_specifics = cryptographer->DecryptToString( | |
182 base_server_specifics.encrypted()); | |
183 } | |
184 if (decrypted_server_specifics == decrypted_base_server_specifics) | |
185 base_server_specifics_match = true; | |
186 } | |
187 | |
188 bool attachment_metadata_matches = AttachmentMetadataMatches(entry); | |
189 if (!entry_deleted && name_matches && parent_matches && specifics_match && | |
190 position_matches && attachment_metadata_matches) { | |
191 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " | |
192 << "changes for: " << entry; | |
193 conflict_util::IgnoreConflict(&entry); | |
194 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
195 CHANGES_MATCH, | |
196 CONFLICT_RESOLUTION_SIZE); | |
197 } else if (base_server_specifics_match) { | |
198 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " | |
199 << " changes for: " << entry; | |
200 status->increment_num_server_overwrites(); | |
201 counters->num_server_overwrites++; | |
202 conflict_util::OverwriteServerChanges(&entry); | |
203 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
204 IGNORE_ENCRYPTION, | |
205 CONFLICT_RESOLUTION_SIZE); | |
206 } else if (entry_deleted || !name_matches || !parent_matches) { | |
207 // NOTE: The update application logic assumes that conflict resolution | |
208 // will never result in changes to the local hierarchy. The entry_deleted | |
209 // and !parent_matches cases here are critical to maintaining that | |
210 // assumption. | |
211 conflict_util::OverwriteServerChanges(&entry); | |
212 status->increment_num_server_overwrites(); | |
213 counters->num_server_overwrites++; | |
214 DVLOG(1) << "Resolving simple conflict, overwriting server changes " | |
215 << "for: " << entry; | |
216 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
217 OVERWRITE_SERVER, | |
218 CONFLICT_RESOLUTION_SIZE); | |
219 } else { | |
220 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " | |
221 << entry; | |
222 conflict_util::IgnoreLocalChanges(&entry); | |
223 status->increment_num_local_overwrites(); | |
224 counters->num_local_overwrites++; | |
225 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
226 OVERWRITE_LOCAL, | |
227 CONFLICT_RESOLUTION_SIZE); | |
228 } | |
229 // Now that we've resolved the conflict, clear the prev server | |
230 // specifics. | |
231 entry.PutBaseServerSpecifics(sync_pb::EntitySpecifics()); | |
232 } else { // SERVER_IS_DEL is true | |
233 ModelType type = entry.GetModelType(); | |
234 if (type == EXTENSIONS || type == APPS) { | |
235 // Ignore local changes for extensions/apps when server had a delete, to | |
236 // avoid unwanted reinstall of an uninstalled extension. | |
237 DVLOG(1) << "Resolving simple conflict, ignoring local changes for " | |
238 << "extension/app: " << entry; | |
239 conflict_util::IgnoreLocalChanges(&entry); | |
240 status->increment_num_local_overwrites(); | |
241 counters->num_local_overwrites++; | |
242 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
243 OVERWRITE_LOCAL, | |
244 CONFLICT_RESOLUTION_SIZE); | |
245 } else { | |
246 if (entry.GetIsDir()) { | |
247 Directory::Metahandles children; | |
248 trans->directory()->GetChildHandlesById(trans, | |
249 entry.GetId(), | |
250 &children); | |
251 // If a server deleted folder has local contents it should be a | |
252 // hierarchy conflict. Hierarchy conflicts should not be processed by | |
253 // this function. | |
254 DCHECK(children.empty()); | |
255 } | |
256 | |
257 // The entry is deleted on the server but still exists locally. | |
258 // We undelete it by overwriting the server's tombstone with the local | |
259 // data. | |
260 conflict_util::OverwriteServerChanges(&entry); | |
261 status->increment_num_server_overwrites(); | |
262 counters->num_server_overwrites++; | |
263 DVLOG(1) << "Resolving simple conflict, undeleting server entry: " | |
264 << entry; | |
265 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
266 UNDELETE, | |
267 CONFLICT_RESOLUTION_SIZE); | |
268 } | |
269 } | |
270 } | |
271 | |
272 void ConflictResolver::ResolveConflicts( | |
273 syncable::WriteTransaction* trans, | |
274 const Cryptographer* cryptographer, | |
275 const std::set<syncable::Id>& simple_conflict_ids, | |
276 sessions::StatusController* status, | |
277 UpdateCounters* counters) { | |
278 // Iterate over simple conflict items. | |
279 set<Id>::const_iterator it; | |
280 for (it = simple_conflict_ids.begin(); | |
281 it != simple_conflict_ids.end(); | |
282 ++it) { | |
283 // We don't resolve conflicts for control types here. | |
284 Entry conflicting_node(trans, syncable::GET_BY_ID, *it); | |
285 CHECK(conflicting_node.good()); | |
286 if (IsControlType( | |
287 GetModelTypeFromSpecifics(conflicting_node.GetSpecifics()))) { | |
288 continue; | |
289 } | |
290 | |
291 ProcessSimpleConflict(trans, *it, cryptographer, status, counters); | |
292 } | |
293 return; | |
294 } | |
295 | |
296 } // namespace syncer | |
OLD | NEW |