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/internal_api/public/sessions/update_counters.h"
     15 #include "sync/sessions/status_controller.h"
     16 #include "sync/syncable/directory.h"
     17 #include "sync/syncable/mutable_entry.h"
     18 #include "sync/syncable/syncable_write_transaction.h"
     19 #include "sync/util/cryptographer.h"
     20 
     21 using std::list;
     22 using std::set;
     23 
     24 namespace syncer {
     25 
     26 using sessions::StatusController;
     27 using syncable::Directory;
     28 using syncable::Entry;
     29 using syncable::Id;
     30 using syncable::MutableEntry;
     31 using syncable::WriteTransaction;
     32 
     33 namespace {
     34 
     35 // Returns true iff the set of attachment ids contained in attachment_metadata
     36 // matches the set of ids contained in server_attachment_metadata.
     37 bool AttachmentMetadataMatches(const MutableEntry& entity) {
     38   const sync_pb::AttachmentMetadata& local = entity.GetAttachmentMetadata();
     39   const sync_pb::AttachmentMetadata& server =
     40       entity.GetServerAttachmentMetadata();
     41   if (local.record_size() != server.record_size()) {
     42     return false;
     43   }
     44 
     45   // The order of records in local and server may be different so use a std::set
     46   // to determine if they are equivalent.
     47   std::set<std::string> local_ids;
     48   for (int i = 0; i < local.record_size(); ++i) {
     49     const sync_pb::AttachmentMetadataRecord& record = local.record(i);
     50     DCHECK(record.is_on_server());
     51     local_ids.insert(record.id().SerializeAsString());
     52   }
     53   for (int i = 0; i < server.record_size(); ++i) {
     54     const sync_pb::AttachmentMetadataRecord& record = server.record(i);
     55     DCHECK(record.is_on_server());
     56     if (local_ids.find(record.id().SerializeAsString()) == local_ids.end()) {
     57       return false;
     58     }
     59   }
     60 
     61   return true;
     62 }
     63 
     64 }  // namespace
     65 
     66 ConflictResolver::ConflictResolver() {
     67 }
     68 
     69 ConflictResolver::~ConflictResolver() {
     70 }
     71 
     72 void ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
     73                                              const Id& id,
     74                                              const Cryptographer* cryptographer,
     75                                              StatusController* status,
     76                                              UpdateCounters* counters) {
     77   MutableEntry entry(trans, syncable::GET_BY_ID, id);
     78   // Must be good as the entry won't have been cleaned up.
     79   CHECK(entry.good());
     80 
     81   // This function can only resolve simple conflicts.  Simple conflicts have
     82   // both IS_UNSYNCED and IS_UNAPPLIED_UDPATE set.
     83   if (!entry.GetIsUnappliedUpdate() || !entry.GetIsUnsynced()) {
     84     // This is very unusual, but it can happen in tests.  We may be able to
     85     // assert NOTREACHED() here when those tests are updated.
     86     return;
     87   }
     88 
     89   if (entry.GetIsDel() && entry.GetServerIsDel()) {
     90     // we've both deleted it, so lets just drop the need to commit/update this
     91     // entry.
     92     entry.PutIsUnsynced(false);
     93     entry.PutIsUnappliedUpdate(false);
     94     // we've made changes, but they won't help syncing progress.
     95     // METRIC simple conflict resolved by merge.
     96     return;
     97   }
     98 
     99   // This logic determines "client wins" vs. "server wins" strategy picking.
    100   // By the time we get to this point, we rely on the following to be true:
    101   // a) We can decrypt both the local and server data (else we'd be in
    102   //    conflict encryption and not attempting to resolve).
    103   // b) All unsynced changes have been re-encrypted with the default key (
    104   //    occurs either in AttemptToUpdateEntry, SetEncryptionPassphrase,
    105   //    SetDecryptionPassphrase, or RefreshEncryption).
    106   // c) Base_server_specifics having a valid datatype means that we received
    107   //    an undecryptable update that only changed specifics, and since then have
    108   //    not received any further non-specifics-only or decryptable updates.
    109   // d) If the server_specifics match specifics, server_specifics are
    110   //    encrypted with the default key, and all other visible properties match,
    111   //    then we can safely ignore the local changes as redundant.
    112   // e) Otherwise if the base_server_specifics match the server_specifics, no
    113   //    functional change must have been made server-side (else
    114   //    base_server_specifics would have been cleared), and we can therefore
    115   //    safely ignore the server changes as redundant.
    116   // f) Otherwise, it's in general safer to ignore local changes, with the
    117   //    exception of deletion conflicts (choose to undelete) and conflicts
    118   //    where the non_unique_name or parent don't match.
    119   if (!entry.GetServerIsDel()) {
    120     // TODO(nick): The current logic is arbitrary; instead, it ought to be made
    121     // consistent with the ModelAssociator behavior for a datatype.  It would
    122     // be nice if we could route this back to ModelAssociator code to pick one
    123     // of three options: CLIENT, SERVER, or MERGE.  Some datatypes (autofill)
    124     // are easily mergeable.
    125     // See http://crbug.com/77339.
    126     bool name_matches = entry.GetNonUniqueName() ==
    127         entry.GetServerNonUniqueName();
    128     bool parent_matches = entry.GetParentId() == entry.GetServerParentId();
    129     bool entry_deleted = entry.GetIsDel();
    130     // The position check might fail spuriously if one of the positions was
    131     // based on a legacy random suffix, rather than a deterministic one based on
    132     // originator_cache_guid and originator_item_id.  If an item is being
    133     // modified regularly, it shouldn't take long for the suffix and position to
    134     // be updated, so such false failures shouldn't be a problem for long.
    135     //
    136     // Lucky for us, it's OK to be wrong here.  The position_matches check is
    137     // allowed to return false negatives, as long as it returns no false
    138     // positives.
    139     bool position_matches = parent_matches &&
    140          entry.GetServerUniquePosition().Equals(entry.GetUniquePosition());
    141     const sync_pb::EntitySpecifics& specifics = entry.GetSpecifics();
    142     const sync_pb::EntitySpecifics& server_specifics =
    143         entry.GetServerSpecifics();
    144     const sync_pb::EntitySpecifics& base_server_specifics =
    145         entry.GetBaseServerSpecifics();
    146     std::string decrypted_specifics, decrypted_server_specifics;
    147     bool specifics_match = false;
    148     bool server_encrypted_with_default_key = false;
    149     if (specifics.has_encrypted()) {
    150       DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted()));
    151       decrypted_specifics = cryptographer->DecryptToString(
    152           specifics.encrypted());
    153     } else {
    154       decrypted_specifics = specifics.SerializeAsString();
    155     }
    156     if (server_specifics.has_encrypted()) {
    157       server_encrypted_with_default_key =
    158           cryptographer->CanDecryptUsingDefaultKey(
    159               server_specifics.encrypted());
    160       decrypted_server_specifics = cryptographer->DecryptToString(
    161           server_specifics.encrypted());
    162     } else {
    163       decrypted_server_specifics = server_specifics.SerializeAsString();
    164     }
    165     if (decrypted_server_specifics == decrypted_specifics &&
    166         server_encrypted_with_default_key == specifics.has_encrypted()) {
    167       specifics_match = true;
    168     }
    169     bool base_server_specifics_match = false;
    170     if (server_specifics.has_encrypted() &&
    171         IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) {
    172       std::string decrypted_base_server_specifics;
    173       if (!base_server_specifics.has_encrypted()) {
    174         decrypted_base_server_specifics =
    175             base_server_specifics.SerializeAsString();
    176       } else {
    177         decrypted_base_server_specifics = cryptographer->DecryptToString(
    178             base_server_specifics.encrypted());
    179       }
    180       if (decrypted_server_specifics == decrypted_base_server_specifics)
    181           base_server_specifics_match = true;
    182     }
    183 
    184     bool attachment_metadata_matches = AttachmentMetadataMatches(entry);
    185     if (!entry_deleted && name_matches && parent_matches && specifics_match &&
    186         position_matches && attachment_metadata_matches) {
    187       DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
    188                << "changes for: " << entry;
    189       conflict_util::IgnoreConflict(&entry);
    190       UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    191                                 CHANGES_MATCH,
    192                                 CONFLICT_RESOLUTION_SIZE);
    193     } else if (base_server_specifics_match) {
    194       DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
    195                << " changes for: " << entry;
    196       status->increment_num_server_overwrites();
    197       counters->num_server_overwrites++;
    198       conflict_util::OverwriteServerChanges(&entry);
    199       UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    200                                 IGNORE_ENCRYPTION,
    201                                 CONFLICT_RESOLUTION_SIZE);
    202     } else if (entry_deleted || !name_matches || !parent_matches) {
    203       // NOTE: The update application logic assumes that conflict resolution
    204       // will never result in changes to the local hierarchy.  The entry_deleted
    205       // and !parent_matches cases here are critical to maintaining that
    206       // assumption.
    207       conflict_util::OverwriteServerChanges(&entry);
    208       status->increment_num_server_overwrites();
    209       counters->num_server_overwrites++;
    210       DVLOG(1) << "Resolving simple conflict, overwriting server changes "
    211                << "for: " << entry;
    212       UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    213                                 OVERWRITE_SERVER,
    214                                 CONFLICT_RESOLUTION_SIZE);
    215     } else {
    216       DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
    217                << entry;
    218       conflict_util::IgnoreLocalChanges(&entry);
    219       status->increment_num_local_overwrites();
    220       counters->num_local_overwrites++;
    221       UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    222                                 OVERWRITE_LOCAL,
    223                                 CONFLICT_RESOLUTION_SIZE);
    224     }
    225     // Now that we've resolved the conflict, clear the prev server
    226     // specifics.
    227     entry.PutBaseServerSpecifics(sync_pb::EntitySpecifics());
    228   } else {  // SERVER_IS_DEL is true
    229     if (entry.GetIsDir()) {
    230       Directory::Metahandles children;
    231       trans->directory()->GetChildHandlesById(trans,
    232                                               entry.GetId(),
    233                                               &children);
    234       // If a server deleted folder has local contents it should be a hierarchy
    235       // conflict.  Hierarchy conflicts should not be processed by this
    236       // function.
    237       DCHECK(children.empty());
    238     }
    239 
    240     // The entry is deleted on the server but still exists locally.
    241     // We undelete it by overwriting the server's tombstone with the local
    242     // data.
    243     conflict_util::OverwriteServerChanges(&entry);
    244     status->increment_num_server_overwrites();
    245     counters->num_server_overwrites++;
    246     DVLOG(1) << "Resolving simple conflict, undeleting server entry: "
    247              << entry;
    248     UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
    249                               UNDELETE,
    250                               CONFLICT_RESOLUTION_SIZE);
    251   }
    252 }
    253 
    254 void ConflictResolver::ResolveConflicts(
    255     syncable::WriteTransaction* trans,
    256     const Cryptographer* cryptographer,
    257     const std::set<syncable::Id>& simple_conflict_ids,
    258     sessions::StatusController* status,
    259     UpdateCounters* counters) {
    260   // Iterate over simple conflict items.
    261   set<Id>::const_iterator it;
    262   for (it = simple_conflict_ids.begin();
    263        it != simple_conflict_ids.end();
    264        ++it) {
    265     // We don't resolve conflicts for control types here.
    266     Entry conflicting_node(trans, syncable::GET_BY_ID, *it);
    267     CHECK(conflicting_node.good());
    268     if (IsControlType(
    269         GetModelTypeFromSpecifics(conflicting_node.GetSpecifics()))) {
    270       continue;
    271     }
    272 
    273     ProcessSimpleConflict(trans, *it, cryptographer, status, counters);
    274   }
    275   return;
    276 }
    277 
    278 }  // namespace syncer
    279