Home | History | Annotate | Download | only in engine
      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