Home | History | Annotate | Download | only in engine
      1 // Copyright 2013 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/process_updates_util.h"
      6 
      7 #include "base/location.h"
      8 #include "sync/engine/syncer_proto_util.h"
      9 #include "sync/engine/syncer_types.h"
     10 #include "sync/engine/syncer_util.h"
     11 #include "sync/internal_api/public/sessions/update_counters.h"
     12 #include "sync/syncable/directory.h"
     13 #include "sync/syncable/model_neutral_mutable_entry.h"
     14 #include "sync/syncable/syncable_model_neutral_write_transaction.h"
     15 #include "sync/syncable/syncable_proto_util.h"
     16 #include "sync/syncable/syncable_util.h"
     17 #include "sync/util/cryptographer.h"
     18 
     19 namespace syncer {
     20 
     21 using sessions::StatusController;
     22 
     23 using syncable::GET_BY_ID;
     24 
     25 namespace {
     26 
     27 // This function attempts to determine whether or not this update is genuinely
     28 // new, or if it is a reflection of one of our own commits.
     29 //
     30 // There is a known inaccuracy in its implementation.  If this update ends up
     31 // being applied to a local item with a different ID, we will count the change
     32 // as being a non-reflection update.  Fortunately, the server usually updates
     33 // our IDs correctly in its commit response, so a new ID during GetUpdate should
     34 // be rare.
     35 //
     36 // The only secnarios I can think of where this might happen are:
     37 // - We commit a  new item to the server, but we don't persist the
     38 // server-returned new ID to the database before we shut down.  On the GetUpdate
     39 // following the next restart, we will receive an update from the server that
     40 // updates its local ID.
     41 // - When two attempts to create an item with identical UNIQUE_CLIENT_TAG values
     42 // collide at the server.  I have seen this in testing.  When it happens, the
     43 // test server will send one of the clients a response to upate its local ID so
     44 // that both clients will refer to the item using the same ID going forward.  In
     45 // this case, we're right to assume that the update is not a reflection.
     46 //
     47 // For more information, see FindLocalIdToUpdate().
     48 bool UpdateContainsNewVersion(syncable::BaseTransaction *trans,
     49                               const sync_pb::SyncEntity &update) {
     50   int64 existing_version = -1; // The server always sends positive versions.
     51   syncable::Entry existing_entry(trans, GET_BY_ID,
     52                                  SyncableIdFromProto(update.id_string()));
     53   if (existing_entry.good())
     54     existing_version = existing_entry.GetBaseVersion();
     55 
     56   if (!existing_entry.good() && update.deleted()) {
     57     // There are several possible explanations for this.  The most common cases
     58     // will be first time sync and the redelivery of deletions we've already
     59     // synced, accepted, and purged from our database.  In either case, the
     60     // update is useless to us.  Let's count them all as "not new", even though
     61     // that may not always be entirely accurate.
     62     return false;
     63   }
     64 
     65   if (existing_entry.good() &&
     66       !existing_entry.GetUniqueClientTag().empty() &&
     67       existing_entry.GetIsDel() &&
     68       update.deleted()) {
     69     // Unique client tags will have their version set to zero when they're
     70     // deleted.  The usual version comparison logic won't be able to detect
     71     // reflections of these items.  Instead, we assume any received tombstones
     72     // are reflections.  That should be correct most of the time.
     73     return false;
     74   }
     75 
     76   return existing_version < update.version();
     77 }
     78 
     79 // In the event that IDs match, but tags differ AttemptReuniteClient tag
     80 // will have refused to unify the update.
     81 // We should not attempt to apply it at all since it violates consistency
     82 // rules.
     83 VerifyResult VerifyTagConsistency(
     84     const sync_pb::SyncEntity& entry,
     85     const syncable::ModelNeutralMutableEntry& same_id) {
     86   if (entry.has_client_defined_unique_tag() &&
     87       entry.client_defined_unique_tag() !=
     88           same_id.GetUniqueClientTag()) {
     89     return VERIFY_FAIL;
     90   }
     91   return VERIFY_UNDECIDED;
     92 }
     93 
     94 // Checks whether or not an update is fit for processing.
     95 //
     96 // The answer may be "no" if the update appears invalid, or it's not releveant
     97 // (ie. a delete for an item we've never heard of), or other reasons.
     98 VerifyResult VerifyUpdate(
     99     syncable::ModelNeutralWriteTransaction* trans,
    100     const sync_pb::SyncEntity& entry,
    101     ModelType requested_type) {
    102   syncable::Id id = SyncableIdFromProto(entry.id_string());
    103   VerifyResult result = VERIFY_FAIL;
    104 
    105   const bool deleted = entry.has_deleted() && entry.deleted();
    106   const bool is_directory = IsFolder(entry);
    107   const ModelType model_type = GetModelType(entry);
    108 
    109   if (!id.ServerKnows()) {
    110     LOG(ERROR) << "Illegal negative id in received updates";
    111     return result;
    112   }
    113   {
    114     const std::string name = SyncerProtoUtil::NameFromSyncEntity(entry);
    115     if (name.empty() && !deleted) {
    116       LOG(ERROR) << "Zero length name in non-deleted update";
    117       return result;
    118     }
    119   }
    120 
    121   syncable::ModelNeutralMutableEntry same_id(trans, GET_BY_ID, id);
    122   result = VerifyNewEntry(entry, &same_id, deleted);
    123 
    124   ModelType placement_type = !deleted ? GetModelType(entry)
    125       : same_id.good() ? same_id.GetModelType() : UNSPECIFIED;
    126 
    127   if (VERIFY_UNDECIDED == result) {
    128     result = VerifyTagConsistency(entry, same_id);
    129   }
    130 
    131   if (VERIFY_UNDECIDED == result) {
    132     if (deleted) {
    133       // For deletes the server could send tombostones for items that
    134       // the client did not request. If so ignore those items.
    135       if (IsRealDataType(placement_type) && requested_type != placement_type) {
    136         result = VERIFY_SKIP;
    137       } else {
    138         result = VERIFY_SUCCESS;
    139       }
    140     }
    141   }
    142 
    143   // If we have an existing entry, we check here for updates that break
    144   // consistency rules.
    145   if (VERIFY_UNDECIDED == result) {
    146     result = VerifyUpdateConsistency(trans, entry, deleted,
    147                                      is_directory, model_type, &same_id);
    148   }
    149 
    150   if (VERIFY_UNDECIDED == result)
    151     result = VERIFY_SUCCESS;  // No news is good news.
    152 
    153   return result;  // This might be VERIFY_SUCCESS as well
    154 }
    155 
    156 // Returns true if the entry is still ok to process.
    157 bool ReverifyEntry(syncable::ModelNeutralWriteTransaction* trans,
    158                    const sync_pb::SyncEntity& entry,
    159                    syncable::ModelNeutralMutableEntry* same_id) {
    160 
    161   const bool deleted = entry.has_deleted() && entry.deleted();
    162   const bool is_directory = IsFolder(entry);
    163   const ModelType model_type = GetModelType(entry);
    164 
    165   return VERIFY_SUCCESS == VerifyUpdateConsistency(trans,
    166                                                    entry,
    167                                                    deleted,
    168                                                    is_directory,
    169                                                    model_type,
    170                                                    same_id);
    171 }
    172 
    173 // Process a single update. Will avoid touching global state.
    174 //
    175 // If the update passes a series of checks, this function will copy
    176 // the SyncEntity's data into the SERVER side of the syncable::Directory.
    177 void ProcessUpdate(
    178     const sync_pb::SyncEntity& update,
    179     const Cryptographer* cryptographer,
    180     syncable::ModelNeutralWriteTransaction* const trans) {
    181   const syncable::Id& server_id = SyncableIdFromProto(update.id_string());
    182   const std::string name = SyncerProtoUtil::NameFromSyncEntity(update);
    183 
    184   // Look to see if there's a local item that should recieve this update,
    185   // maybe due to a duplicate client tag or a lost commit response.
    186   syncable::Id local_id = FindLocalIdToUpdate(trans, update);
    187 
    188   // FindLocalEntryToUpdate has veto power.
    189   if (local_id.IsNull()) {
    190     return;  // The entry has become irrelevant.
    191   }
    192 
    193   CreateNewEntry(trans, local_id);
    194 
    195   // We take a two step approach. First we store the entries data in the
    196   // server fields of a local entry and then move the data to the local fields
    197   syncable::ModelNeutralMutableEntry target_entry(trans, GET_BY_ID, local_id);
    198 
    199   // We need to run the Verify checks again; the world could have changed
    200   // since we last verified.
    201   if (!ReverifyEntry(trans, update, &target_entry)) {
    202     return;  // The entry has become irrelevant.
    203   }
    204 
    205   // If we're repurposing an existing local entry with a new server ID,
    206   // change the ID now, after we're sure that the update can succeed.
    207   if (local_id != server_id) {
    208     DCHECK(!update.deleted());
    209     ChangeEntryIDAndUpdateChildren(trans, &target_entry, server_id);
    210     // When IDs change, versions become irrelevant.  Forcing BASE_VERSION
    211     // to zero would ensure that this update gets applied, but would indicate
    212     // creation or undeletion if it were committed that way.  Instead, prefer
    213     // forcing BASE_VERSION to entry.version() while also forcing
    214     // IS_UNAPPLIED_UPDATE to true.  If the item is UNSYNCED, it's committable
    215     // from the new state; it may commit before the conflict resolver gets
    216     // a crack at it.
    217     if (target_entry.GetIsUnsynced() || target_entry.GetBaseVersion() > 0) {
    218       // If either of these conditions are met, then we can expect valid client
    219       // fields for this entry.  When BASE_VERSION is positive, consistency is
    220       // enforced on the client fields at update-application time.  Otherwise,
    221       // we leave the BASE_VERSION field alone; it'll get updated the first time
    222       // we successfully apply this update.
    223       target_entry.PutBaseVersion(update.version());
    224     }
    225     // Force application of this update, no matter what.
    226     target_entry.PutIsUnappliedUpdate(true);
    227   }
    228 
    229   // If this is a newly received undecryptable update, and the only thing that
    230   // has changed are the specifics, store the original decryptable specifics,
    231   // (on which any current or future local changes are based) before we
    232   // overwrite SERVER_SPECIFICS.
    233   // MTIME, CTIME, and NON_UNIQUE_NAME are not enforced.
    234 
    235   bool position_matches = false;
    236   if (target_entry.ShouldMaintainPosition() && !update.deleted()) {
    237     std::string update_tag = GetUniqueBookmarkTagFromUpdate(update);
    238     if (UniquePosition::IsValidSuffix(update_tag)) {
    239       position_matches = GetUpdatePosition(update, update_tag).Equals(
    240           target_entry.GetServerUniquePosition());
    241     } else {
    242       NOTREACHED();
    243     }
    244   } else {
    245     // If this item doesn't care about positions, then set this flag to true.
    246     position_matches = true;
    247   }
    248 
    249   if (!update.deleted() && !target_entry.GetServerIsDel() &&
    250       (SyncableIdFromProto(update.parent_id_string()) ==
    251           target_entry.GetServerParentId()) &&
    252       position_matches &&
    253       update.has_specifics() && update.specifics().has_encrypted() &&
    254       !cryptographer->CanDecrypt(update.specifics().encrypted())) {
    255     sync_pb::EntitySpecifics prev_specifics =
    256         target_entry.GetServerSpecifics();
    257     // We only store the old specifics if they were decryptable and applied and
    258     // there is no BASE_SERVER_SPECIFICS already. Else do nothing.
    259     if (!target_entry.GetIsUnappliedUpdate() &&
    260         !IsRealDataType(GetModelTypeFromSpecifics(
    261             target_entry.GetBaseServerSpecifics())) &&
    262         (!prev_specifics.has_encrypted() ||
    263          cryptographer->CanDecrypt(prev_specifics.encrypted()))) {
    264       DVLOG(2) << "Storing previous server specifcs: "
    265                << prev_specifics.SerializeAsString();
    266       target_entry.PutBaseServerSpecifics(prev_specifics);
    267     }
    268   } else if (IsRealDataType(GetModelTypeFromSpecifics(
    269                  target_entry.GetBaseServerSpecifics()))) {
    270     // We have a BASE_SERVER_SPECIFICS, but a subsequent non-specifics-only
    271     // change arrived. As a result, we can't use the specifics alone to detect
    272     // changes, so we clear BASE_SERVER_SPECIFICS.
    273     target_entry.PutBaseServerSpecifics(
    274                      sync_pb::EntitySpecifics());
    275   }
    276 
    277   UpdateServerFieldsFromUpdate(&target_entry, update, name);
    278 
    279   return;
    280 }
    281 
    282 }  // namespace
    283 
    284 void ProcessDownloadedUpdates(
    285     syncable::Directory* dir,
    286     syncable::ModelNeutralWriteTransaction* trans,
    287     ModelType type,
    288     const SyncEntityList& applicable_updates,
    289     sessions::StatusController* status,
    290     UpdateCounters* counters) {
    291   for (SyncEntityList::const_iterator update_it = applicable_updates.begin();
    292        update_it != applicable_updates.end(); ++update_it) {
    293     DCHECK_EQ(type, GetModelType(**update_it));
    294     if (!UpdateContainsNewVersion(trans, **update_it)) {
    295       status->increment_num_reflected_updates_downloaded_by(1);
    296       counters->num_reflected_updates_received++;
    297     }
    298     if ((*update_it)->deleted()) {
    299       status->increment_num_tombstone_updates_downloaded_by(1);
    300       counters->num_tombstone_updates_received++;
    301     }
    302     VerifyResult verify_result = VerifyUpdate(trans, **update_it, type);
    303     if (verify_result != VERIFY_SUCCESS && verify_result != VERIFY_UNDELETE)
    304       continue;
    305     ProcessUpdate(**update_it, dir->GetCryptographer(trans), trans);
    306   }
    307 }
    308 
    309 void ExpireEntriesByVersion(syncable::Directory* dir,
    310                             syncable::ModelNeutralWriteTransaction* trans,
    311                             ModelType type,
    312                             int64 version_watermark) {
    313   syncable::Directory::Metahandles handles;
    314   dir->GetMetaHandlesOfType(trans, type, &handles);
    315   for (size_t i = 0; i < handles.size(); ++i) {
    316     syncable::ModelNeutralMutableEntry entry(trans, syncable::GET_BY_HANDLE,
    317                                              handles[i]);
    318     if (!entry.good() || !entry.GetId().ServerKnows() ||
    319         entry.GetUniqueServerTag() == ModelTypeToRootTag(type) ||
    320         entry.GetIsUnappliedUpdate() || entry.GetIsUnsynced() ||
    321         entry.GetIsDel() || entry.GetServerIsDel() ||
    322         entry.GetBaseVersion() >= version_watermark) {
    323       continue;
    324     }
    325 
    326     // Mark entry as deleted by server.
    327     entry.PutServerIsDel(true);
    328     entry.PutServerVersion(version_watermark);
    329     entry.PutIsUnappliedUpdate(true);
    330   }
    331 }
    332 
    333 }  // namespace syncer
    334