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

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: Split out nigori conflict code and rebase 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).
tim (not reviewing) 2011/12/15 20:55:11 In the case we just discussed offline (two clients
Nicolas Zea 2011/12/16 19:25:14 Yes, depending on the consistency at the server on
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 prev_server_specifics match the server_specifics, no
120 // functional change must have been made server-side (else
121 // prev_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& prev_server_specifics =
146 entry.Get(syncable::PREV_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 prev_server_specifics_match = false;
171 if (server_specifics.has_encrypted() &&
172 IsRealDataType(GetModelTypeFromSpecifics(prev_server_specifics))) {
173 std::string decrypted_prev_server_specifics;
174 if (!prev_server_specifics.has_encrypted()) {
175 decrypted_prev_server_specifics =
176 prev_server_specifics.SerializeAsString();
177 } else {
178 decrypted_prev_server_specifics = cryptographer->DecryptToString(
179 prev_server_specifics.encrypted());
180 }
181 if (decrypted_server_specifics == decrypted_prev_server_specifics)
182 prev_server_specifics_match = true;
183 }
130 184
131 // We manually merge nigori data. 185 // We manually merge nigori data.
132 // TODO(zea): Find a better way of doing this. As it stands, we have to 186 // TODO(zea): Find a better way of doing this. As it stands, we have to
133 // update this code whenever we add a new non-cryptographer related field to 187 // update this code whenever we add a new non-cryptographer related field to
134 // the nigori node. 188 // the nigori node.
135 if (entry.GetModelType() == syncable::NIGORI) { 189 if (entry.GetModelType() == syncable::NIGORI) {
136 // Create a new set of specifics based on the server specifics (which 190 // Create a new set of specifics based on the server specifics (which
137 // preserves their encryption keys). 191 // preserves their encryption keys).
138 sync_pb::EntitySpecifics specifics = 192 sync_pb::EntitySpecifics specifics =
139 entry.Get(syncable::SERVER_SPECIFICS); 193 entry.Get(syncable::SERVER_SPECIFICS);
(...skipping 11 matching lines...) Expand all
151 cryptographer->GetKeys(nigori->mutable_encrypted()); 205 cryptographer->GetKeys(nigori->mutable_encrypted());
152 } 206 }
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 }
157 OverwriteServerChanges(trans, &entry); 211 OverwriteServerChanges(trans, &entry);
158 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 212 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
159 NIGORI_MERGE, 213 NIGORI_MERGE,
160 CONFLICT_RESOLUTION_SIZE); 214 CONFLICT_RESOLUTION_SIZE);
161 } else if (!entry_deleted && name_matches && parent_matches) { 215 } else if (!entry_deleted && name_matches && parent_matches &&
162 // TODO(zea): We may prefer to choose the local changes over the server 216 specifics_match) {
163 // if we know the local changes happened before (or vice versa). 217 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
164 // See http://crbug.com/76596 218 << "changes for: " << entry;
165 DVLOG(1) << "Resolving simple conflict, ignoring local changes for:" 219 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the
220 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset
221 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data
222 // when the server update gets applied and the item is re-inserted into
223 // the PREV_ID/NEXT_ID linked list (see above TODO about comparing
224 // positional info).
225 OverwriteServerChanges(trans, &entry);
226 IgnoreLocalChanges(&entry);
227 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
228 CHANGES_MATCH,
229 CONFLICT_RESOLUTION_SIZE);
230 } else if (prev_server_specifics_match) {
231 DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
232 << " changes for: " << entry;
233 status->increment_num_server_overwrites();
234 OverwriteServerChanges(trans, &entry);
235 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
236 IGNORE_ENCRYPTION,
237 CONFLICT_RESOLUTION_SIZE);
238 // Now that we've resolved the conflict, clear the prev server
239 // specifics.
240 entry.Put(syncable::PREV_SERVER_SPECIFICS, sync_pb::EntitySpecifics());
241 } else if (entry_deleted || !name_matches || !parent_matches) {
242 OverwriteServerChanges(trans, &entry);
243 status->increment_num_server_overwrites();
244 DVLOG(1) << "Resolving simple conflict, overwriting server changes "
245 << "for: " << entry;
246 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
247 OVERWRITE_SERVER,
248 CONFLICT_RESOLUTION_SIZE);
249 } else {
250 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
166 << entry; 251 << entry;
167 IgnoreLocalChanges(&entry); 252 IgnoreLocalChanges(&entry);
168 status->increment_num_local_overwrites(); 253 status->increment_num_local_overwrites();
169 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 254 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
170 OVERWRITE_LOCAL, 255 OVERWRITE_LOCAL,
171 CONFLICT_RESOLUTION_SIZE); 256 CONFLICT_RESOLUTION_SIZE);
172 } else {
173 DVLOG(1) << "Resolving simple conflict, overwriting server changes for:"
174 << entry;
175 OverwriteServerChanges(trans, &entry);
176 status->increment_num_server_overwrites();
177 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
178 OVERWRITE_SERVER,
179 CONFLICT_RESOLUTION_SIZE);
180 } 257 }
181 return SYNC_PROGRESS; 258 return SYNC_PROGRESS;
182 } else { // SERVER_IS_DEL is true 259 } else { // SERVER_IS_DEL is true
183 // If a server deleted folder has local contents we should be in a set. 260 // If a server deleted folder has local contents we should be in a set.
184 if (entry.Get(syncable::IS_DIR)) { 261 if (entry.Get(syncable::IS_DIR)) {
185 Directory::ChildHandles children; 262 Directory::ChildHandles children;
186 trans->directory()->GetChildHandlesById(trans, 263 trans->directory()->GetChildHandlesById(trans,
187 entry.Get(syncable::ID), 264 entry.Get(syncable::ID),
188 &children); 265 &children);
189 if (0 != children.size()) { 266 if (0 != children.size()) {
190 DVLOG(1) << "Entry is a server deleted directory with local contents, " 267 DVLOG(1) << "Entry is a server deleted directory with local contents, "
191 << "should be in a set. (race condition)."; 268 << "should be in a set. (race condition).";
192 return NO_SYNC_PROGRESS; 269 return NO_SYNC_PROGRESS;
193 } 270 }
194 } 271 }
195 272
196 // The entry is deleted on the server but still exists locally. 273 // The entry is deleted on the server but still exists locally.
197 if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { 274 if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) {
198 // If we've got a client-unique tag, we can undelete while retaining 275 // If we've got a client-unique tag, we can undelete while retaining
199 // our present ID. 276 // our present ID.
200 DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to " 277 DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to "
201 "know to re-create, client-tagged items should revert to version 0 " 278 "know to re-create, client-tagged items should revert to version 0 "
202 "when server-deleted."; 279 "when server-deleted.";
203 OverwriteServerChanges(trans, &entry); 280 OverwriteServerChanges(trans, &entry);
204 status->increment_num_server_overwrites(); 281 status->increment_num_server_overwrites();
282 DVLOG(1) << "Resolving simple conflict, undeleting server entry: "
283 << entry;
205 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 284 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
206 OVERWRITE_SERVER, 285 OVERWRITE_SERVER,
207 CONFLICT_RESOLUTION_SIZE); 286 CONFLICT_RESOLUTION_SIZE);
208 // Clobber the versions, just in case the above DCHECK is violated. 287 // Clobber the versions, just in case the above DCHECK is violated.
209 entry.Put(syncable::SERVER_VERSION, 0); 288 entry.Put(syncable::SERVER_VERSION, 0);
210 entry.Put(syncable::BASE_VERSION, 0); 289 entry.Put(syncable::BASE_VERSION, 0);
211 } else { 290 } else {
212 // Otherwise, we've got to undelete by creating a new locally 291 // Otherwise, we've got to undelete by creating a new locally
213 // uncommitted entry. 292 // uncommitted entry.
214 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); 293 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry);
(...skipping 359 matching lines...) Expand 10 before | Expand all | Expand 10 after
574 conflict_set_count_map_.erase(i++); 653 conflict_set_count_map_.erase(i++);
575 // METRIC self resolved conflict sets ++. 654 // METRIC self resolved conflict sets ++.
576 } else { 655 } else {
577 ++i; 656 ++i;
578 } 657 }
579 } 658 }
580 return rv; 659 return rv;
581 } 660 }
582 661
583 } // namespace browser_sync 662 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698