Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(121)

Side by Side Diff: chrome/browser/sync/engine/conflict_resolver.cc

Issue 8770032: [Sync] Implement encryption-aware conflict resolution. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix test Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/sync/engine/conflict_resolver.h" 5 #include "chrome/browser/sync/engine/conflict_resolver.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <map> 8 #include <map>
9 #include <set> 9 #include <set>
10 10
11 #include "base/location.h" 11 #include "base/location.h"
12 #include "base/metrics/histogram.h" 12 #include "base/metrics/histogram.h"
13 #include "chrome/browser/sync/engine/syncer.h" 13 #include "chrome/browser/sync/engine/syncer.h"
14 #include "chrome/browser/sync/engine/syncer_util.h" 14 #include "chrome/browser/sync/engine/syncer_util.h"
15 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" 15 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h"
16 #include "chrome/browser/sync/protocol/service_constants.h" 16 #include "chrome/browser/sync/protocol/service_constants.h"
17 #include "chrome/browser/sync/sessions/status_controller.h" 17 #include "chrome/browser/sync/sessions/status_controller.h"
18 #include "chrome/browser/sync/syncable/directory_manager.h" 18 #include "chrome/browser/sync/syncable/directory_manager.h"
19 #include "chrome/browser/sync/syncable/syncable.h" 19 #include "chrome/browser/sync/syncable/syncable.h"
20 #include "chrome/browser/sync/util/cryptographer.h" 20 #include "chrome/browser/sync/util/cryptographer.h"
21 21
22 using std::map; 22 using std::map;
23 using std::set; 23 using std::set;
24 using syncable::BaseTransaction; 24 using syncable::BaseTransaction;
25 using syncable::Directory; 25 using syncable::Directory;
26 using syncable::Entry; 26 using syncable::Entry;
27 using syncable::GetModelTypeFromSpecifics;
27 using syncable::Id; 28 using syncable::Id;
29 using syncable::IsRealDataType;
28 using syncable::MutableEntry; 30 using syncable::MutableEntry;
29 using syncable::ScopedDirLookup; 31 using syncable::ScopedDirLookup;
30 using syncable::WriteTransaction; 32 using syncable::WriteTransaction;
31 33
32 namespace browser_sync { 34 namespace browser_sync {
33 35
34 using sessions::ConflictProgress; 36 using sessions::ConflictProgress;
35 using sessions::StatusController; 37 using sessions::StatusController;
36 38
37 namespace { 39 namespace {
38 40
39 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; 41 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8;
40 42
41 // Enumeration of different conflict resolutions. Used for histogramming.
42 enum SimpleConflictResolutions {
43 OVERWRITE_LOCAL, // Resolved by overwriting local changes.
44 OVERWRITE_SERVER, // Resolved by overwriting server changes.
45 UNDELETE, // Resolved by undeleting local item.
46 IGNORE_ENCRYPTION, // Resolved by ignoring an encryption-only server
47 // change. TODO(zea): implement and use this.
48 NIGORI_MERGE, // Resolved by merging nigori nodes.
49 CONFLICT_RESOLUTION_SIZE,
50 };
51
52 } // namespace 43 } // namespace
53 44
54 ConflictResolver::ConflictResolver() { 45 ConflictResolver::ConflictResolver() {
55 } 46 }
56 47
57 ConflictResolver::~ConflictResolver() { 48 ConflictResolver::~ConflictResolver() {
58 } 49 }
59 50
60 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { 51 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) {
61 // An update matches local actions, merge the changes. 52 // An update matches local actions, merge the changes.
62 // This is a little fishy because we don't actually merge them. 53 // This is a little fishy because we don't actually merge them.
63 // In the future we should do a 3-way merge. 54 // In the future we should do a 3-way merge.
64 DVLOG(1) << "Resolving conflict by ignoring local changes:" << entry;
65 // With IS_UNSYNCED false, changes should be merged. 55 // With IS_UNSYNCED false, changes should be merged.
66 entry->Put(syncable::IS_UNSYNCED, false); 56 entry->Put(syncable::IS_UNSYNCED, false);
67 } 57 }
68 58
69 void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans, 59 void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans,
70 MutableEntry * entry) { 60 MutableEntry * entry) {
71 // This is similar to an overwrite from the old client. 61 // This is similar to an overwrite from the old client.
72 // This is equivalent to a scenario where we got the update before we'd 62 // This is equivalent to a scenario where we got the update before we'd
73 // made our local client changes. 63 // made our local client changes.
74 // TODO(chron): This is really a general property clobber. We clobber 64 // TODO(chron): This is really a general property clobber. We clobber
75 // the server side property. Perhaps we should actually do property merging. 65 // the server side property. Perhaps we should actually do property merging.
76 DVLOG(1) << "Resolving conflict by ignoring server changes:" << entry;
77 entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION)); 66 entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION));
78 entry->Put(syncable::IS_UNAPPLIED_UPDATE, false); 67 entry->Put(syncable::IS_UNAPPLIED_UPDATE, false);
79 } 68 }
80 69
81 ConflictResolver::ProcessSimpleConflictResult 70 ConflictResolver::ProcessSimpleConflictResult
82 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, 71 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
83 const Id& id, 72 const Id& id,
84 const Cryptographer* cryptographer, 73 const Cryptographer* cryptographer,
85 StatusController* status) { 74 StatusController* status) {
86 MutableEntry entry(trans, syncable::GET_BY_ID, id); 75 MutableEntry entry(trans, syncable::GET_BY_ID, id);
(...skipping 20 matching lines...) Expand all
107 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { 96 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) {
108 // we've both deleted it, so lets just drop the need to commit/update this 97 // we've both deleted it, so lets just drop the need to commit/update this
109 // entry. 98 // entry.
110 entry.Put(syncable::IS_UNSYNCED, false); 99 entry.Put(syncable::IS_UNSYNCED, false);
111 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); 100 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false);
112 // we've made changes, but they won't help syncing progress. 101 // we've made changes, but they won't help syncing progress.
113 // METRIC simple conflict resolved by merge. 102 // METRIC simple conflict resolved by merge.
114 return NO_SYNC_PROGRESS; 103 return NO_SYNC_PROGRESS;
115 } 104 }
116 105
106 // This logic determines "client wins" vs. "server wins" strategy picking.
107 // By the time we get to this point, we rely on the following to be true:
108 // a) We can decrypt both the local and server data (else we'd be in
109 // conflict encryption and not attempting to resolve).
110 // b) All unsynced changes have been re-encrypted with the default key (
111 // occurs either in AttemptToUpdateEntry, SetPassphrase, or
112 // RefreshEncryption).
113 // c) Prev_server_specifics having a valid datatype means that we received
114 // an undecryptable update that only changed specifics, and since then have
115 // not received any further non-specifics-only or decryptable updates.
116 // d) If the server_specifics match specifics, server_specifics are
117 // encrypted with the default key, and all other visible properties match,
118 // then we can safely ignore the local changes as redundant.
119 // e) Otherwise if the base_server_specifics match the server_specifics, no
120 // functional change must have been made server-side (else
121 // base_server_specifics would have been cleared), and we can therefore
122 // safely ignore the server changes as redundant.
123 // f) Otherwise, it's in general safer to ignore local changes, with the
124 // exception of deletion conflicts (choose to undelete) and conflicts
125 // where the non_unique_name or parent don't match.
126 // TODO(sync): We can't compare position here without performing the
127 // NEXT_ID/PREV_ID -> position_in_parent interpolation. Fix that, and take it
128 // into account.
117 if (!entry.Get(syncable::SERVER_IS_DEL)) { 129 if (!entry.Get(syncable::SERVER_IS_DEL)) {
118 // This logic determines "client wins" vs. "server wins" strategy picking.
119 // TODO(nick): The current logic is arbitrary; instead, it ought to be made 130 // TODO(nick): The current logic is arbitrary; instead, it ought to be made
120 // consistent with the ModelAssociator behavior for a datatype. It would 131 // 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 132 // 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) 133 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill)
123 // are easily mergeable. 134 // are easily mergeable.
124 // See http://crbug.com/77339. 135 // See http://crbug.com/77339.
125 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == 136 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) ==
126 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); 137 entry.Get(syncable::SERVER_NON_UNIQUE_NAME);
127 bool parent_matches = entry.Get(syncable::PARENT_ID) == 138 bool parent_matches = entry.Get(syncable::PARENT_ID) ==
128 entry.Get(syncable::SERVER_PARENT_ID); 139 entry.Get(syncable::SERVER_PARENT_ID);
129 bool entry_deleted = entry.Get(syncable::IS_DEL); 140 bool entry_deleted = entry.Get(syncable::IS_DEL);
141 const sync_pb::EntitySpecifics& specifics =
142 entry.Get(syncable::SPECIFICS);
143 const sync_pb::EntitySpecifics& server_specifics =
144 entry.Get(syncable::SERVER_SPECIFICS);
145 const sync_pb::EntitySpecifics& base_server_specifics =
146 entry.Get(syncable::BASE_SERVER_SPECIFICS);
147 std::string decrypted_specifics, decrypted_server_specifics;
148 bool specifics_match = false;
149 bool server_encrypted_with_default_key = false;
150 if (specifics.has_encrypted()) {
151 DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted()));
152 decrypted_specifics = cryptographer->DecryptToString(
153 specifics.encrypted());
154 } else {
155 decrypted_specifics = specifics.SerializeAsString();
156 }
157 if (server_specifics.has_encrypted()) {
158 server_encrypted_with_default_key =
159 cryptographer->CanDecryptUsingDefaultKey(
160 server_specifics.encrypted());
161 decrypted_server_specifics = cryptographer->DecryptToString(
162 server_specifics.encrypted());
163 } else {
164 decrypted_server_specifics = server_specifics.SerializeAsString();
165 }
166 if (decrypted_server_specifics == decrypted_specifics &&
167 server_encrypted_with_default_key == specifics.has_encrypted()) {
168 specifics_match = true;
169 }
170 bool base_server_specifics_match = false;
171 if (server_specifics.has_encrypted() &&
172 IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) {
173 std::string decrypted_base_server_specifics;
174 if (!base_server_specifics.has_encrypted()) {
175 decrypted_base_server_specifics =
176 base_server_specifics.SerializeAsString();
177 } else {
178 decrypted_base_server_specifics = cryptographer->DecryptToString(
179 base_server_specifics.encrypted());
180 }
181 if (decrypted_server_specifics == decrypted_base_server_specifics)
182 base_server_specifics_match = true;
183 }
130 184
131 // We manually merge nigori data. 185 // We manually merge nigori data.
132 if (entry.GetModelType() == syncable::NIGORI) { 186 if (entry.GetModelType() == syncable::NIGORI) {
133 // Create a new set of specifics based on the server specifics (which 187 // Create a new set of specifics based on the server specifics (which
134 // preserves their encryption keys). 188 // preserves their encryption keys).
135 sync_pb::EntitySpecifics specifics = 189 sync_pb::EntitySpecifics specifics =
136 entry.Get(syncable::SERVER_SPECIFICS); 190 entry.Get(syncable::SERVER_SPECIFICS);
137 sync_pb::NigoriSpecifics* nigori = 191 sync_pb::NigoriSpecifics* nigori =
138 specifics.MutableExtension(sync_pb::nigori); 192 specifics.MutableExtension(sync_pb::nigori);
139 // Store the merged set of encrypted types (cryptographer->Update(..) will 193 // Store the merged set of encrypted types (cryptographer->Update(..) will
140 // have merged the local types already). 194 // have merged the local types already).
141 cryptographer->UpdateNigoriFromEncryptedTypes(nigori); 195 cryptographer->UpdateNigoriFromEncryptedTypes(nigori);
142 // The local set of keys is already merged with the server's set within 196 // The local set of keys is already merged with the server's set within
143 // the cryptographer. If we don't have pending keys we can store the 197 // the cryptographer. If we don't have pending keys we can store the
144 // merged set back immediately. Else we preserve the server keys and will 198 // merged set back immediately. Else we preserve the server keys and will
145 // update the nigori when the user provides the pending passphrase via 199 // update the nigori when the user provides the pending passphrase via
146 // SetPassphrase(..). 200 // SetPassphrase(..).
147 if (cryptographer->is_ready()) { 201 if (cryptographer->is_ready()) {
148 cryptographer->GetKeys(nigori->mutable_encrypted()); 202 cryptographer->GetKeys(nigori->mutable_encrypted());
149 } 203 }
150 // TODO(zea): Find a better way of doing this. As it stands, we have to 204 // TODO(zea): Find a better way of doing this. As it stands, we have to
151 // update this code whenever we add a new non-cryptographer related field 205 // update this code whenever we add a new non-cryptographer related field
152 // to the nigori node. 206 // to the nigori node.
153 if (entry.Get(syncable::SPECIFICS).GetExtension(sync_pb::nigori) 207 if (entry.Get(syncable::SPECIFICS).GetExtension(sync_pb::nigori)
154 .sync_tabs()) { 208 .sync_tabs()) {
155 nigori->set_sync_tabs(true); 209 nigori->set_sync_tabs(true);
156 } 210 }
211 // We deliberately leave the server's device information. This client will
212 // add it's own device information on restart.
157 entry.Put(syncable::SPECIFICS, specifics); 213 entry.Put(syncable::SPECIFICS, specifics);
158 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; 214 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry;
159 status->increment_num_server_overwrites(); 215 status->increment_num_server_overwrites();
160 OverwriteServerChanges(trans, &entry); 216 OverwriteServerChanges(trans, &entry);
161 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 217 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
162 NIGORI_MERGE, 218 NIGORI_MERGE,
163 CONFLICT_RESOLUTION_SIZE); 219 CONFLICT_RESOLUTION_SIZE);
164 } else if (!entry_deleted && name_matches && parent_matches) { 220 } else if (!entry_deleted && name_matches && parent_matches &&
165 // TODO(zea): We may prefer to choose the local changes over the server 221 specifics_match) {
166 // if we know the local changes happened before (or vice versa). 222 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
167 // See http://crbug.com/76596 223 << "changes for: " << entry;
168 DVLOG(1) << "Resolving simple conflict, ignoring local changes for:" 224 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the
225 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset
226 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data
227 // when the server update gets applied and the item is re-inserted into
228 // the PREV_ID/NEXT_ID linked list (see above TODO about comparing
229 // positional info).
230 OverwriteServerChanges(trans, &entry);
231 IgnoreLocalChanges(&entry);
232 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
233 CHANGES_MATCH,
234 CONFLICT_RESOLUTION_SIZE);
235 } else if (base_server_specifics_match) {
236 DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
237 << " changes for: " << entry;
238 status->increment_num_server_overwrites();
239 OverwriteServerChanges(trans, &entry);
240 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
241 IGNORE_ENCRYPTION,
242 CONFLICT_RESOLUTION_SIZE);
243 // Now that we've resolved the conflict, clear the prev server
244 // specifics.
245 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics());
246 } else if (entry_deleted || !name_matches || !parent_matches) {
247 OverwriteServerChanges(trans, &entry);
248 status->increment_num_server_overwrites();
249 DVLOG(1) << "Resolving simple conflict, overwriting server changes "
250 << "for: " << entry;
251 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
252 OVERWRITE_SERVER,
253 CONFLICT_RESOLUTION_SIZE);
254 } else {
255 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
169 << entry; 256 << entry;
170 IgnoreLocalChanges(&entry); 257 IgnoreLocalChanges(&entry);
171 status->increment_num_local_overwrites(); 258 status->increment_num_local_overwrites();
172 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 259 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
173 OVERWRITE_LOCAL, 260 OVERWRITE_LOCAL,
174 CONFLICT_RESOLUTION_SIZE); 261 CONFLICT_RESOLUTION_SIZE);
175 } else {
176 DVLOG(1) << "Resolving simple conflict, overwriting server changes for:"
177 << entry;
178 OverwriteServerChanges(trans, &entry);
179 status->increment_num_server_overwrites();
180 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
181 OVERWRITE_SERVER,
182 CONFLICT_RESOLUTION_SIZE);
183 } 262 }
184 return SYNC_PROGRESS; 263 return SYNC_PROGRESS;
185 } else { // SERVER_IS_DEL is true 264 } else { // SERVER_IS_DEL is true
186 // If a server deleted folder has local contents we should be in a set. 265 // If a server deleted folder has local contents we should be in a set.
187 if (entry.Get(syncable::IS_DIR)) { 266 if (entry.Get(syncable::IS_DIR)) {
188 Directory::ChildHandles children; 267 Directory::ChildHandles children;
189 trans->directory()->GetChildHandlesById(trans, 268 trans->directory()->GetChildHandlesById(trans,
190 entry.Get(syncable::ID), 269 entry.Get(syncable::ID),
191 &children); 270 &children);
192 if (0 != children.size()) { 271 if (0 != children.size()) {
193 DVLOG(1) << "Entry is a server deleted directory with local contents, " 272 DVLOG(1) << "Entry is a server deleted directory with local contents, "
194 << "should be in a set. (race condition)."; 273 << "should be in a set. (race condition).";
195 return NO_SYNC_PROGRESS; 274 return NO_SYNC_PROGRESS;
196 } 275 }
197 } 276 }
198 277
199 // The entry is deleted on the server but still exists locally. 278 // The entry is deleted on the server but still exists locally.
200 if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { 279 if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) {
201 // If we've got a client-unique tag, we can undelete while retaining 280 // If we've got a client-unique tag, we can undelete while retaining
202 // our present ID. 281 // our present ID.
203 DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to " 282 DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to "
204 "know to re-create, client-tagged items should revert to version 0 " 283 "know to re-create, client-tagged items should revert to version 0 "
205 "when server-deleted."; 284 "when server-deleted.";
206 OverwriteServerChanges(trans, &entry); 285 OverwriteServerChanges(trans, &entry);
207 status->increment_num_server_overwrites(); 286 status->increment_num_server_overwrites();
287 DVLOG(1) << "Resolving simple conflict, undeleting server entry: "
288 << entry;
208 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 289 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
209 OVERWRITE_SERVER, 290 OVERWRITE_SERVER,
210 CONFLICT_RESOLUTION_SIZE); 291 CONFLICT_RESOLUTION_SIZE);
211 // Clobber the versions, just in case the above DCHECK is violated. 292 // Clobber the versions, just in case the above DCHECK is violated.
212 entry.Put(syncable::SERVER_VERSION, 0); 293 entry.Put(syncable::SERVER_VERSION, 0);
213 entry.Put(syncable::BASE_VERSION, 0); 294 entry.Put(syncable::BASE_VERSION, 0);
214 } else { 295 } else {
215 // Otherwise, we've got to undelete by creating a new locally 296 // Otherwise, we've got to undelete by creating a new locally
216 // uncommitted entry. 297 // uncommitted entry.
217 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); 298 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry);
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
273 if (progress.ConflictSetsSize() > 0) { 354 if (progress.ConflictSetsSize() > 0) {
274 DVLOG(1) << "Detected " << progress.IdToConflictSetSize() 355 DVLOG(1) << "Detected " << progress.IdToConflictSetSize()
275 << " non-simple conflicting entries in " << progress.ConflictSetsSize() 356 << " non-simple conflicting entries in " << progress.ConflictSetsSize()
276 << " unprocessed conflict sets."; 357 << " unprocessed conflict sets.";
277 } 358 }
278 359
279 return ResolveSimpleConflicts(trans, cryptographer, progress, status); 360 return ResolveSimpleConflicts(trans, cryptographer, progress, status);
280 } 361 }
281 362
282 } // namespace browser_sync 363 } // namespace browser_sync
OLDNEW
« no previous file with comments | « chrome/browser/sync/engine/conflict_resolver.h ('k') | chrome/browser/sync/engine/get_commit_ids_command.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698