summaryrefslogtreecommitdiffstats
path: root/chromium/sync/engine/conflict_resolver.cc
blob: c4814720478fe0e373d2a6d013ef184e6d8f4cf6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sync/engine/conflict_resolver.h"

#include <list>
#include <set>
#include <string>

#include "base/metrics/histogram.h"
#include "sync/engine/conflict_util.h"
#include "sync/engine/syncer_util.h"
#include "sync/sessions/status_controller.h"
#include "sync/syncable/directory.h"
#include "sync/syncable/mutable_entry.h"
#include "sync/syncable/syncable_write_transaction.h"
#include "sync/util/cryptographer.h"

using std::list;
using std::set;

namespace syncer {

using sessions::StatusController;
using syncable::Directory;
using syncable::Entry;
using syncable::Id;
using syncable::MutableEntry;
using syncable::WriteTransaction;

ConflictResolver::ConflictResolver() {
}

ConflictResolver::~ConflictResolver() {
}

void ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
                                             const Id& id,
                                             const Cryptographer* cryptographer,
                                             StatusController* status) {
  MutableEntry entry(trans, syncable::GET_BY_ID, id);
  // Must be good as the entry won't have been cleaned up.
  CHECK(entry.good());

  // This function can only resolve simple conflicts.  Simple conflicts have
  // both IS_UNSYNCED and IS_UNAPPLIED_UDPATE set.
  if (!entry.GetIsUnappliedUpdate() || !entry.GetIsUnsynced()) {
    // This is very unusual, but it can happen in tests.  We may be able to
    // assert NOTREACHED() here when those tests are updated.
    return;
  }

  if (entry.GetIsDel() && entry.GetServerIsDel()) {
    // we've both deleted it, so lets just drop the need to commit/update this
    // entry.
    entry.PutIsUnsynced(false);
    entry.PutIsUnappliedUpdate(false);
    // we've made changes, but they won't help syncing progress.
    // METRIC simple conflict resolved by merge.
    return;
  }

  // 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, SetEncryptionPassphrase,
  //    SetDecryptionPassphrase, or RefreshEncryption).
  // c) Base_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.
  if (!entry.GetServerIsDel()) {
    // 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
    // of three options: CLIENT, SERVER, or MERGE.  Some datatypes (autofill)
    // are easily mergeable.
    // See http://crbug.com/77339.
    bool name_matches = entry.GetNonUniqueName() ==
        entry.GetServerNonUniqueName();
    bool parent_matches = entry.GetParentId() == entry.GetServerParentId();
    bool entry_deleted = entry.GetIsDel();
    // The position check might fail spuriously if one of the positions was
    // based on a legacy random suffix, rather than a deterministic one based on
    // originator_cache_guid and originator_item_id.  If an item is being
    // modified regularly, it shouldn't take long for the suffix and position to
    // be updated, so such false failures shouldn't be a problem for long.
    //
    // Lucky for us, it's OK to be wrong here.  The position_matches check is
    // allowed to return false negatives, as long as it returns no false
    // positives.
    bool position_matches = parent_matches &&
         entry.GetServerUniquePosition().Equals(entry.GetUniquePosition());
    const sync_pb::EntitySpecifics& specifics = entry.GetSpecifics();
    const sync_pb::EntitySpecifics& server_specifics =
        entry.GetServerSpecifics();
    const sync_pb::EntitySpecifics& base_server_specifics =
        entry.GetBaseServerSpecifics();
    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;
    }

    if (!entry_deleted && name_matches && parent_matches && specifics_match &&
        position_matches) {
      DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
               << "changes for: " << entry;
      conflict_util::IgnoreConflict(&entry);
      UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
                                CHANGES_MATCH,
                                CONFLICT_RESOLUTION_SIZE);
    } else if (base_server_specifics_match) {
      DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
               << " changes for: " << entry;
      status->increment_num_server_overwrites();
      conflict_util::OverwriteServerChanges(&entry);
      UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
                                IGNORE_ENCRYPTION,
                                CONFLICT_RESOLUTION_SIZE);
    } else if (entry_deleted || !name_matches || !parent_matches) {
      // NOTE: The update application logic assumes that conflict resolution
      // will never result in changes to the local hierarchy.  The entry_deleted
      // and !parent_matches cases here are critical to maintaining that
      // assumption.
      conflict_util::OverwriteServerChanges(&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;
      conflict_util::IgnoreLocalChanges(&entry);
      status->increment_num_local_overwrites();
      UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
                                OVERWRITE_LOCAL,
                                CONFLICT_RESOLUTION_SIZE);
    }
    // Now that we've resolved the conflict, clear the prev server
    // specifics.
    entry.PutBaseServerSpecifics(sync_pb::EntitySpecifics());
  } else {  // SERVER_IS_DEL is true
    if (entry.GetIsDir()) {
      Directory::Metahandles children;
      trans->directory()->GetChildHandlesById(trans,
                                              entry.GetId(),
                                              &children);
      // If a server deleted folder has local contents it should be a hierarchy
      // conflict.  Hierarchy conflicts should not be processed by this
      // function.
      DCHECK(children.empty());
    }

    // The entry is deleted on the server but still exists locally.
    // We undelete it by overwriting the server's tombstone with the local
    // data.
    conflict_util::OverwriteServerChanges(&entry);
    status->increment_num_server_overwrites();
    DVLOG(1) << "Resolving simple conflict, undeleting server entry: "
             << entry;
    UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
                              UNDELETE,
                              CONFLICT_RESOLUTION_SIZE);
  }
}

void ConflictResolver::ResolveConflicts(
    syncable::WriteTransaction* trans,
    const Cryptographer* cryptographer,
    const std::set<syncable::Id>& simple_conflict_ids,
    sessions::StatusController* status) {
  // Iterate over simple conflict items.
  set<Id>::const_iterator it;
  for (it = simple_conflict_ids.begin();
       it != simple_conflict_ids.end();
       ++it) {
    // We don't resolve conflicts for control types here.
    Entry conflicting_node(trans, syncable::GET_BY_ID, *it);
    CHECK(conflicting_node.good());
    if (IsControlType(
        GetModelTypeFromSpecifics(conflicting_node.GetSpecifics()))) {
      continue;
    }

    ProcessSimpleConflict(trans, *it, cryptographer, status);
  }
  return;
}

}  // namespace syncer