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.Get(syncable::IS_UNAPPLIED_UPDATE) ||
     49       !entry.Get(syncable::IS_UNSYNCED)) {
     50     // This is very unusual, but it can happen in tests.  We may be able to
     51     // assert NOTREACHED() here when those tests are updated.
     52     return;
     53   }
     54 
     55   if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) {
     56     // we've both deleted it, so lets just drop the need to commit/update this
     57     // entry.
     58     entry.Put(syncable::IS_UNSYNCED, false);
     59     entry.Put(syncable::IS_UNAPPLIED_UPDATE, false);
     60     // we've made changes, but they won't help syncing progress.
     61     // METRIC simple conflict resolved by merge.
     62     return;
     63   }
     64 
     65   // This logic determines "client wins" vs. "server wins" strategy picking.
     66   // By the time we get to this point, we rely on the following to be true:
     67   // a) We can decrypt both the local and server data (else we'd be in
     68   //    conflict encryption and not attempting to resolve).
     69   // b) All unsynced changes have been re-encrypted with the default key (
     70   //    occurs either in AttemptToUpdateEntry, SetEncryptionPassphrase,
     71   //    SetDecryptionPassphrase, or RefreshEncryption).
     72   // c) Base_server_specifics having a valid datatype means that we received
     73   //    an undecryptable update that only changed specifics, and since then have
     74   //    not received any further non-specifics-only or decryptable updates.
     75   // d) If the server_specifics match specifics, server_specifics are
     76   //    encrypted with the default key, and all other visible properties match,
     77   //    then we can safely ignore the local changes as redundant.
     78   // e) Otherwise if the base_server_specifics match the server_specifics, no
     79   //    functional change must have been made server-side (else
     80   //    base_server_specifics would have been cleared), and we can therefore
     81   //    safely ignore the server changes as redundant.
     82   // f) Otherwise, it's in general safer to ignore local changes, with the
     83   //    exception of deletion conflicts (choose to undelete) and conflicts
     84   //    where the non_unique_name or parent don't match.
     85   if (!entry.Get(syncable::SERVER_IS_DEL)) {
     86     // TODO(nick): The current logic is arbitrary; instead, it ought to be made
     87     // consistent with the ModelAssociator behavior for a datatype.  It would
     88     // be nice if we could route this back to ModelAssociator code to pick one
     89     // of three options: CLIENT, SERVER, or MERGE.  Some datatypes (autofill)
     90     // are easily mergeable.
     91     // See http://crbug.com/77339.
     92     bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) ==
     93                         entry.Get(syncable::SERVER_NON_UNIQUE_NAME);
     94     bool parent_matches = entry.Get(syncable::PARENT_ID) ==
     95                           entry.Get(syncable::SERVER_PARENT_ID);
     96     bool entry_deleted = entry.Get(syncable::IS_DEL);
     97 
     98     // The position check might fail spuriously if one of the positions was
     99     // based on a legacy random suffix, rather than a deterministic one based on
    100     // originator_cache_guid and originator_item_id.  If an item is being
    101     // modified regularly, it shouldn't take long for the suffix and position to
    102     // be updated, so such false failures shouldn't be a problem for long.
    103     //
    104     // Lucky for us, it's OK to be wrong here.  The position_matches check is
    105     // allowed to return false negatives, as long as it returns no false
    106     // positives.
    107     bool position_matches = parent_matches &&
    108          entry.Get(syncable::SERVER_UNIQUE_POSITION).Equals(
    109              entry.Get(syncable::UNIQUE_POSITION));
    110     const sync_pb::EntitySpecifics& specifics =
    111         entry.Get(syncable::SPECIFICS);
    112     const sync_pb::EntitySpecifics& server_specifics =
    113         entry.Get(syncable::SERVER_SPECIFICS);
    114     const sync_pb::EntitySpecifics& base_server_specifics =
    115         entry.Get(syncable::BASE_SERVER_SPECIFICS);
    116     std::string decrypted_specifics, decrypted_server_specifics;
    117     bool specifics_match = false;
    118     bool server_encrypted_with_default_key = false;
    119     if (specifics.has_encrypted()) {
    120       DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted()));
    121       decrypted_specifics = cryptographer->DecryptToString(
    122           specifics.encrypted());
    123     } else {
    124       decrypted_specifics = specifics.SerializeAsString();
    125     }
    126     if (server_specifics.has_encrypted()) {
    127       server_encrypted_with_default_key =
    128           cryptographer->CanDecryptUsingDefaultKey(
    129               server_specifics.encrypted());
    130       decrypted_server_specifics = cryptographer->DecryptToString(
    131           server_specifics.encrypted());
    132     } else {
    133       decrypted_server_specifics = server_specifics.SerializeAsString();
    134     }
    135     if (decrypted_server_specifics == decrypted_specifics &&
    136         server_encrypted_with_default_key == specifics.has_encrypted()) {
    137       specifics_match = true;
    138     }
    139     bool base_server_specifics_match = false;
    140     if (server_specifics.has_encrypted() &&
    141         IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) {
    142       std::string decrypted_base_server_specifics;
    143       if (!base_server_specifics.has_encrypted()) {
    144         decrypted_base_server_specifics =
    145             base_server_specifics.SerializeAsString();
    146       } else {
    147         decrypted_base_server_specifics = cryptographer->DecryptToString(
    148             base_server_specifics.encrypted());
    149       }
    150       if (decrypted_server_specifics == decrypted_base_server_specifics)
    151           base_server_specifics_match = true;
    152     }
    153 
    154     if (!entry_deleted && name_matches && parent_matches && specifics_match &&
    155         position_matches) {
    156       DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
    157                << "changes for: " << entry;
    158       conflict_util::IgnoreConflict(&entry);
    159       UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    160                                 CHANGES_MATCH,
    161                                 CONFLICT_RESOLUTION_SIZE);
    162     } else if (base_server_specifics_match) {
    163       DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
    164                << " changes for: " << entry;
    165       status->increment_num_server_overwrites();
    166       conflict_util::OverwriteServerChanges(&entry);
    167       UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    168                                 IGNORE_ENCRYPTION,
    169                                 CONFLICT_RESOLUTION_SIZE);
    170     } else if (entry_deleted || !name_matches || !parent_matches) {
    171       // NOTE: The update application logic assumes that conflict resolution
    172       // will never result in changes to the local hierarchy.  The entry_deleted
    173       // and !parent_matches cases here are critical to maintaining that
    174       // assumption.
    175       conflict_util::OverwriteServerChanges(&entry);
    176       status->increment_num_server_overwrites();
    177       DVLOG(1) << "Resolving simple conflict, overwriting server changes "
    178                << "for: " << entry;
    179       UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    180                                 OVERWRITE_SERVER,
    181                                 CONFLICT_RESOLUTION_SIZE);
    182     } else {
    183       DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
    184                << entry;
    185       conflict_util::IgnoreLocalChanges(&entry);
    186       status->increment_num_local_overwrites();
    187       UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    188                                 OVERWRITE_LOCAL,
    189                                 CONFLICT_RESOLUTION_SIZE);
    190     }
    191     // Now that we've resolved the conflict, clear the prev server
    192     // specifics.
    193     entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics());
    194   } else {  // SERVER_IS_DEL is true
    195     if (entry.Get(syncable::IS_DIR)) {
    196       Directory::Metahandles children;
    197       trans->directory()->GetChildHandlesById(trans,
    198                                               entry.Get(syncable::ID),
    199                                               &children);
    200       // If a server deleted folder has local contents it should be a hierarchy
    201       // conflict.  Hierarchy conflicts should not be processed by this
    202       // function.
    203       DCHECK(children.empty());
    204     }
    205 
    206     // The entry is deleted on the server but still exists locally.
    207     // We undelete it by overwriting the server's tombstone with the local
    208     // data.
    209     conflict_util::OverwriteServerChanges(&entry);
    210     status->increment_num_server_overwrites();
    211     DVLOG(1) << "Resolving simple conflict, undeleting server entry: "
    212              << entry;
    213     UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    214                               UNDELETE,
    215                               CONFLICT_RESOLUTION_SIZE);
    216   }
    217 }
    218 
    219 void ConflictResolver::ResolveConflicts(
    220     syncable::WriteTransaction* trans,
    221     const Cryptographer* cryptographer,
    222     const std::set<syncable::Id>& simple_conflict_ids,
    223     sessions::StatusController* status) {
    224   // Iterate over simple conflict items.
    225   set<Id>::const_iterator it;
    226   for (it = simple_conflict_ids.begin();
    227        it != simple_conflict_ids.end();
    228        ++it) {
    229     // We don't resolve conflicts for control types here.
    230     Entry conflicting_node(trans, syncable::GET_BY_ID, *it);
    231     CHECK(conflicting_node.good());
    232     if (IsControlType(
    233         GetModelTypeFromSpecifics(conflicting_node.Get(syncable::SPECIFICS)))) {
    234       continue;
    235     }
    236 
    237     ProcessSimpleConflict(trans, *it, cryptographer, status);
    238   }
    239   return;
    240 }
    241 
    242 }  // namespace syncer
    243