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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/sync/engine/conflict_resolver.cc
diff --git a/chrome/browser/sync/engine/conflict_resolver.cc b/chrome/browser/sync/engine/conflict_resolver.cc
index e37ac36e2b0ae21e5719e15a8a71d4d106ce9297..cca0c31d8939b42b18b9cd0db87ec8e9c1230eb8 100644
--- a/chrome/browser/sync/engine/conflict_resolver.cc
+++ b/chrome/browser/sync/engine/conflict_resolver.cc
@@ -24,7 +24,9 @@ using std::set;
using syncable::BaseTransaction;
using syncable::Directory;
using syncable::Entry;
+using syncable::GetModelTypeFromSpecifics;
using syncable::Id;
+using syncable::IsRealDataType;
using syncable::MutableEntry;
using syncable::ScopedDirLookup;
using syncable::WriteTransaction;
@@ -38,17 +40,6 @@ namespace {
const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8;
-// Enumeration of different conflict resolutions. Used for histogramming.
-enum SimpleConflictResolutions {
- OVERWRITE_LOCAL, // Resolved by overwriting local changes.
- OVERWRITE_SERVER, // Resolved by overwriting server changes.
- UNDELETE, // Resolved by undeleting local item.
- IGNORE_ENCRYPTION, // Resolved by ignoring an encryption-only server
- // change. TODO(zea): implement and use this.
- NIGORI_MERGE, // Resolved by merging nigori nodes.
- CONFLICT_RESOLUTION_SIZE,
-};
-
} // namespace
ConflictResolver::ConflictResolver() {
@@ -61,7 +52,6 @@ void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) {
// An update matches local actions, merge the changes.
// This is a little fishy because we don't actually merge them.
// In the future we should do a 3-way merge.
- DVLOG(1) << "Resolving conflict by ignoring local changes:" << entry;
// With IS_UNSYNCED false, changes should be merged.
entry->Put(syncable::IS_UNSYNCED, false);
}
@@ -73,7 +63,6 @@ void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans,
// made our local client changes.
// TODO(chron): This is really a general property clobber. We clobber
// the server side property. Perhaps we should actually do property merging.
- DVLOG(1) << "Resolving conflict by ignoring server changes:" << entry;
entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION));
entry->Put(syncable::IS_UNAPPLIED_UPDATE, false);
}
@@ -114,8 +103,30 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
return NO_SYNC_PROGRESS;
}
+ // This logic determines "client wins" vs. "server wins" strategy picking.
+ // By the time we get to this point, we rely on the following to be true:
+ // a) We can decrypt both the local and server data (else we'd be in
+ // conflict encryption and not attempting to resolve).
+ // b) All unsynced changes have been re-encrypted with the default key (
+ // occurs either in AttemptToUpdateEntry, SetPassphrase, or
+ // RefreshEncryption).
+ // c) Prev_server_specifics having a valid datatype means that we received
+ // an undecryptable update that only changed specifics, and since then have
+ // not received any further non-specifics-only or decryptable updates.
+ // d) If the server_specifics match specifics, server_specifics are
+ // encrypted with the default key, and all other visible properties match,
+ // then we can safely ignore the local changes as redundant.
+ // e) Otherwise if the base_server_specifics match the server_specifics, no
+ // functional change must have been made server-side (else
+ // base_server_specifics would have been cleared), and we can therefore
+ // safely ignore the server changes as redundant.
+ // f) Otherwise, it's in general safer to ignore local changes, with the
+ // exception of deletion conflicts (choose to undelete) and conflicts
+ // where the non_unique_name or parent don't match.
+ // TODO(sync): We can't compare position here without performing the
+ // NEXT_ID/PREV_ID -> position_in_parent interpolation. Fix that, and take it
+ // into account.
if (!entry.Get(syncable::SERVER_IS_DEL)) {
- // This logic determines "client wins" vs. "server wins" strategy picking.
// TODO(nick): The current logic is arbitrary; instead, it ought to be made
// consistent with the ModelAssociator behavior for a datatype. It would
// be nice if we could route this back to ModelAssociator code to pick one
@@ -127,6 +138,49 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
bool parent_matches = entry.Get(syncable::PARENT_ID) ==
entry.Get(syncable::SERVER_PARENT_ID);
bool entry_deleted = entry.Get(syncable::IS_DEL);
+ const sync_pb::EntitySpecifics& specifics =
+ entry.Get(syncable::SPECIFICS);
+ const sync_pb::EntitySpecifics& server_specifics =
+ entry.Get(syncable::SERVER_SPECIFICS);
+ const sync_pb::EntitySpecifics& base_server_specifics =
+ entry.Get(syncable::BASE_SERVER_SPECIFICS);
+ std::string decrypted_specifics, decrypted_server_specifics;
+ bool specifics_match = false;
+ bool server_encrypted_with_default_key = false;
+ if (specifics.has_encrypted()) {
+ DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted()));
+ decrypted_specifics = cryptographer->DecryptToString(
+ specifics.encrypted());
+ } else {
+ decrypted_specifics = specifics.SerializeAsString();
+ }
+ if (server_specifics.has_encrypted()) {
+ server_encrypted_with_default_key =
+ cryptographer->CanDecryptUsingDefaultKey(
+ server_specifics.encrypted());
+ decrypted_server_specifics = cryptographer->DecryptToString(
+ server_specifics.encrypted());
+ } else {
+ decrypted_server_specifics = server_specifics.SerializeAsString();
+ }
+ if (decrypted_server_specifics == decrypted_specifics &&
+ server_encrypted_with_default_key == specifics.has_encrypted()) {
+ specifics_match = true;
+ }
+ bool base_server_specifics_match = false;
+ if (server_specifics.has_encrypted() &&
+ IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) {
+ std::string decrypted_base_server_specifics;
+ if (!base_server_specifics.has_encrypted()) {
+ decrypted_base_server_specifics =
+ base_server_specifics.SerializeAsString();
+ } else {
+ decrypted_base_server_specifics = cryptographer->DecryptToString(
+ base_server_specifics.encrypted());
+ }
+ if (decrypted_server_specifics == decrypted_base_server_specifics)
+ base_server_specifics_match = true;
+ }
// We manually merge nigori data.
if (entry.GetModelType() == syncable::NIGORI) {
@@ -154,6 +208,8 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
.sync_tabs()) {
nigori->set_sync_tabs(true);
}
+ // We deliberately leave the server's device information. This client will
+ // add it's own device information on restart.
entry.Put(syncable::SPECIFICS, specifics);
DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry;
status->increment_num_server_overwrites();
@@ -161,25 +217,48 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
NIGORI_MERGE,
CONFLICT_RESOLUTION_SIZE);
- } else if (!entry_deleted && name_matches && parent_matches) {
- // TODO(zea): We may prefer to choose the local changes over the server
- // if we know the local changes happened before (or vice versa).
- // See http://crbug.com/76596
- DVLOG(1) << "Resolving simple conflict, ignoring local changes for:"
- << entry;
+ } else if (!entry_deleted && name_matches && parent_matches &&
+ specifics_match) {
+ DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
+ << "changes for: " << entry;
+ // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the
+ // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset
+ // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data
+ // when the server update gets applied and the item is re-inserted into
+ // the PREV_ID/NEXT_ID linked list (see above TODO about comparing
+ // positional info).
+ OverwriteServerChanges(trans, &entry);
IgnoreLocalChanges(&entry);
- status->increment_num_local_overwrites();
UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
- OVERWRITE_LOCAL,
+ CHANGES_MATCH,
CONFLICT_RESOLUTION_SIZE);
- } else {
- DVLOG(1) << "Resolving simple conflict, overwriting server changes for:"
- << entry;
+ } else if (base_server_specifics_match) {
+ DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
+ << " changes for: " << entry;
+ status->increment_num_server_overwrites();
+ OverwriteServerChanges(trans, &entry);
+ UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
+ IGNORE_ENCRYPTION,
+ CONFLICT_RESOLUTION_SIZE);
+ // Now that we've resolved the conflict, clear the prev server
+ // specifics.
+ entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics());
+ } else if (entry_deleted || !name_matches || !parent_matches) {
OverwriteServerChanges(trans, &entry);
status->increment_num_server_overwrites();
+ DVLOG(1) << "Resolving simple conflict, overwriting server changes "
+ << "for: " << entry;
UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
OVERWRITE_SERVER,
CONFLICT_RESOLUTION_SIZE);
+ } else {
+ DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
+ << entry;
+ IgnoreLocalChanges(&entry);
+ status->increment_num_local_overwrites();
+ UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
+ OVERWRITE_LOCAL,
+ CONFLICT_RESOLUTION_SIZE);
}
return SYNC_PROGRESS;
} else { // SERVER_IS_DEL is true
@@ -205,6 +284,8 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
"when server-deleted.";
OverwriteServerChanges(trans, &entry);
status->increment_num_server_overwrites();
+ DVLOG(1) << "Resolving simple conflict, undeleting server entry: "
+ << entry;
UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
OVERWRITE_SERVER,
CONFLICT_RESOLUTION_SIZE);
« 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