summaryrefslogtreecommitdiffstats
path: root/sync
diff options
context:
space:
mode:
authorrlarocque@chromium.org <rlarocque@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-02 23:16:34 +0000
committerrlarocque@chromium.org <rlarocque@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-02 23:16:34 +0000
commitd1929aaf01ac9c3166d31b3eeaaf2676629cebcb (patch)
tree4218d03dcb01b02a41a2eef448e5009202035855 /sync
parent713eac6708cdadc256d996e6695a4ee9d09212d8 (diff)
downloadchromium_src-d1929aaf01ac9c3166d31b3eeaaf2676629cebcb.zip
chromium_src-d1929aaf01ac9c3166d31b3eeaaf2676629cebcb.tar.gz
chromium_src-d1929aaf01ac9c3166d31b3eeaaf2676629cebcb.tar.bz2
Revert of Move some sync/notifier to components/invalidation (https://codereview.chromium.org/294123004/)
Reason for revert: Passed the CQ but broke the buildbot. Original issue's description: > Move some sync/notifier to components/invalidation > > Moves many of the files in sync/notifier to components/invalidation. > > This change does not introduce any new dependencies. The relevant > dependency rules both before and after this change should be: > - chrome/browser/invalidation and chrome in general depend on > components/invalidation. > - components/invalidation depends on sync/notifier and sync in > general. > - sync/notifier, components/invalidation, and various parts of > chrome all depend on sync/internal_api/public. > > The eventual goal is to move all of sync/notifier into > components/invalidation. The invalidation-related parts of > sync/internal_api/public should be moved to components/invalidation, > too. This will allow us to remove the deopendencies from > components/invalidation to sync, and remove sync's dependencies on > cacheinvalidation and libjingle. > > This change is a regression in terms of shared library componentization. > the files in the sync/notifier folder could be built as a shared > library. The files in compononents/invalidation do not support this > yet. The SYNC_EXPORT declarations in the moved files have been changed > to INVALIDATION_EXPORT so as to not lose this information, but the > macros are currently #defined to no-ops. > > This change does not attempt to rename any classes or namespaces. > Many of the files ported from sync/notifier still use the syncer > namespace. Some, like SyncSystemResources, still have names tied > to their sync heritage. This will be addressed in future CLs. > > Some non-trivial or non-obvious changes include: > - invalidator_state.h was moved to sync/internal_api/public/base so it > could be shared by both sync/ and components/invalidation. This should > be fixed in a future CL. > - FromNotifierReason was split out of invalidator_state.h and moved to > the newly-created components/invalidator_reason_util.h > > TBR=zea,rtenneti,mallinath,dcheng > BUG=259559 > > Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=274350 TBR=pavely@chromium.org,dcheng@chromium.org,zea@chromium.org,rtenneti@chromium.org,mallinath@chromium.org,maniscalco@chromium.org NOTREECHECKS=true NOTRY=true BUG=259559 Review URL: https://codereview.chromium.org/308413002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@274364 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync')
-rw-r--r--sync/internal_api/public/base/invalidator_state.cc25
-rw-r--r--sync/internal_api/public/test/fake_sync_manager.h4
-rw-r--r--sync/internal_api/sync_manager_impl.h2
-rw-r--r--sync/internal_api/sync_manager_impl_unittest.cc1
-rw-r--r--sync/internal_api/test/fake_sync_manager.cc2
-rw-r--r--sync/notifier/DEPS13
-rw-r--r--sync/notifier/fake_invalidation_handler.cc40
-rw-r--r--sync/notifier/fake_invalidation_handler.h42
-rw-r--r--sync/notifier/fake_invalidation_state_tracker.cc55
-rw-r--r--sync/notifier/fake_invalidation_state_tracker.h42
-rw-r--r--sync/notifier/fake_invalidator.cc69
-rw-r--r--sync/notifier/fake_invalidator.h53
-rw-r--r--sync/notifier/fake_invalidator_unittest.cc63
-rw-r--r--sync/notifier/gcm_network_channel.cc430
-rw-r--r--sync/notifier/gcm_network_channel.h135
-rw-r--r--sync/notifier/gcm_network_channel_delegate.h51
-rw-r--r--sync/notifier/gcm_network_channel_unittest.cc494
-rw-r--r--sync/notifier/invalidation_handler.h2
-rw-r--r--sync/notifier/invalidation_notifier.cc93
-rw-r--r--sync/notifier/invalidation_notifier.h108
-rw-r--r--sync/notifier/invalidation_notifier_unittest.cc92
-rw-r--r--sync/notifier/invalidation_state_tracker.cc13
-rw-r--r--sync/notifier/invalidation_state_tracker.h4
-rw-r--r--sync/notifier/invalidator.h2
-rw-r--r--sync/notifier/invalidator_registrar.cc149
-rw-r--r--sync/notifier/invalidator_registrar.h97
-rw-r--r--sync/notifier/invalidator_registrar_unittest.cc163
-rw-r--r--sync/notifier/invalidator_state.cc55
-rw-r--r--sync/notifier/invalidator_state.h (renamed from sync/internal_api/public/base/invalidator_state.h)14
-rw-r--r--sync/notifier/invalidator_test_template.cc28
-rw-r--r--sync/notifier/invalidator_test_template.h377
-rw-r--r--sync/notifier/non_blocking_invalidator.cc367
-rw-r--r--sync/notifier/non_blocking_invalidator.h114
-rw-r--r--sync/notifier/non_blocking_invalidator_unittest.cc98
-rw-r--r--sync/notifier/p2p_invalidator.cc299
-rw-r--r--sync/notifier/p2p_invalidator.h150
-rw-r--r--sync/notifier/p2p_invalidator_unittest.cc355
-rw-r--r--sync/notifier/push_client_channel.cc161
-rw-r--r--sync/notifier/push_client_channel.h91
-rw-r--r--sync/notifier/push_client_channel_unittest.cc252
-rw-r--r--sync/notifier/state_writer.h25
-rw-r--r--sync/notifier/sync_invalidation_listener.cc443
-rw-r--r--sync/notifier/sync_invalidation_listener.h196
-rw-r--r--sync/notifier/sync_invalidation_listener_unittest.cc1129
-rw-r--r--sync/notifier/sync_system_resources.cc333
-rw-r--r--sync/notifier/sync_system_resources.h241
-rw-r--r--sync/notifier/sync_system_resources_unittest.cc249
-rw-r--r--sync/notifier/unacked_invalidation_set.cc1
-rw-r--r--sync/sync_internal_api.gypi2
-rw-r--r--sync/sync_notifier.gypi21
-rw-r--r--sync/sync_tests.gypi17
-rw-r--r--sync/tools/DEPS1
-rw-r--r--sync/tools/sync_client.cc2
-rw-r--r--sync/tools/sync_listen_notifications.cc2
-rw-r--r--sync/tools/sync_tools.gyp2
55 files changed, 7214 insertions, 55 deletions
diff --git a/sync/internal_api/public/base/invalidator_state.cc b/sync/internal_api/public/base/invalidator_state.cc
deleted file mode 100644
index e42ca63..0000000
--- a/sync/internal_api/public/base/invalidator_state.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2014 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/internal_api/public/base/invalidator_state.h"
-
-#include "base/logging.h"
-
-namespace syncer {
-
-const char* InvalidatorStateToString(InvalidatorState state) {
- switch (state) {
- case TRANSIENT_INVALIDATION_ERROR:
- return "TRANSIENT_INVALIDATION_ERROR";
- case INVALIDATION_CREDENTIALS_REJECTED:
- return "INVALIDATION_CREDENTIALS_REJECTED";
- case INVALIDATIONS_ENABLED:
- return "INVALIDATIONS_ENABLED";
- default:
- NOTREACHED();
- return "UNKNOWN_INVALIDATOR_STATE";
- }
-}
-
-} // namespace syncer
diff --git a/sync/internal_api/public/test/fake_sync_manager.h b/sync/internal_api/public/test/fake_sync_manager.h
index 31f5d412..9b2dbea 100644
--- a/sync/internal_api/public/test/fake_sync_manager.h
+++ b/sync/internal_api/public/test/fake_sync_manager.h
@@ -12,6 +12,7 @@
#include "sync/internal_api/public/sync_manager.h"
#include "sync/internal_api/public/test/null_sync_core_proxy.h"
#include "sync/internal_api/public/test/test_user_share.h"
+#include "sync/notifier/invalidator_registrar.h"
namespace base {
class SequencedTaskRunner;
@@ -156,6 +157,9 @@ class FakeSyncManager : public SyncManager {
// The set of types that have been enabled.
ModelTypeSet enabled_types_;
+ // Faked invalidator state.
+ InvalidatorRegistrar registrar_;
+
// The types for which a refresh was most recently requested.
ModelTypeSet last_refresh_request_types_;
diff --git a/sync/internal_api/sync_manager_impl.h b/sync/internal_api/sync_manager_impl.h
index dd6e191..2e02b60 100644
--- a/sync/internal_api/sync_manager_impl.h
+++ b/sync/internal_api/sync_manager_impl.h
@@ -19,13 +19,13 @@
#include "sync/internal_api/js_sync_encryption_handler_observer.h"
#include "sync/internal_api/js_sync_manager_observer.h"
#include "sync/internal_api/protocol_event_buffer.h"
-#include "sync/internal_api/public/base/invalidator_state.h"
#include "sync/internal_api/public/sync_core_proxy.h"
#include "sync/internal_api/public/sync_manager.h"
#include "sync/internal_api/public/user_share.h"
#include "sync/internal_api/sync_encryption_handler_impl.h"
#include "sync/js/js_backend.h"
#include "sync/notifier/invalidation_handler.h"
+#include "sync/notifier/invalidator_state.h"
#include "sync/syncable/directory_change_delegate.h"
#include "sync/util/cryptographer.h"
#include "sync/util/time.h"
diff --git a/sync/internal_api/sync_manager_impl_unittest.cc b/sync/internal_api/sync_manager_impl_unittest.cc
index a3eff02..20b7685 100644
--- a/sync/internal_api/sync_manager_impl_unittest.cc
+++ b/sync/internal_api/sync_manager_impl_unittest.cc
@@ -45,6 +45,7 @@
#include "sync/js/js_backend.h"
#include "sync/js/js_event_handler.h"
#include "sync/js/js_test_util.h"
+#include "sync/notifier/fake_invalidation_handler.h"
#include "sync/notifier/invalidation_handler.h"
#include "sync/notifier/invalidator.h"
#include "sync/protocol/bookmark_specifics.pb.h"
diff --git a/sync/internal_api/test/fake_sync_manager.cc b/sync/internal_api/test/fake_sync_manager.cc
index 7526795..9374cd1 100644
--- a/sync/internal_api/test/fake_sync_manager.cc
+++ b/sync/internal_api/test/fake_sync_manager.cc
@@ -14,11 +14,11 @@
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_task_runner_handle.h"
-#include "sync/internal_api/public/base/invalidator_state.h"
#include "sync/internal_api/public/http_post_provider_factory.h"
#include "sync/internal_api/public/internal_components_factory.h"
#include "sync/internal_api/public/util/weak_handle.h"
#include "sync/notifier/invalidator.h"
+#include "sync/notifier/invalidator_state.h"
#include "sync/notifier/object_id_invalidation_map.h"
#include "sync/syncable/directory.h"
#include "sync/test/fake_sync_encryption_handler.h"
diff --git a/sync/notifier/DEPS b/sync/notifier/DEPS
index 0856674..c16c291 100644
--- a/sync/notifier/DEPS
+++ b/sync/notifier/DEPS
@@ -1,7 +1,20 @@
include_rules = [
"+google/cacheinvalidation",
+ "+jingle/notifier",
+ "+net/base/backoff_entry.h",
+ "+net/base/mock_host_resolver.h",
+ "+net/base/network_change_notifier.h",
+ "+net/http/http_status_code.h",
+ "+net/url_request",
"+sync/base",
"+sync/internal_api/public/base",
"+sync/internal_api/public/util",
+ "+sync/protocol/service_constants.h",
+ "+sync/util",
+
+ # unit tests depend on talk/base.
+ "+talk/base",
+ # sync_notifier depends on the xmpp part of libjingle.
+ "+talk/xmpp",
]
diff --git a/sync/notifier/fake_invalidation_handler.cc b/sync/notifier/fake_invalidation_handler.cc
new file mode 100644
index 0000000..e6497a4
--- /dev/null
+++ b/sync/notifier/fake_invalidation_handler.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 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/notifier/fake_invalidation_handler.h"
+
+namespace syncer {
+
+FakeInvalidationHandler::FakeInvalidationHandler()
+ : state_(DEFAULT_INVALIDATION_ERROR),
+ invalidation_count_(0) {}
+
+FakeInvalidationHandler::~FakeInvalidationHandler() {}
+
+InvalidatorState FakeInvalidationHandler::GetInvalidatorState() const {
+ return state_;
+}
+
+const ObjectIdInvalidationMap&
+FakeInvalidationHandler::GetLastInvalidationMap() const {
+ return last_invalidation_map_;
+}
+
+int FakeInvalidationHandler::GetInvalidationCount() const {
+ return invalidation_count_;
+}
+
+void FakeInvalidationHandler::OnInvalidatorStateChange(InvalidatorState state) {
+ state_ = state;
+}
+
+void FakeInvalidationHandler::OnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ last_invalidation_map_ = invalidation_map;
+ ++invalidation_count_;
+}
+
+std::string FakeInvalidationHandler::GetOwnerName() const { return "Fake"; }
+
+} // namespace syncer
diff --git a/sync/notifier/fake_invalidation_handler.h b/sync/notifier/fake_invalidation_handler.h
new file mode 100644
index 0000000..985de50
--- /dev/null
+++ b/sync/notifier/fake_invalidation_handler.h
@@ -0,0 +1,42 @@
+// Copyright 2014 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.
+
+#ifndef SYNC_NOTIFIER_FAKE_SYNC_NOTIFIER_OBSERVER_H_
+#define SYNC_NOTIFIER_FAKE_SYNC_NOTIFIER_OBSERVER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "sync/notifier/invalidation_handler.h"
+#include "sync/notifier/object_id_invalidation_map.h"
+
+namespace syncer {
+
+class FakeInvalidationHandler : public InvalidationHandler {
+ public:
+ FakeInvalidationHandler();
+ virtual ~FakeInvalidationHandler();
+
+ InvalidatorState GetInvalidatorState() const;
+ const ObjectIdInvalidationMap& GetLastInvalidationMap() const;
+ int GetInvalidationCount() const;
+
+ // InvalidationHandler implementation.
+ virtual void OnInvalidatorStateChange(InvalidatorState state) OVERRIDE;
+ virtual void OnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) OVERRIDE;
+ virtual std::string GetOwnerName() const OVERRIDE;
+
+ private:
+ InvalidatorState state_;
+ ObjectIdInvalidationMap last_invalidation_map_;
+ int invalidation_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeInvalidationHandler);
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_FAKE_SYNC_NOTIFIER_OBSERVER_H_
diff --git a/sync/notifier/fake_invalidation_state_tracker.cc b/sync/notifier/fake_invalidation_state_tracker.cc
new file mode 100644
index 0000000..681bda9
--- /dev/null
+++ b/sync/notifier/fake_invalidation_state_tracker.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 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/notifier/fake_invalidation_state_tracker.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/task_runner.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+const int64 FakeInvalidationStateTracker::kMinVersion = kint64min;
+
+FakeInvalidationStateTracker::FakeInvalidationStateTracker() {}
+
+FakeInvalidationStateTracker::~FakeInvalidationStateTracker() {}
+
+void FakeInvalidationStateTracker::ClearAndSetNewClientId(
+ const std::string& client_id) {
+ Clear();
+ invalidator_client_id_ = client_id;
+}
+
+std::string FakeInvalidationStateTracker::GetInvalidatorClientId() const {
+ return invalidator_client_id_;
+}
+
+void FakeInvalidationStateTracker::SetBootstrapData(
+ const std::string& data) {
+ bootstrap_data_ = data;
+}
+
+std::string FakeInvalidationStateTracker::GetBootstrapData() const {
+ return bootstrap_data_;
+}
+
+void FakeInvalidationStateTracker::SetSavedInvalidations(
+ const UnackedInvalidationsMap& states) {
+ unacked_invalidations_map_ = states;
+}
+
+UnackedInvalidationsMap
+FakeInvalidationStateTracker::GetSavedInvalidations() const {
+ return unacked_invalidations_map_;
+}
+
+void FakeInvalidationStateTracker::Clear() {
+ invalidator_client_id_.clear();
+ bootstrap_data_.clear();
+}
+
+} // namespace syncer
diff --git a/sync/notifier/fake_invalidation_state_tracker.h b/sync/notifier/fake_invalidation_state_tracker.h
new file mode 100644
index 0000000..cd19134
--- /dev/null
+++ b/sync/notifier/fake_invalidation_state_tracker.h
@@ -0,0 +1,42 @@
+// Copyright 2014 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.
+
+#ifndef SYNC_NOTIFIER_FAKE_INVALIDATION_STATE_TRACKER_H_
+#define SYNC_NOTIFIER_FAKE_INVALIDATION_STATE_TRACKER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "sync/notifier/invalidation_state_tracker.h"
+
+namespace syncer {
+
+// InvalidationStateTracker implementation that simply keeps track of
+// the max versions and invalidation state in memory.
+class FakeInvalidationStateTracker
+ : public InvalidationStateTracker,
+ public base::SupportsWeakPtr<FakeInvalidationStateTracker> {
+ public:
+ FakeInvalidationStateTracker();
+ virtual ~FakeInvalidationStateTracker();
+
+ // InvalidationStateTracker implementation.
+ virtual void ClearAndSetNewClientId(const std::string& client_id) OVERRIDE;
+ virtual std::string GetInvalidatorClientId() const OVERRIDE;
+ virtual void SetBootstrapData(const std::string& data) OVERRIDE;
+ virtual std::string GetBootstrapData() const OVERRIDE;
+ virtual void SetSavedInvalidations(
+ const UnackedInvalidationsMap& states) OVERRIDE;
+ virtual UnackedInvalidationsMap GetSavedInvalidations() const OVERRIDE;
+ virtual void Clear() OVERRIDE;
+
+ static const int64 kMinVersion;
+
+ private:
+ std::string invalidator_client_id_;
+ std::string bootstrap_data_;
+ UnackedInvalidationsMap unacked_invalidations_map_;
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_FAKE_INVALIDATION_STATE_TRACKER_H_
diff --git a/sync/notifier/fake_invalidator.cc b/sync/notifier/fake_invalidator.cc
new file mode 100644
index 0000000..cec6f52
--- /dev/null
+++ b/sync/notifier/fake_invalidator.cc
@@ -0,0 +1,69 @@
+// Copyright 2014 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/notifier/fake_invalidator.h"
+
+#include "sync/notifier/object_id_invalidation_map.h"
+
+namespace syncer {
+
+FakeInvalidator::FakeInvalidator() {}
+
+FakeInvalidator::~FakeInvalidator() {}
+
+bool FakeInvalidator::IsHandlerRegistered(InvalidationHandler* handler) const {
+ return registrar_.IsHandlerRegisteredForTest(handler);
+}
+
+ObjectIdSet FakeInvalidator::GetRegisteredIds(
+ InvalidationHandler* handler) const {
+ return registrar_.GetRegisteredIds(handler);
+}
+
+const std::string& FakeInvalidator::GetCredentialsEmail() const {
+ return email_;
+}
+
+const std::string& FakeInvalidator::GetCredentialsToken() const {
+ return token_;
+}
+
+void FakeInvalidator::EmitOnInvalidatorStateChange(InvalidatorState state) {
+ registrar_.UpdateInvalidatorState(state);
+}
+
+void FakeInvalidator::EmitOnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ registrar_.DispatchInvalidationsToHandlers(invalidation_map);
+}
+
+void FakeInvalidator::RegisterHandler(InvalidationHandler* handler) {
+ registrar_.RegisterHandler(handler);
+}
+
+void FakeInvalidator::UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids) {
+ registrar_.UpdateRegisteredIds(handler, ids);
+}
+
+void FakeInvalidator::UnregisterHandler(InvalidationHandler* handler) {
+ registrar_.UnregisterHandler(handler);
+}
+
+InvalidatorState FakeInvalidator::GetInvalidatorState() const {
+ return registrar_.GetInvalidatorState();
+}
+
+void FakeInvalidator::UpdateCredentials(
+ const std::string& email, const std::string& token) {
+ email_ = email;
+ token_ = token;
+}
+
+void FakeInvalidator::RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const {
+ base::DictionaryValue value;
+ callback.Run(value);
+}
+} // namespace syncer
diff --git a/sync/notifier/fake_invalidator.h b/sync/notifier/fake_invalidator.h
new file mode 100644
index 0000000..25d03e2
--- /dev/null
+++ b/sync/notifier/fake_invalidator.h
@@ -0,0 +1,53 @@
+// Copyright 2014 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.
+
+#ifndef SYNC_NOTIFIER_FAKE_INVALIDATOR_H_
+#define SYNC_NOTIFIER_FAKE_INVALIDATOR_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "sync/notifier/invalidation_util.h"
+#include "sync/notifier/invalidator.h"
+#include "sync/notifier/invalidator_registrar.h"
+
+namespace syncer {
+
+class FakeInvalidator : public Invalidator {
+ public:
+ FakeInvalidator();
+ virtual ~FakeInvalidator();
+
+ bool IsHandlerRegistered(InvalidationHandler* handler) const;
+ ObjectIdSet GetRegisteredIds(InvalidationHandler* handler) const;
+ const std::string& GetUniqueId() const;
+ const std::string& GetCredentialsEmail() const;
+ const std::string& GetCredentialsToken() const;
+
+ void EmitOnInvalidatorStateChange(InvalidatorState state);
+ void EmitOnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map);
+
+ virtual void RegisterHandler(InvalidationHandler* handler) OVERRIDE;
+ virtual void UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids) OVERRIDE;
+ virtual void UnregisterHandler(InvalidationHandler* handler) OVERRIDE;
+ virtual InvalidatorState GetInvalidatorState() const OVERRIDE;
+ virtual void UpdateCredentials(
+ const std::string& email, const std::string& token) OVERRIDE;
+ virtual void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const
+ OVERRIDE;
+
+ private:
+ InvalidatorRegistrar registrar_;
+ std::string state_;
+ std::string email_;
+ std::string token_;
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_FAKE_INVALIDATOR_H_
diff --git a/sync/notifier/fake_invalidator_unittest.cc b/sync/notifier/fake_invalidator_unittest.cc
new file mode 100644
index 0000000..c48098a
--- /dev/null
+++ b/sync/notifier/fake_invalidator_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 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 "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "sync/notifier/fake_invalidator.h"
+#include "sync/notifier/invalidator_test_template.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+class FakeInvalidatorTestDelegate {
+ public:
+ FakeInvalidatorTestDelegate() {}
+
+ ~FakeInvalidatorTestDelegate() {
+ DestroyInvalidator();
+ }
+
+ void CreateInvalidator(
+ const std::string& invalidator_client_id,
+ const std::string& initial_state,
+ const base::WeakPtr<InvalidationStateTracker>&
+ invalidation_state_tracker) {
+ DCHECK(!invalidator_.get());
+ invalidator_.reset(new FakeInvalidator());
+ }
+
+ FakeInvalidator* GetInvalidator() {
+ return invalidator_.get();
+ }
+
+ void DestroyInvalidator() {
+ invalidator_.reset();
+ }
+
+ void WaitForInvalidator() {
+ // Do Nothing.
+ }
+
+ void TriggerOnInvalidatorStateChange(InvalidatorState state) {
+ invalidator_->EmitOnInvalidatorStateChange(state);
+ }
+
+ void TriggerOnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ invalidator_->EmitOnIncomingInvalidation(invalidation_map);
+ }
+
+ private:
+ scoped_ptr<FakeInvalidator> invalidator_;
+};
+
+INSTANTIATE_TYPED_TEST_CASE_P(
+ FakeInvalidatorTest, InvalidatorTest,
+ FakeInvalidatorTestDelegate);
+
+} // namespace
+
+} // namespace syncer
diff --git a/sync/notifier/gcm_network_channel.cc b/sync/notifier/gcm_network_channel.cc
new file mode 100644
index 0000000..59f1a57
--- /dev/null
+++ b/sync/notifier/gcm_network_channel.cc
@@ -0,0 +1,430 @@
+// Copyright 2014 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 "base/base64.h"
+#include "base/i18n/time_formatting.h"
+#include "base/metrics/histogram.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#if !defined(OS_ANDROID)
+// channel_common.proto defines ANDROID constant that conflicts with Android
+// build. At the same time TiclInvalidationService is not used on Android so it
+// is safe to exclude these protos from Android build.
+#include "google/cacheinvalidation/android_channel.pb.h"
+#include "google/cacheinvalidation/channel_common.pb.h"
+#include "google/cacheinvalidation/types.pb.h"
+#endif
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_status.h"
+#include "sync/notifier/gcm_network_channel.h"
+#include "sync/notifier/gcm_network_channel_delegate.h"
+
+namespace syncer {
+
+namespace {
+
+const char kCacheInvalidationEndpointUrl[] =
+ "https://clients4.google.com/invalidation/android/request/";
+const char kCacheInvalidationPackageName[] = "com.google.chrome.invalidations";
+
+// Register backoff policy.
+const net::BackoffEntry::Policy kRegisterBackoffPolicy = {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ 0,
+
+ // Initial delay for exponential back-off in ms.
+ 2000, // 2 seconds.
+
+ // Factor by which the waiting time will be multiplied.
+ 2,
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ 0.2, // 20%.
+
+ // Maximum amount of time we are willing to delay our request in ms.
+ 1000 * 3600 * 4, // 4 hours.
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ -1,
+
+ // Don't use initial delay unless the last request was an error.
+ false,
+};
+
+// Incoming message status values for UMA_HISTOGRAM.
+enum IncomingMessageStatus {
+ INCOMING_MESSAGE_SUCCESS,
+ MESSAGE_EMPTY, // GCM message's content is missing or empty.
+ INVALID_ENCODING, // Base64Decode failed.
+ INVALID_PROTO, // Parsing protobuf failed.
+
+ // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
+ // this line.
+ INCOMING_MESSAGE_STATUS_COUNT
+};
+
+// Outgoing message status values for UMA_HISTOGRAM.
+enum OutgoingMessageStatus {
+ OUTGOING_MESSAGE_SUCCESS,
+ MESSAGE_DISCARDED, // New message started before old one was sent.
+ ACCESS_TOKEN_FAILURE, // Requeting access token failed.
+ POST_FAILURE, // HTTP Post failed.
+
+ // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above
+ // this line.
+ OUTGOING_MESSAGE_STATUS_COUNT
+};
+
+const char kIncomingMessageStatusHistogram[] =
+ "GCMInvalidations.IncomingMessageStatus";
+const char kOutgoingMessageStatusHistogram[] =
+ "GCMInvalidations.OutgoingMessageStatus";
+
+void RecordIncomingMessageStatus(IncomingMessageStatus status) {
+ UMA_HISTOGRAM_ENUMERATION(kIncomingMessageStatusHistogram,
+ status,
+ INCOMING_MESSAGE_STATUS_COUNT);
+}
+
+void RecordOutgoingMessageStatus(OutgoingMessageStatus status) {
+ UMA_HISTOGRAM_ENUMERATION(kOutgoingMessageStatusHistogram,
+ status,
+ OUTGOING_MESSAGE_STATUS_COUNT);
+}
+
+} // namespace
+
+GCMNetworkChannel::GCMNetworkChannel(
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ scoped_ptr<GCMNetworkChannelDelegate> delegate)
+ : request_context_getter_(request_context_getter),
+ delegate_(delegate.Pass()),
+ register_backoff_entry_(new net::BackoffEntry(&kRegisterBackoffPolicy)),
+ diagnostic_info_(this),
+ weak_factory_(this) {
+ net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+ delegate_->Initialize();
+ Register();
+}
+
+GCMNetworkChannel::~GCMNetworkChannel() {
+ net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+}
+
+void GCMNetworkChannel::Register() {
+ delegate_->Register(base::Bind(&GCMNetworkChannel::OnRegisterComplete,
+ weak_factory_.GetWeakPtr()));
+}
+
+void GCMNetworkChannel::OnRegisterComplete(
+ const std::string& registration_id,
+ gcm::GCMClient::Result result) {
+ DCHECK(CalledOnValidThread());
+ if (result == gcm::GCMClient::SUCCESS) {
+ DCHECK(!registration_id.empty());
+ DVLOG(2) << "Got registration_id";
+ register_backoff_entry_->Reset();
+ registration_id_ = registration_id;
+ if (!cached_message_.empty())
+ RequestAccessToken();
+ } else {
+ DVLOG(2) << "Register failed: " << result;
+ // Retry in case of transient error.
+ switch (result) {
+ case gcm::GCMClient::NETWORK_ERROR:
+ case gcm::GCMClient::SERVER_ERROR:
+ case gcm::GCMClient::TTL_EXCEEDED:
+ case gcm::GCMClient::UNKNOWN_ERROR: {
+ register_backoff_entry_->InformOfRequest(false);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&GCMNetworkChannel::Register,
+ weak_factory_.GetWeakPtr()),
+ register_backoff_entry_->GetTimeUntilRelease());
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ diagnostic_info_.registration_id_ = registration_id_;
+ diagnostic_info_.registration_result_ = result;
+}
+
+void GCMNetworkChannel::SendMessage(const std::string& message) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!message.empty());
+ DVLOG(2) << "SendMessage";
+ diagnostic_info_.sent_messages_count_++;
+ if (!cached_message_.empty()) {
+ RecordOutgoingMessageStatus(MESSAGE_DISCARDED);
+ }
+ cached_message_ = message;
+
+ if (!registration_id_.empty()) {
+ RequestAccessToken();
+ }
+}
+
+void GCMNetworkChannel::RequestAccessToken() {
+ DCHECK(CalledOnValidThread());
+ delegate_->RequestToken(base::Bind(&GCMNetworkChannel::OnGetTokenComplete,
+ weak_factory_.GetWeakPtr()));
+}
+
+void GCMNetworkChannel::OnGetTokenComplete(
+ const GoogleServiceAuthError& error,
+ const std::string& token) {
+ DCHECK(CalledOnValidThread());
+ if (cached_message_.empty()) {
+ // Nothing to do.
+ return;
+ }
+
+ if (error.state() != GoogleServiceAuthError::NONE) {
+ // Requesting access token failed. Persistent errors will be reported by
+ // token service. Just drop this request, cacheinvalidations will retry
+ // sending message and at that time we'll retry requesting access token.
+ DVLOG(1) << "RequestAccessToken failed: " << error.ToString();
+ RecordOutgoingMessageStatus(ACCESS_TOKEN_FAILURE);
+ // Message won't get sent because of connection failure. Let's retry once
+ // connection is restored.
+ if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED)
+ NotifyStateChange(TRANSIENT_INVALIDATION_ERROR);
+ cached_message_.clear();
+ return;
+ }
+ DCHECK(!token.empty());
+ // Save access token in case POST fails and we need to invalidate it.
+ access_token_ = token;
+
+ DVLOG(2) << "Got access token, sending message";
+ fetcher_.reset(net::URLFetcher::Create(
+ BuildUrl(registration_id_), net::URLFetcher::POST, this));
+ fetcher_->SetRequestContext(request_context_getter_);
+ const std::string auth_header("Authorization: Bearer " + access_token_);
+ fetcher_->AddExtraRequestHeader(auth_header);
+ if (!echo_token_.empty()) {
+ const std::string echo_header("echo-token: " + echo_token_);
+ fetcher_->AddExtraRequestHeader(echo_header);
+ }
+ fetcher_->SetUploadData("application/x-protobuffer", cached_message_);
+ fetcher_->Start();
+ // Clear message to prevent accidentally resending it in the future.
+ cached_message_.clear();
+}
+
+void GCMNetworkChannel::OnURLFetchComplete(const net::URLFetcher* source) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(fetcher_, source);
+ // Free fetcher at the end of function.
+ scoped_ptr<net::URLFetcher> fetcher = fetcher_.Pass();
+
+ net::URLRequestStatus status = fetcher->GetStatus();
+ diagnostic_info_.last_post_response_code_ =
+ status.is_success() ? source->GetResponseCode() : status.error();
+
+ if (status.is_success() &&
+ fetcher->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
+ DVLOG(1) << "URLFetcher failure: HTTP_UNAUTHORIZED";
+ delegate_->InvalidateToken(access_token_);
+ }
+
+ if (!status.is_success() ||
+ (fetcher->GetResponseCode() != net::HTTP_OK &&
+ fetcher->GetResponseCode() != net::HTTP_NO_CONTENT)) {
+ DVLOG(1) << "URLFetcher failure";
+ RecordOutgoingMessageStatus(POST_FAILURE);
+ NotifyStateChange(TRANSIENT_INVALIDATION_ERROR);
+ return;
+ }
+
+ RecordOutgoingMessageStatus(OUTGOING_MESSAGE_SUCCESS);
+ NotifyStateChange(INVALIDATIONS_ENABLED);
+ DVLOG(2) << "URLFetcher success";
+}
+
+void GCMNetworkChannel::OnIncomingMessage(const std::string& message,
+ const std::string& echo_token) {
+#if !defined(OS_ANDROID)
+ if (!echo_token.empty())
+ echo_token_ = echo_token;
+ diagnostic_info_.last_message_empty_echo_token_ = echo_token.empty();
+ diagnostic_info_.last_message_received_time_ = base::Time::Now();
+
+ if (message.empty()) {
+ RecordIncomingMessageStatus(MESSAGE_EMPTY);
+ return;
+ }
+ std::string data;
+ if (!Base64DecodeURLSafe(message, &data)) {
+ RecordIncomingMessageStatus(INVALID_ENCODING);
+ return;
+ }
+ ipc::invalidation::AddressedAndroidMessage android_message;
+ if (!android_message.ParseFromString(data) ||
+ !android_message.has_message()) {
+ RecordIncomingMessageStatus(INVALID_PROTO);
+ return;
+ }
+ DVLOG(2) << "Deliver incoming message";
+ RecordIncomingMessageStatus(INCOMING_MESSAGE_SUCCESS);
+ DeliverIncomingMessage(android_message.message());
+#else
+ // This code shouldn't be invoked on Android.
+ NOTREACHED();
+#endif
+}
+
+void GCMNetworkChannel::OnNetworkChanged(
+ net::NetworkChangeNotifier::ConnectionType connection_type) {
+ // Network connection is restored. Let's notify cacheinvalidations so it has
+ // chance to retry.
+ if (connection_type != net::NetworkChangeNotifier::CONNECTION_NONE)
+ NotifyStateChange(INVALIDATIONS_ENABLED);
+}
+
+GURL GCMNetworkChannel::BuildUrl(const std::string& registration_id) {
+ DCHECK(!registration_id.empty());
+
+#if !defined(OS_ANDROID)
+ ipc::invalidation::EndpointId endpoint_id;
+ endpoint_id.set_c2dm_registration_id(registration_id);
+ endpoint_id.set_client_key(std::string());
+ endpoint_id.set_package_name(kCacheInvalidationPackageName);
+ endpoint_id.mutable_channel_version()->set_major_version(
+ ipc::invalidation::INITIAL);
+ std::string endpoint_id_buffer;
+ endpoint_id.SerializeToString(&endpoint_id_buffer);
+
+ ipc::invalidation::NetworkEndpointId network_endpoint_id;
+ network_endpoint_id.set_network_address(
+ ipc::invalidation::NetworkEndpointId_NetworkAddress_ANDROID);
+ network_endpoint_id.set_client_address(endpoint_id_buffer);
+ std::string network_endpoint_id_buffer;
+ network_endpoint_id.SerializeToString(&network_endpoint_id_buffer);
+
+ std::string base64URLPiece;
+ Base64EncodeURLSafe(network_endpoint_id_buffer, &base64URLPiece);
+
+ std::string url(kCacheInvalidationEndpointUrl);
+ url += base64URLPiece;
+ return GURL(url);
+#else
+ // This code shouldn't be invoked on Android.
+ NOTREACHED();
+ return GURL();
+#endif
+}
+
+void GCMNetworkChannel::Base64EncodeURLSafe(const std::string& input,
+ std::string* output) {
+ base::Base64Encode(input, output);
+ // Covert to url safe alphabet.
+ base::ReplaceChars(*output, "+", "-", output);
+ base::ReplaceChars(*output, "/", "_", output);
+ // Trim padding.
+ size_t padding_size = 0;
+ for (size_t i = output->size(); i > 0 && (*output)[i - 1] == '='; --i)
+ ++padding_size;
+ output->resize(output->size() - padding_size);
+}
+
+bool GCMNetworkChannel::Base64DecodeURLSafe(const std::string& input,
+ std::string* output) {
+ // Add padding.
+ size_t padded_size = (input.size() + 3) - (input.size() + 3) % 4;
+ std::string padded_input(input);
+ padded_input.resize(padded_size, '=');
+ // Convert to standard base64 alphabet.
+ base::ReplaceChars(padded_input, "-", "+", &padded_input);
+ base::ReplaceChars(padded_input, "_", "/", &padded_input);
+ return base::Base64Decode(padded_input, output);
+}
+
+void GCMNetworkChannel::SetMessageReceiver(
+ invalidation::MessageCallback* incoming_receiver) {
+ delegate_->SetMessageReceiver(base::Bind(
+ &GCMNetworkChannel::OnIncomingMessage, weak_factory_.GetWeakPtr()));
+ SyncNetworkChannel::SetMessageReceiver(incoming_receiver);
+}
+
+void GCMNetworkChannel::RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) {
+ callback.Run(*diagnostic_info_.CollectDebugData());
+}
+
+void GCMNetworkChannel::UpdateCredentials(const std::string& email,
+ const std::string& token) {
+ // Do nothing. We get access token by requesting it for every message.
+}
+
+int GCMNetworkChannel::GetInvalidationClientType() {
+#if defined(OS_IOS)
+ return ipc::invalidation::ClientType::CHROME_SYNC_GCM_IOS;
+#else
+ return ipc::invalidation::ClientType::CHROME_SYNC_GCM_DESKTOP;
+#endif
+}
+
+void GCMNetworkChannel::ResetRegisterBackoffEntryForTest(
+ const net::BackoffEntry::Policy* policy) {
+ register_backoff_entry_.reset(new net::BackoffEntry(policy));
+}
+
+GCMNetworkChannelDiagnostic::GCMNetworkChannelDiagnostic(
+ GCMNetworkChannel* parent)
+ : parent_(parent),
+ last_message_empty_echo_token_(false),
+ last_post_response_code_(0),
+ registration_result_(gcm::GCMClient::UNKNOWN_ERROR),
+ sent_messages_count_(0) {}
+
+scoped_ptr<base::DictionaryValue>
+GCMNetworkChannelDiagnostic::CollectDebugData() const {
+ scoped_ptr<base::DictionaryValue> status(new base::DictionaryValue);
+ status->SetString("GCMNetworkChannel.Channel", "GCM");
+ std::string reg_id_hash = base::SHA1HashString(registration_id_);
+ status->SetString("GCMNetworkChannel.HashedRegistrationID",
+ base::HexEncode(reg_id_hash.c_str(), reg_id_hash.size()));
+ status->SetString("GCMNetworkChannel.RegistrationResult",
+ GCMClientResultToString(registration_result_));
+ status->SetBoolean("GCMNetworkChannel.HadLastMessageEmptyEchoToken",
+ last_message_empty_echo_token_);
+ status->SetString(
+ "GCMNetworkChannel.LastMessageReceivedTime",
+ base::TimeFormatShortDateAndTime(last_message_received_time_));
+ status->SetInteger("GCMNetworkChannel.LastPostResponseCode",
+ last_post_response_code_);
+ status->SetInteger("GCMNetworkChannel.SentMessages", sent_messages_count_);
+ status->SetInteger("GCMNetworkChannel.ReceivedMessages",
+ parent_->GetReceivedMessagesCount());
+ return status.Pass();
+}
+
+std::string GCMNetworkChannelDiagnostic::GCMClientResultToString(
+ const gcm::GCMClient::Result result) const {
+#define ENUM_CASE(x) case x: return #x; break;
+ switch (result) {
+ ENUM_CASE(gcm::GCMClient::SUCCESS);
+ ENUM_CASE(gcm::GCMClient::NETWORK_ERROR);
+ ENUM_CASE(gcm::GCMClient::SERVER_ERROR);
+ ENUM_CASE(gcm::GCMClient::TTL_EXCEEDED);
+ ENUM_CASE(gcm::GCMClient::UNKNOWN_ERROR);
+ ENUM_CASE(gcm::GCMClient::NOT_SIGNED_IN);
+ ENUM_CASE(gcm::GCMClient::INVALID_PARAMETER);
+ ENUM_CASE(gcm::GCMClient::ASYNC_OPERATION_PENDING);
+ ENUM_CASE(gcm::GCMClient::GCM_DISABLED);
+ }
+ NOTREACHED();
+ return "";
+}
+
+} // namespace syncer
diff --git a/sync/notifier/gcm_network_channel.h b/sync/notifier/gcm_network_channel.h
new file mode 100644
index 0000000..5f86ce7
--- /dev/null
+++ b/sync/notifier/gcm_network_channel.h
@@ -0,0 +1,135 @@
+// Copyright 2014 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.
+
+#ifndef SYNC_NOTIFIER_GCM_NETWORK_CHANNEL_H_
+#define SYNC_NOTIFIER_GCM_NETWORK_CHANNEL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/network_change_notifier.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "sync/base/sync_export.h"
+#include "sync/notifier/gcm_network_channel_delegate.h"
+#include "sync/notifier/sync_system_resources.h"
+#include "url/gurl.h"
+
+class GoogleServiceAuthError;
+
+namespace syncer {
+class GCMNetworkChannel;
+
+// POD with copy of some statuses for debugging purposes.
+struct GCMNetworkChannelDiagnostic {
+ explicit GCMNetworkChannelDiagnostic(GCMNetworkChannel* parent);
+
+ // Collect all the internal variables in a single readable dictionary.
+ scoped_ptr<base::DictionaryValue> CollectDebugData() const;
+
+ // TODO(pavely): Move this toString to a more appropiate place in GCMClient.
+ std::string GCMClientResultToString(
+ const gcm::GCMClient::Result result) const;
+
+ GCMNetworkChannel* parent_;
+ bool last_message_empty_echo_token_;
+ base::Time last_message_received_time_;
+ int last_post_response_code_;
+ std::string registration_id_;
+ gcm::GCMClient::Result registration_result_;
+ int sent_messages_count_;
+};
+
+// GCMNetworkChannel is an implementation of SyncNetworkChannel that routes
+// messages through GCMService.
+class SYNC_EXPORT_PRIVATE GCMNetworkChannel
+ : public SyncNetworkChannel,
+ public net::URLFetcherDelegate,
+ public net::NetworkChangeNotifier::NetworkChangeObserver,
+ public base::NonThreadSafe {
+ public:
+ GCMNetworkChannel(
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ scoped_ptr<GCMNetworkChannelDelegate> delegate);
+
+ virtual ~GCMNetworkChannel();
+
+ // invalidation::NetworkChannel implementation.
+ virtual void SendMessage(const std::string& message) OVERRIDE;
+ virtual void SetMessageReceiver(
+ invalidation::MessageCallback* incoming_receiver) OVERRIDE;
+
+ // SyncNetworkChannel implementation.
+ virtual void UpdateCredentials(const std::string& email,
+ const std::string& token) OVERRIDE;
+ virtual int GetInvalidationClientType() OVERRIDE;
+ virtual void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) OVERRIDE;
+
+ // URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ // NetworkChangeObserver implementation.
+ virtual void OnNetworkChanged(
+ net::NetworkChangeNotifier::ConnectionType connection_type) OVERRIDE;
+
+ protected:
+ void ResetRegisterBackoffEntryForTest(
+ const net::BackoffEntry::Policy* policy);
+
+ virtual GURL BuildUrl(const std::string& registration_id);
+
+ private:
+ friend class GCMNetworkChannelTest;
+ void Register();
+ void OnRegisterComplete(const std::string& registration_id,
+ gcm::GCMClient::Result result);
+ void RequestAccessToken();
+ void OnGetTokenComplete(const GoogleServiceAuthError& error,
+ const std::string& token);
+ void OnIncomingMessage(const std::string& message,
+ const std::string& echo_token);
+
+ // Base64 encoding/decoding with URL safe alphabet.
+ // http://tools.ietf.org/html/rfc4648#page-7
+ static void Base64EncodeURLSafe(const std::string& input,
+ std::string* output);
+ static bool Base64DecodeURLSafe(const std::string& input,
+ std::string* output);
+
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ scoped_ptr<GCMNetworkChannelDelegate> delegate_;
+
+ // Message is saved until all conditions are met: there is valid
+ // registration_id and access_token.
+ std::string cached_message_;
+
+ // Access token is saved because in case of auth failure from server we need
+ // to invalidate it.
+ std::string access_token_;
+
+ // GCM registration_id is requested one at startup and never refreshed until
+ // next restart.
+ std::string registration_id_;
+ scoped_ptr<net::BackoffEntry> register_backoff_entry_;
+
+ scoped_ptr<net::URLFetcher> fetcher_;
+
+ // cacheinvalidation client receives echo_token with incoming message from
+ // GCM and shuld include it in headers with outgoing message over http.
+ std::string echo_token_;
+
+ GCMNetworkChannelDiagnostic diagnostic_info_;
+
+ base::WeakPtrFactory<GCMNetworkChannel> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCMNetworkChannel);
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_GCM_NETWORK_CHANNEL_H_
diff --git a/sync/notifier/gcm_network_channel_delegate.h b/sync/notifier/gcm_network_channel_delegate.h
new file mode 100644
index 0000000..36485a1
--- /dev/null
+++ b/sync/notifier/gcm_network_channel_delegate.h
@@ -0,0 +1,51 @@
+// Copyright 2014 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.
+
+#ifndef SYNC_NOTIFIER_GCM_NETWORK_CHANNEL_DELEGATE_H_
+#define SYNC_NOTIFIER_GCM_NETWORK_CHANNEL_DELEGATE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "google_apis/gcm/gcm_client.h"
+
+class GoogleServiceAuthError;
+
+namespace syncer {
+
+// Delegate for GCMNetworkChannel.
+// GCMNetworkChannel needs Register to register with GCM client and obtain gcm
+// registration id. This id is used for building URL to cache invalidation
+// endpoint.
+// It needs RequestToken and InvalidateToken to get access token to include it
+// in HTTP message to server.
+// GCMNetworkChannel lives on IO thread therefore calls will be made on IO
+// thread and callbacks should be invoked there as well.
+class GCMNetworkChannelDelegate {
+ public:
+ typedef base::Callback<void(const GoogleServiceAuthError& error,
+ const std::string& token)> RequestTokenCallback;
+ typedef base::Callback<void(const std::string& registration_id,
+ gcm::GCMClient::Result result)> RegisterCallback;
+ typedef base::Callback<void(const std::string& message,
+ const std::string& echo_token)> MessageCallback;
+
+ virtual ~GCMNetworkChannelDelegate() {}
+
+ virtual void Initialize() = 0;
+ // Request access token. Callback should be called either with access token or
+ // error code.
+ virtual void RequestToken(RequestTokenCallback callback) = 0;
+ // Invalidate access token that was rejected by server.
+ virtual void InvalidateToken(const std::string& token) = 0;
+
+ // Request registration_id from GCMService. Callback should be called with
+ // either registration id or error code.
+ virtual void Register(RegisterCallback callback) = 0;
+ // Provide callback for incoming messages from GCM.
+ virtual void SetMessageReceiver(MessageCallback callback) = 0;
+};
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_GCM_NETWORK_CHANNEL_DELEGATE_H_
diff --git a/sync/notifier/gcm_network_channel_unittest.cc b/sync/notifier/gcm_network_channel_unittest.cc
new file mode 100644
index 0000000..2564d0b
--- /dev/null
+++ b/sync/notifier/gcm_network_channel_unittest.cc
@@ -0,0 +1,494 @@
+// Copyright 2014 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 "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_test_util.h"
+#include "sync/notifier/gcm_network_channel.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+class TestGCMNetworkChannelDelegate : public GCMNetworkChannelDelegate {
+ public:
+ TestGCMNetworkChannelDelegate()
+ : register_call_count_(0) {}
+
+ virtual void Initialize() OVERRIDE {}
+
+ virtual void RequestToken(RequestTokenCallback callback) OVERRIDE {
+ request_token_callback = callback;
+ }
+
+ virtual void InvalidateToken(const std::string& token) OVERRIDE {
+ invalidated_token = token;
+ }
+
+ virtual void Register(RegisterCallback callback) OVERRIDE {
+ ++register_call_count_;
+ register_callback = callback;
+ }
+
+ virtual void SetMessageReceiver(MessageCallback callback) OVERRIDE {
+ message_callback = callback;
+ }
+
+ RequestTokenCallback request_token_callback;
+ std::string invalidated_token;
+ RegisterCallback register_callback;
+ int register_call_count_;
+ MessageCallback message_callback;
+};
+
+// Backoff policy for test. Run first 5 retries without delay.
+const net::BackoffEntry::Policy kTestBackoffPolicy = {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ 5,
+
+ // Initial delay for exponential back-off in ms.
+ 2000, // 2 seconds.
+
+ // Factor by which the waiting time will be multiplied.
+ 2,
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ 0.2, // 20%.
+
+ // Maximum amount of time we are willing to delay our request in ms.
+ 1000 * 3600 * 4, // 4 hours.
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ -1,
+
+ // Don't use initial delay unless the last request was an error.
+ false,
+};
+
+class TestGCMNetworkChannel : public GCMNetworkChannel {
+ public:
+ TestGCMNetworkChannel(
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ scoped_ptr<GCMNetworkChannelDelegate> delegate)
+ : GCMNetworkChannel(request_context_getter, delegate.Pass()) {
+ ResetRegisterBackoffEntryForTest(&kTestBackoffPolicy);
+ }
+
+ protected:
+ // On Android GCMNetworkChannel::BuildUrl hits NOTREACHED(). I still want
+ // tests to run.
+ virtual GURL BuildUrl(const std::string& registration_id) OVERRIDE {
+ return GURL("http://test.url.com");
+ }
+};
+
+class GCMNetworkChannelTest;
+
+// Test needs to capture setting echo-token header on http request.
+// This class is going to do that.
+class TestNetworkChannelURLFetcher : public net::FakeURLFetcher {
+ public:
+ TestNetworkChannelURLFetcher(GCMNetworkChannelTest* test,
+ const GURL& url,
+ net::URLFetcherDelegate* delegate,
+ const std::string& response_data,
+ net::HttpStatusCode response_code,
+ net::URLRequestStatus::Status status)
+ : net::FakeURLFetcher(url,
+ delegate,
+ response_data,
+ response_code,
+ status),
+ test_(test) {}
+
+ virtual void AddExtraRequestHeader(const std::string& header_line) OVERRIDE;
+
+ private:
+ GCMNetworkChannelTest* test_;
+};
+
+class GCMNetworkChannelTest
+ : public ::testing::Test,
+ public SyncNetworkChannel::Observer {
+ public:
+ GCMNetworkChannelTest()
+ : delegate_(NULL),
+ url_fetchers_created_count_(0),
+ last_invalidator_state_(TRANSIENT_INVALIDATION_ERROR) {}
+
+ virtual ~GCMNetworkChannelTest() {
+ }
+
+ virtual void SetUp() {
+ request_context_getter_ = new net::TestURLRequestContextGetter(
+ base::MessageLoopProxy::current());
+ // Ownership of delegate goes to GCNMentworkChannel but test needs pointer
+ // to it.
+ delegate_ = new TestGCMNetworkChannelDelegate();
+ scoped_ptr<GCMNetworkChannelDelegate> delegate(delegate_);
+ gcm_network_channel_.reset(new TestGCMNetworkChannel(
+ request_context_getter_,
+ delegate.Pass()));
+ gcm_network_channel_->AddObserver(this);
+ gcm_network_channel_->SetMessageReceiver(
+ invalidation::NewPermanentCallback(
+ this, &GCMNetworkChannelTest::OnIncomingMessage));
+ url_fetcher_factory_.reset(new net::FakeURLFetcherFactory(NULL,
+ base::Bind(&GCMNetworkChannelTest::CreateURLFetcher,
+ base::Unretained(this))));
+ }
+
+ virtual void TearDown() {
+ gcm_network_channel_->RemoveObserver(this);
+ }
+
+ // Helper functions to call private methods from test
+ GURL BuildUrl(const std::string& registration_id) {
+ return gcm_network_channel_->GCMNetworkChannel::BuildUrl(registration_id);
+ }
+
+ static void Base64EncodeURLSafe(const std::string& input,
+ std::string* output) {
+ GCMNetworkChannel::Base64EncodeURLSafe(input, output);
+ }
+
+ static bool Base64DecodeURLSafe(const std::string& input,
+ std::string* output) {
+ return GCMNetworkChannel::Base64DecodeURLSafe(input, output);
+ }
+
+ virtual void OnNetworkChannelStateChanged(
+ InvalidatorState invalidator_state) OVERRIDE {
+ last_invalidator_state_ = invalidator_state;
+ }
+
+ void OnIncomingMessage(std::string incoming_message) {
+ }
+
+ GCMNetworkChannel* network_channel() {
+ return gcm_network_channel_.get();
+ }
+
+ TestGCMNetworkChannelDelegate* delegate() {
+ return delegate_;
+ }
+
+ int url_fetchers_created_count() {
+ return url_fetchers_created_count_;
+ }
+
+ net::FakeURLFetcherFactory* url_fetcher_factory() {
+ return url_fetcher_factory_.get();
+ }
+
+ scoped_ptr<net::FakeURLFetcher> CreateURLFetcher(
+ const GURL& url,
+ net::URLFetcherDelegate* delegate,
+ const std::string& response_data,
+ net::HttpStatusCode response_code,
+ net::URLRequestStatus::Status status) {
+ ++url_fetchers_created_count_;
+ return scoped_ptr<net::FakeURLFetcher>(new TestNetworkChannelURLFetcher(
+ this, url, delegate, response_data, response_code, status));
+ }
+
+ void set_last_echo_token(const std::string& echo_token) {
+ last_echo_token_ = echo_token;
+ }
+
+ const std::string& get_last_echo_token() {
+ return last_echo_token_;
+ }
+
+ InvalidatorState get_last_invalidator_state() {
+ return last_invalidator_state_;
+ }
+
+ void RunLoopUntilIdle() {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ TestGCMNetworkChannelDelegate* delegate_;
+ scoped_ptr<GCMNetworkChannel> gcm_network_channel_;
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
+ scoped_ptr<net::FakeURLFetcherFactory> url_fetcher_factory_;
+ int url_fetchers_created_count_;
+ std::string last_echo_token_;
+ InvalidatorState last_invalidator_state_;
+};
+
+void TestNetworkChannelURLFetcher::AddExtraRequestHeader(
+ const std::string& header_line) {
+ net::FakeURLFetcher::AddExtraRequestHeader(header_line);
+ std::string header_name("echo-token: ");
+ std::string echo_token;
+ if (StartsWithASCII(header_line, header_name, false)) {
+ echo_token = header_line;
+ ReplaceFirstSubstringAfterOffset(
+ &echo_token, 0, header_name, std::string());
+ test_->set_last_echo_token(echo_token);
+ }
+}
+
+TEST_F(GCMNetworkChannelTest, HappyCase) {
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, get_last_invalidator_state());
+ EXPECT_FALSE(delegate()->message_callback.is_null());
+ url_fetcher_factory()->SetFakeResponse(GURL("http://test.url.com"),
+ std::string(),
+ net::HTTP_NO_CONTENT,
+ net::URLRequestStatus::SUCCESS);
+
+ // After construction GCMNetworkChannel should have called Register.
+ EXPECT_FALSE(delegate()->register_callback.is_null());
+ // Return valid registration id.
+ delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
+
+ network_channel()->SendMessage("abra.cadabra");
+ // SendMessage should have triggered RequestToken. No HTTP request should be
+ // started yet.
+ EXPECT_FALSE(delegate()->request_token_callback.is_null());
+ EXPECT_EQ(url_fetchers_created_count(), 0);
+ // Return valid access token. This should trigger HTTP request.
+ delegate()->request_token_callback.Run(
+ GoogleServiceAuthError::AuthErrorNone(), "access.token");
+ RunLoopUntilIdle();
+ EXPECT_EQ(url_fetchers_created_count(), 1);
+
+ // Return another access token. Message should be cleared by now and shouldn't
+ // be sent.
+ delegate()->request_token_callback.Run(
+ GoogleServiceAuthError::AuthErrorNone(), "access.token2");
+ RunLoopUntilIdle();
+ EXPECT_EQ(url_fetchers_created_count(), 1);
+ EXPECT_EQ(INVALIDATIONS_ENABLED, get_last_invalidator_state());
+}
+
+TEST_F(GCMNetworkChannelTest, FailedRegister) {
+ // After construction GCMNetworkChannel should have called Register.
+ EXPECT_FALSE(delegate()->register_callback.is_null());
+ EXPECT_EQ(1, delegate()->register_call_count_);
+ // Return transient error from Register call.
+ delegate()->register_callback.Run("", gcm::GCMClient::NETWORK_ERROR);
+ RunLoopUntilIdle();
+ // GcmNetworkChannel should have scheduled Register retry.
+ EXPECT_EQ(2, delegate()->register_call_count_);
+ // Return persistent error from Register call.
+ delegate()->register_callback.Run("", gcm::GCMClient::NOT_SIGNED_IN);
+ RunLoopUntilIdle();
+ // GcmNetworkChannel should give up trying.
+ EXPECT_EQ(2, delegate()->register_call_count_);
+
+ network_channel()->SendMessage("abra.cadabra");
+ // SendMessage shouldn't trigger RequestToken.
+ EXPECT_TRUE(delegate()->request_token_callback.is_null());
+ EXPECT_EQ(0, url_fetchers_created_count());
+}
+
+TEST_F(GCMNetworkChannelTest, RegisterFinishesAfterSendMessage) {
+ url_fetcher_factory()->SetFakeResponse(GURL("http://test.url.com"),
+ "",
+ net::HTTP_NO_CONTENT,
+ net::URLRequestStatus::SUCCESS);
+
+ // After construction GCMNetworkChannel should have called Register.
+ EXPECT_FALSE(delegate()->register_callback.is_null());
+
+ network_channel()->SendMessage("abra.cadabra");
+ // SendMessage shouldn't trigger RequestToken.
+ EXPECT_TRUE(delegate()->request_token_callback.is_null());
+ EXPECT_EQ(url_fetchers_created_count(), 0);
+
+ // Return valid registration id.
+ delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
+
+ EXPECT_FALSE(delegate()->request_token_callback.is_null());
+ EXPECT_EQ(url_fetchers_created_count(), 0);
+ // Return valid access token. This should trigger HTTP request.
+ delegate()->request_token_callback.Run(
+ GoogleServiceAuthError::AuthErrorNone(), "access.token");
+ RunLoopUntilIdle();
+ EXPECT_EQ(url_fetchers_created_count(), 1);
+}
+
+TEST_F(GCMNetworkChannelTest, RequestTokenFailure) {
+ // After construction GCMNetworkChannel should have called Register.
+ EXPECT_FALSE(delegate()->register_callback.is_null());
+ // Return valid registration id.
+ delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
+
+ network_channel()->SendMessage("abra.cadabra");
+ // SendMessage should have triggered RequestToken. No HTTP request should be
+ // started yet.
+ EXPECT_FALSE(delegate()->request_token_callback.is_null());
+ EXPECT_EQ(url_fetchers_created_count(), 0);
+ // RequestToken returns failure.
+ delegate()->request_token_callback.Run(
+ GoogleServiceAuthError::FromConnectionError(1), "");
+
+ // Should be no HTTP requests.
+ EXPECT_EQ(url_fetchers_created_count(), 0);
+}
+
+TEST_F(GCMNetworkChannelTest, AuthErrorFromServer) {
+ // Setup fake response to return AUTH_ERROR.
+ url_fetcher_factory()->SetFakeResponse(GURL("http://test.url.com"),
+ "",
+ net::HTTP_UNAUTHORIZED,
+ net::URLRequestStatus::SUCCESS);
+
+ // After construction GCMNetworkChannel should have called Register.
+ EXPECT_FALSE(delegate()->register_callback.is_null());
+ // Return valid registration id.
+ delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
+
+ network_channel()->SendMessage("abra.cadabra");
+ // SendMessage should have triggered RequestToken. No HTTP request should be
+ // started yet.
+ EXPECT_FALSE(delegate()->request_token_callback.is_null());
+ EXPECT_EQ(url_fetchers_created_count(), 0);
+ // Return valid access token. This should trigger HTTP request.
+ delegate()->request_token_callback.Run(
+ GoogleServiceAuthError::AuthErrorNone(), "access.token");
+ RunLoopUntilIdle();
+ EXPECT_EQ(url_fetchers_created_count(), 1);
+ EXPECT_EQ(delegate()->invalidated_token, "access.token");
+}
+
+// Following two tests are to check for memory leaks/crashes when Register and
+// RequestToken don't complete by the teardown.
+TEST_F(GCMNetworkChannelTest, RegisterNeverCompletes) {
+ network_channel()->SendMessage("abra.cadabra");
+ // Register should be called by now. Let's not complete and see what happens.
+ EXPECT_FALSE(delegate()->register_callback.is_null());
+}
+
+TEST_F(GCMNetworkChannelTest, RequestTokenNeverCompletes) {
+ network_channel()->SendMessage("abra.cadabra");
+ // Return valid registration id.
+ delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
+ // RequestToken should be called by now. Let's not complete and see what
+ // happens.
+ EXPECT_FALSE(delegate()->request_token_callback.is_null());
+}
+
+TEST_F(GCMNetworkChannelTest, Base64EncodeDecode) {
+ std::string input;
+ std::string plain;
+ std::string base64;
+ // Empty string.
+ Base64EncodeURLSafe(input, &base64);
+ EXPECT_TRUE(base64.empty());
+ EXPECT_TRUE(Base64DecodeURLSafe(base64, &plain));
+ EXPECT_EQ(input, plain);
+ // String length: 1..7.
+ for (int length = 1; length < 8; length++) {
+ input = "abra.cadabra";
+ input.resize(length);
+ Base64EncodeURLSafe(input, &base64);
+ // Ensure no padding at the end.
+ EXPECT_NE(base64[base64.size() - 1], '=');
+ EXPECT_TRUE(Base64DecodeURLSafe(base64, &plain));
+ EXPECT_EQ(input, plain);
+ }
+ // Presence of '-', '_'.
+ input = "\xfb\xff";
+ Base64EncodeURLSafe(input, &base64);
+ EXPECT_EQ("-_8", base64);
+ EXPECT_TRUE(Base64DecodeURLSafe(base64, &plain));
+ EXPECT_EQ(input, plain);
+}
+
+TEST_F(GCMNetworkChannelTest, TransientError) {
+ EXPECT_FALSE(delegate()->message_callback.is_null());
+ // POST will fail.
+ url_fetcher_factory()->SetFakeResponse(GURL("http://test.url.com"),
+ std::string(),
+ net::HTTP_SERVICE_UNAVAILABLE,
+ net::URLRequestStatus::SUCCESS);
+
+ delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
+
+ network_channel()->SendMessage("abra.cadabra");
+ EXPECT_FALSE(delegate()->request_token_callback.is_null());
+ delegate()->request_token_callback.Run(
+ GoogleServiceAuthError::AuthErrorNone(), "access.token");
+ RunLoopUntilIdle();
+ EXPECT_EQ(url_fetchers_created_count(), 1);
+ // Failing HTTP POST should cause TRANSIENT_INVALIDATION_ERROR.
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, get_last_invalidator_state());
+ // Network change to CONNECTION_NONE shouldn't affect invalidator state.
+ network_channel()->OnNetworkChanged(
+ net::NetworkChangeNotifier::CONNECTION_NONE);
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, get_last_invalidator_state());
+ // Network change to something else should trigger retry.
+ network_channel()->OnNetworkChanged(
+ net::NetworkChangeNotifier::CONNECTION_WIFI);
+ EXPECT_EQ(INVALIDATIONS_ENABLED, get_last_invalidator_state());
+ network_channel()->OnNetworkChanged(
+ net::NetworkChangeNotifier::CONNECTION_NONE);
+ EXPECT_EQ(INVALIDATIONS_ENABLED, get_last_invalidator_state());
+}
+
+#if !defined(OS_ANDROID)
+TEST_F(GCMNetworkChannelTest, BuildUrl) {
+ GURL url = BuildUrl("registration.id");
+ EXPECT_TRUE(url.SchemeIsHTTPOrHTTPS());
+ EXPECT_FALSE(url.host().empty());
+ EXPECT_FALSE(url.path().empty());
+ std::vector<std::string> parts;
+ Tokenize(url.path(), "/", &parts);
+ std::string buffer;
+ EXPECT_TRUE(Base64DecodeURLSafe(parts[parts.size() - 1], &buffer));
+}
+
+TEST_F(GCMNetworkChannelTest, EchoToken) {
+ url_fetcher_factory()->SetFakeResponse(GURL("http://test.url.com"),
+ std::string(),
+ net::HTTP_OK,
+ net::URLRequestStatus::SUCCESS);
+ // After construction GCMNetworkChannel should have called Register.
+ // Return valid registration id.
+ delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
+
+ network_channel()->SendMessage("abra.cadabra");
+ // Return valid access token. This should trigger HTTP request.
+ delegate()->request_token_callback.Run(
+ GoogleServiceAuthError::AuthErrorNone(), "access.token");
+ RunLoopUntilIdle();
+ EXPECT_EQ(url_fetchers_created_count(), 1);
+ EXPECT_TRUE(get_last_echo_token().empty());
+
+ // Trigger response.
+ delegate()->message_callback.Run("abra.cadabra", "echo.token");
+ // Send another message.
+ network_channel()->SendMessage("abra.cadabra");
+ // Return valid access token. This should trigger HTTP request.
+ delegate()->request_token_callback.Run(
+ GoogleServiceAuthError::AuthErrorNone(), "access.token");
+ RunLoopUntilIdle();
+ EXPECT_EQ(url_fetchers_created_count(), 2);
+ EXPECT_EQ("echo.token", get_last_echo_token());
+
+ // Trigger response with empty echo token.
+ delegate()->message_callback.Run("abra.cadabra", "");
+ // Send another message.
+ network_channel()->SendMessage("abra.cadabra");
+ // Return valid access token. This should trigger HTTP request.
+ delegate()->request_token_callback.Run(
+ GoogleServiceAuthError::AuthErrorNone(), "access.token");
+ RunLoopUntilIdle();
+ EXPECT_EQ(url_fetchers_created_count(), 3);
+ // Echo_token should be from second message.
+ EXPECT_EQ("echo.token", get_last_echo_token());
+}
+#endif
+
+} // namespace syncer
diff --git a/sync/notifier/invalidation_handler.h b/sync/notifier/invalidation_handler.h
index a999cf6..29843904 100644
--- a/sync/notifier/invalidation_handler.h
+++ b/sync/notifier/invalidation_handler.h
@@ -6,7 +6,7 @@
#define SYNC_NOTIFIER_INVALIDATION_HANDLER_H_
#include "sync/base/sync_export.h"
-#include "sync/internal_api/public/base/invalidator_state.h"
+#include "sync/notifier/invalidator_state.h"
namespace syncer {
diff --git a/sync/notifier/invalidation_notifier.cc b/sync/notifier/invalidation_notifier.cc
new file mode 100644
index 0000000..7e3e8a3
--- /dev/null
+++ b/sync/notifier/invalidation_notifier.cc
@@ -0,0 +1,93 @@
+// Copyright 2014 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/notifier/invalidation_notifier.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "google/cacheinvalidation/include/invalidation-client-factory.h"
+#include "jingle/notifier/listener/push_client.h"
+#include "net/url_request/url_request_context.h"
+#include "sync/notifier/invalidation_handler.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace syncer {
+
+InvalidationNotifier::InvalidationNotifier(
+ scoped_ptr<SyncNetworkChannel> network_channel,
+ const std::string& invalidator_client_id,
+ const UnackedInvalidationsMap& saved_invalidations,
+ const std::string& invalidation_bootstrap_data,
+ const WeakHandle<InvalidationStateTracker>& invalidation_state_tracker,
+ const std::string& client_info)
+ : state_(STOPPED),
+ saved_invalidations_(saved_invalidations),
+ invalidation_state_tracker_(invalidation_state_tracker),
+ client_info_(client_info),
+ invalidator_client_id_(invalidator_client_id),
+ invalidation_bootstrap_data_(invalidation_bootstrap_data),
+ invalidation_listener_(network_channel.Pass()) {
+}
+
+InvalidationNotifier::~InvalidationNotifier() {
+ DCHECK(CalledOnValidThread());
+}
+
+void InvalidationNotifier::RegisterHandler(InvalidationHandler* handler) {
+ DCHECK(CalledOnValidThread());
+ registrar_.RegisterHandler(handler);
+}
+
+void InvalidationNotifier::UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids) {
+ DCHECK(CalledOnValidThread());
+ registrar_.UpdateRegisteredIds(handler, ids);
+ invalidation_listener_.UpdateRegisteredIds(registrar_.GetAllRegisteredIds());
+}
+
+void InvalidationNotifier::UnregisterHandler(InvalidationHandler* handler) {
+ DCHECK(CalledOnValidThread());
+ registrar_.UnregisterHandler(handler);
+}
+
+InvalidatorState InvalidationNotifier::GetInvalidatorState() const {
+ DCHECK(CalledOnValidThread());
+ return registrar_.GetInvalidatorState();
+}
+
+void InvalidationNotifier::UpdateCredentials(
+ const std::string& email, const std::string& token) {
+ if (state_ == STOPPED) {
+ invalidation_listener_.Start(
+ base::Bind(&invalidation::CreateInvalidationClient),
+ invalidator_client_id_, client_info_, invalidation_bootstrap_data_,
+ saved_invalidations_,
+ invalidation_state_tracker_,
+ this);
+ state_ = STARTED;
+ }
+ invalidation_listener_.UpdateCredentials(email, token);
+}
+
+void InvalidationNotifier::RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const {
+ DCHECK(CalledOnValidThread());
+ invalidation_listener_.RequestDetailedStatus(callback);
+}
+
+void InvalidationNotifier::OnInvalidate(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ DCHECK(CalledOnValidThread());
+ registrar_.DispatchInvalidationsToHandlers(invalidation_map);
+}
+
+void InvalidationNotifier::OnInvalidatorStateChange(InvalidatorState state) {
+ DCHECK(CalledOnValidThread());
+ registrar_.UpdateInvalidatorState(state);
+}
+
+} // namespace syncer
diff --git a/sync/notifier/invalidation_notifier.h b/sync/notifier/invalidation_notifier.h
new file mode 100644
index 0000000..cd60a06
--- /dev/null
+++ b/sync/notifier/invalidation_notifier.h
@@ -0,0 +1,108 @@
+// Copyright 2014 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.
+//
+// An implementation of Invalidator that wraps an invalidation
+// client. Handles the details of connecting to XMPP and hooking it
+// up to the invalidation client.
+//
+// You probably don't want to use this directly; use
+// NonBlockingInvalidator.
+
+#ifndef SYNC_NOTIFIER_INVALIDATION_NOTIFIER_H_
+#define SYNC_NOTIFIER_INVALIDATION_NOTIFIER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "sync/base/sync_export.h"
+#include "sync/internal_api/public/base/model_type.h"
+#include "sync/internal_api/public/util/weak_handle.h"
+#include "sync/notifier/invalidation_state_tracker.h"
+#include "sync/notifier/invalidator.h"
+#include "sync/notifier/invalidator_registrar.h"
+#include "sync/notifier/sync_invalidation_listener.h"
+
+namespace notifier {
+class PushClient;
+} // namespace notifier
+
+namespace syncer {
+
+// This class must live on the IO thread.
+class SYNC_EXPORT_PRIVATE InvalidationNotifier
+ : public Invalidator,
+ public SyncInvalidationListener::Delegate,
+ public base::NonThreadSafe {
+ public:
+ // |invalidation_state_tracker| must be initialized.
+ InvalidationNotifier(
+ scoped_ptr<SyncNetworkChannel> network_channel,
+ const std::string& invalidator_client_id,
+ const UnackedInvalidationsMap& saved_invalidations,
+ const std::string& invalidation_bootstrap_data,
+ const WeakHandle<InvalidationStateTracker>&
+ invalidation_state_tracker,
+ const std::string& client_info);
+
+ virtual ~InvalidationNotifier();
+
+ // Invalidator implementation.
+ virtual void RegisterHandler(InvalidationHandler* handler) OVERRIDE;
+ virtual void UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids) OVERRIDE;
+ virtual void UnregisterHandler(InvalidationHandler* handler) OVERRIDE;
+ virtual InvalidatorState GetInvalidatorState() const OVERRIDE;
+ virtual void UpdateCredentials(
+ const std::string& email, const std::string& token) OVERRIDE;
+ virtual void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const
+ OVERRIDE;
+
+ // SyncInvalidationListener::Delegate implementation.
+ virtual void OnInvalidate(
+ const ObjectIdInvalidationMap& invalidation_map) OVERRIDE;
+ virtual void OnInvalidatorStateChange(InvalidatorState state) OVERRIDE;
+
+ private:
+ // We start off in the STOPPED state. When we get our initial
+ // credentials, we connect and move to the CONNECTING state. When
+ // we're connected we start the invalidation client and move to the
+ // STARTED state. We never go back to a previous state.
+ enum State {
+ STOPPED,
+ CONNECTING,
+ STARTED
+ };
+ State state_;
+
+ InvalidatorRegistrar registrar_;
+
+ // Passed to |invalidation_listener_|.
+ const UnackedInvalidationsMap saved_invalidations_;
+
+ // Passed to |invalidation_listener_|.
+ const WeakHandle<InvalidationStateTracker>
+ invalidation_state_tracker_;
+
+ // Passed to |invalidation_listener_|.
+ const std::string client_info_;
+
+ // The client ID to pass to |invalidation_listener_|.
+ const std::string invalidator_client_id_;
+
+ // The initial bootstrap data to pass to |invalidation_listener_|.
+ const std::string invalidation_bootstrap_data_;
+
+ // The invalidation listener.
+ SyncInvalidationListener invalidation_listener_;
+
+ DISALLOW_COPY_AND_ASSIGN(InvalidationNotifier);
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_INVALIDATION_NOTIFIER_H_
diff --git a/sync/notifier/invalidation_notifier_unittest.cc b/sync/notifier/invalidation_notifier_unittest.cc
new file mode 100644
index 0000000..75c6b3a
--- /dev/null
+++ b/sync/notifier/invalidation_notifier_unittest.cc
@@ -0,0 +1,92 @@
+// Copyright 2014 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/notifier/invalidation_notifier.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "jingle/notifier/base/fake_base_task.h"
+#include "jingle/notifier/base/notifier_options.h"
+#include "jingle/notifier/listener/fake_push_client.h"
+#include "net/url_request/url_request_test_util.h"
+#include "sync/internal_api/public/base/model_type.h"
+#include "sync/internal_api/public/util/weak_handle.h"
+#include "sync/notifier/fake_invalidation_handler.h"
+#include "sync/notifier/fake_invalidation_state_tracker.h"
+#include "sync/notifier/invalidation_state_tracker.h"
+#include "sync/notifier/invalidator_test_template.h"
+#include "sync/notifier/push_client_channel.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+class InvalidationNotifierTestDelegate {
+ public:
+ InvalidationNotifierTestDelegate() {}
+
+ ~InvalidationNotifierTestDelegate() {
+ DestroyInvalidator();
+ }
+
+ void CreateInvalidator(
+ const std::string& invalidator_client_id,
+ const std::string& initial_state,
+ const base::WeakPtr<InvalidationStateTracker>&
+ invalidation_state_tracker) {
+ DCHECK(!invalidator_.get());
+ scoped_ptr<notifier::PushClient> push_client(
+ new notifier::FakePushClient());
+ scoped_ptr<SyncNetworkChannel> network_channel(
+ new PushClientChannel(push_client.Pass()));
+ invalidator_.reset(
+ new InvalidationNotifier(
+ network_channel.Pass(),
+ invalidator_client_id,
+ UnackedInvalidationsMap(),
+ initial_state,
+ MakeWeakHandle(invalidation_state_tracker),
+ "fake_client_info"));
+ }
+
+ Invalidator* GetInvalidator() {
+ return invalidator_.get();
+ }
+
+ void DestroyInvalidator() {
+ // Stopping the invalidation notifier stops its scheduler, which deletes
+ // any pending tasks without running them. Some tasks "run and delete"
+ // another task, so they must be run in order to avoid leaking the inner
+ // task. Stopping does not schedule any tasks, so it's both necessary and
+ // sufficient to drain the task queue before stopping the notifier.
+ message_loop_.RunUntilIdle();
+ invalidator_.reset();
+ }
+
+ void WaitForInvalidator() {
+ message_loop_.RunUntilIdle();
+ }
+
+ void TriggerOnInvalidatorStateChange(InvalidatorState state) {
+ invalidator_->OnInvalidatorStateChange(state);
+ }
+
+ void TriggerOnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ invalidator_->OnInvalidate(invalidation_map);
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ scoped_ptr<InvalidationNotifier> invalidator_;
+};
+
+INSTANTIATE_TYPED_TEST_CASE_P(
+ InvalidationNotifierTest, InvalidatorTest,
+ InvalidationNotifierTestDelegate);
+
+} // namespace
+
+} // namespace syncer
diff --git a/sync/notifier/invalidation_state_tracker.cc b/sync/notifier/invalidation_state_tracker.cc
deleted file mode 100644
index ab3ce17..0000000
--- a/sync/notifier/invalidation_state_tracker.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 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/notifier/invalidation_state_tracker.h"
-
-namespace syncer {
-
-InvalidationStateTracker::InvalidationStateTracker() {}
-
-InvalidationStateTracker::~InvalidationStateTracker() {}
-
-} // namespace syncer
diff --git a/sync/notifier/invalidation_state_tracker.h b/sync/notifier/invalidation_state_tracker.h
index 6f4c133..22e3e6f 100644
--- a/sync/notifier/invalidation_state_tracker.h
+++ b/sync/notifier/invalidation_state_tracker.h
@@ -32,8 +32,8 @@ namespace syncer {
class SYNC_EXPORT InvalidationStateTracker {
public:
- InvalidationStateTracker();
- virtual ~InvalidationStateTracker();
+ InvalidationStateTracker() {}
+ virtual ~InvalidationStateTracker() {}
// The per-client unique ID used to register the invalidation client with the
// server. This is used to squelch invalidation notifications that originate
diff --git a/sync/notifier/invalidator.h b/sync/notifier/invalidator.h
index 0ed26e2..3c50fe6 100644
--- a/sync/notifier/invalidator.h
+++ b/sync/notifier/invalidator.h
@@ -12,9 +12,9 @@
#include <string>
#include "sync/base/sync_export.h"
-#include "sync/internal_api/public/base/invalidator_state.h"
#include "sync/internal_api/public/base/model_type.h"
#include "sync/notifier/invalidation_util.h"
+#include "sync/notifier/invalidator_state.h"
namespace syncer {
class InvalidationHandler;
diff --git a/sync/notifier/invalidator_registrar.cc b/sync/notifier/invalidator_registrar.cc
new file mode 100644
index 0000000..27f206b
--- /dev/null
+++ b/sync/notifier/invalidator_registrar.cc
@@ -0,0 +1,149 @@
+// Copyright 2014 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/notifier/invalidator_registrar.h"
+
+#include <cstddef>
+#include <iterator>
+#include <utility>
+
+#include "base/logging.h"
+#include "sync/notifier/object_id_invalidation_map.h"
+
+namespace syncer {
+
+InvalidatorRegistrar::InvalidatorRegistrar()
+ : state_(DEFAULT_INVALIDATION_ERROR) {}
+
+InvalidatorRegistrar::~InvalidatorRegistrar() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(!handlers_.might_have_observers());
+ CHECK(handler_to_ids_map_.empty());
+}
+
+void InvalidatorRegistrar::RegisterHandler(InvalidationHandler* handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(handler);
+ CHECK(!handlers_.HasObserver(handler));
+ handlers_.AddObserver(handler);
+}
+
+void InvalidatorRegistrar::UpdateRegisteredIds(
+ InvalidationHandler* handler,
+ const ObjectIdSet& ids) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(handler);
+ CHECK(handlers_.HasObserver(handler));
+
+ for (HandlerIdsMap::const_iterator it = handler_to_ids_map_.begin();
+ it != handler_to_ids_map_.end(); ++it) {
+ if (it->first == handler) {
+ continue;
+ }
+
+ std::vector<invalidation::ObjectId> intersection;
+ std::set_intersection(
+ it->second.begin(), it->second.end(),
+ ids.begin(), ids.end(),
+ std::inserter(intersection, intersection.end()),
+ ObjectIdLessThan());
+ CHECK(intersection.empty())
+ << "Duplicate registration: trying to register "
+ << ObjectIdToString(*intersection.begin()) << " for "
+ << handler << " when it's already registered for "
+ << it->first;
+ }
+
+ if (ids.empty()) {
+ handler_to_ids_map_.erase(handler);
+ } else {
+ handler_to_ids_map_[handler] = ids;
+ }
+}
+
+void InvalidatorRegistrar::UnregisterHandler(InvalidationHandler* handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(handler);
+ CHECK(handlers_.HasObserver(handler));
+ handlers_.RemoveObserver(handler);
+ handler_to_ids_map_.erase(handler);
+}
+
+ObjectIdSet InvalidatorRegistrar::GetRegisteredIds(
+ InvalidationHandler* handler) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ HandlerIdsMap::const_iterator lookup = handler_to_ids_map_.find(handler);
+ if (lookup != handler_to_ids_map_.end()) {
+ return lookup->second;
+ } else {
+ return ObjectIdSet();
+ }
+}
+
+ObjectIdSet InvalidatorRegistrar::GetAllRegisteredIds() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ObjectIdSet registered_ids;
+ for (HandlerIdsMap::const_iterator it = handler_to_ids_map_.begin();
+ it != handler_to_ids_map_.end(); ++it) {
+ registered_ids.insert(it->second.begin(), it->second.end());
+ }
+ return registered_ids;
+}
+
+void InvalidatorRegistrar::DispatchInvalidationsToHandlers(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // If we have no handlers, there's nothing to do.
+ if (!handlers_.might_have_observers()) {
+ return;
+ }
+
+ for (HandlerIdsMap::iterator it = handler_to_ids_map_.begin();
+ it != handler_to_ids_map_.end(); ++it) {
+ ObjectIdInvalidationMap to_emit =
+ invalidation_map.GetSubsetWithObjectIds(it->second);
+ if (!to_emit.Empty()) {
+ it->first->OnIncomingInvalidation(to_emit);
+ }
+ }
+}
+
+void InvalidatorRegistrar::UpdateInvalidatorState(InvalidatorState state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "New invalidator state: " << InvalidatorStateToString(state_)
+ << " -> " << InvalidatorStateToString(state);
+ state_ = state;
+ FOR_EACH_OBSERVER(InvalidationHandler, handlers_,
+ OnInvalidatorStateChange(state));
+}
+
+InvalidatorState InvalidatorRegistrar::GetInvalidatorState() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return state_;
+}
+
+std::map<std::string, ObjectIdSet>
+InvalidatorRegistrar::GetSanitizedHandlersIdsMap() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::map<std::string, ObjectIdSet> clean_handlers_to_ids;
+ for (HandlerIdsMap::const_iterator it = handler_to_ids_map_.begin();
+ it != handler_to_ids_map_.end();
+ ++it) {
+ clean_handlers_to_ids[it->first->GetOwnerName()] = ObjectIdSet(it->second);
+ }
+ return clean_handlers_to_ids;
+}
+
+bool InvalidatorRegistrar::IsHandlerRegisteredForTest(
+ InvalidationHandler* handler) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return handlers_.HasObserver(handler);
+}
+
+void InvalidatorRegistrar::DetachFromThreadForTest() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ thread_checker_.DetachFromThread();
+}
+
+} // namespace syncer
diff --git a/sync/notifier/invalidator_registrar.h b/sync/notifier/invalidator_registrar.h
new file mode 100644
index 0000000..2b636d7
--- /dev/null
+++ b/sync/notifier/invalidator_registrar.h
@@ -0,0 +1,97 @@
+// Copyright 2014 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.
+
+#ifndef SYNC_NOTIFIER_INVALIDATOR_REGISTRAR_H_
+#define SYNC_NOTIFIER_INVALIDATOR_REGISTRAR_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "sync/base/sync_export.h"
+#include "sync/notifier/invalidation_handler.h"
+#include "sync/notifier/invalidation_util.h"
+
+namespace invalidation {
+class ObjectId;
+} // namespace invalidation
+
+namespace syncer {
+
+class ObjectIdInvalidationMap;
+
+// A helper class for implementations of the Invalidator interface. It helps
+// keep track of registered handlers and which object ID registrations are
+// associated with which handlers, so implementors can just reuse the logic
+// here to dispatch invalidations and other interesting notifications.
+class SYNC_EXPORT InvalidatorRegistrar {
+ public:
+ InvalidatorRegistrar();
+
+ // It is an error to have registered handlers on destruction.
+ ~InvalidatorRegistrar();
+
+ // Starts sending notifications to |handler|. |handler| must not be NULL,
+ // and it must already be registered.
+ void RegisterHandler(InvalidationHandler* handler);
+
+ // Updates the set of ObjectIds associated with |handler|. |handler| must
+ // not be NULL, and must already be registered. An ID must be registered for
+ // at most one handler.
+ void UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids);
+
+ // Stops sending notifications to |handler|. |handler| must not be NULL, and
+ // it must already be registered. Note that this doesn't unregister the IDs
+ // associated with |handler|.
+ void UnregisterHandler(InvalidationHandler* handler);
+
+ ObjectIdSet GetRegisteredIds(InvalidationHandler* handler) const;
+
+ // Returns the set of all IDs that are registered to some handler (even
+ // handlers that have been unregistered).
+ ObjectIdSet GetAllRegisteredIds() const;
+
+ // Sorts incoming invalidations into a bucket for each handler and then
+ // dispatches the batched invalidations to the corresponding handler.
+ // Invalidations for IDs with no corresponding handler are dropped, as are
+ // invalidations for handlers that are not added.
+ void DispatchInvalidationsToHandlers(
+ const ObjectIdInvalidationMap& invalidation_map);
+
+ // Updates the invalidator state to the given one and then notifies
+ // all handlers. Note that the order is important; handlers that
+ // call GetInvalidatorState() when notified will see the new state.
+ void UpdateInvalidatorState(InvalidatorState state);
+
+ // Returns the current invalidator state. When called from within
+ // InvalidationHandler::OnInvalidatorStateChange(), this returns the
+ // updated state.
+ InvalidatorState GetInvalidatorState() const;
+
+ // Gets a new map for the name of invalidator handlers and their
+ // objects id. This is used by the InvalidatorLogger to be able
+ // to display every registered handlers and its objectsIds.
+ std::map<std::string, ObjectIdSet> GetSanitizedHandlersIdsMap();
+
+ bool IsHandlerRegisteredForTest(InvalidationHandler* handler) const;
+
+ // Needed for death tests.
+ void DetachFromThreadForTest();
+
+ private:
+ typedef std::map<InvalidationHandler*, ObjectIdSet> HandlerIdsMap;
+
+ base::ThreadChecker thread_checker_;
+ ObserverList<InvalidationHandler> handlers_;
+ HandlerIdsMap handler_to_ids_map_;
+ InvalidatorState state_;
+
+ DISALLOW_COPY_AND_ASSIGN(InvalidatorRegistrar);
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_INVALIDATOR_REGISTRAR_H_
diff --git a/sync/notifier/invalidator_registrar_unittest.cc b/sync/notifier/invalidator_registrar_unittest.cc
new file mode 100644
index 0000000..24e1908
--- /dev/null
+++ b/sync/notifier/invalidator_registrar_unittest.cc
@@ -0,0 +1,163 @@
+// Copyright 2014 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 "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "google/cacheinvalidation/types.pb.h"
+#include "sync/notifier/fake_invalidation_handler.h"
+#include "sync/notifier/invalidator_registrar.h"
+#include "sync/notifier/invalidator_test_template.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+// We test InvalidatorRegistrar by wrapping it in an Invalidator and
+// running the usual Invalidator tests.
+
+// Thin Invalidator wrapper around InvalidatorRegistrar.
+class RegistrarInvalidator : public Invalidator {
+ public:
+ RegistrarInvalidator() {}
+ virtual ~RegistrarInvalidator() {}
+
+ InvalidatorRegistrar* GetRegistrar() {
+ return &registrar_;
+ }
+
+ // Invalidator implementation.
+ virtual void RegisterHandler(InvalidationHandler* handler) OVERRIDE {
+ registrar_.RegisterHandler(handler);
+ }
+
+ virtual void UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids) OVERRIDE {
+ registrar_.UpdateRegisteredIds(handler, ids);
+ }
+
+ virtual void UnregisterHandler(InvalidationHandler* handler) OVERRIDE {
+ registrar_.UnregisterHandler(handler);
+ }
+
+ virtual InvalidatorState GetInvalidatorState() const OVERRIDE {
+ return registrar_.GetInvalidatorState();
+ }
+
+ virtual void UpdateCredentials(
+ const std::string& email, const std::string& token) OVERRIDE {
+ // Do nothing.
+ }
+
+ virtual void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> call) const OVERRIDE {
+ // Do nothing.
+ }
+
+ private:
+ InvalidatorRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(RegistrarInvalidator);
+};
+
+class RegistrarInvalidatorTestDelegate {
+ public:
+ RegistrarInvalidatorTestDelegate() {}
+
+ ~RegistrarInvalidatorTestDelegate() {
+ DestroyInvalidator();
+ }
+
+ void CreateInvalidator(
+ const std::string& invalidator_client_id,
+ const std::string& initial_state,
+ const base::WeakPtr<InvalidationStateTracker>&
+ invalidation_state_tracker) {
+ DCHECK(!invalidator_.get());
+ invalidator_.reset(new RegistrarInvalidator());
+ }
+
+ RegistrarInvalidator* GetInvalidator() {
+ return invalidator_.get();
+ }
+
+ void DestroyInvalidator() {
+ invalidator_.reset();
+ }
+
+ void WaitForInvalidator() {
+ // Do nothing.
+ }
+
+ void TriggerOnInvalidatorStateChange(InvalidatorState state) {
+ invalidator_->GetRegistrar()->UpdateInvalidatorState(state);
+ }
+
+ void TriggerOnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ invalidator_->GetRegistrar()->DispatchInvalidationsToHandlers(
+ invalidation_map);
+ }
+
+ private:
+ scoped_ptr<RegistrarInvalidator> invalidator_;
+};
+
+INSTANTIATE_TYPED_TEST_CASE_P(
+ RegistrarInvalidatorTest, InvalidatorTest,
+ RegistrarInvalidatorTestDelegate);
+
+class InvalidatorRegistrarTest : public testing::Test {};
+
+// Technically the tests below can be part of InvalidatorTest, but we
+// want to keep the number of death tests down.
+
+// When we expect a death via CHECK(), we can't match against the
+// CHECK() message since they are removed in official builds.
+
+#if GTEST_HAS_DEATH_TEST
+// Having registered handlers on destruction should cause a CHECK.
+TEST_F(InvalidatorRegistrarTest, RegisteredHandlerOnDestruction) {
+ scoped_ptr<InvalidatorRegistrar> registrar(new InvalidatorRegistrar());
+ FakeInvalidationHandler handler;
+
+ registrar->RegisterHandler(&handler);
+
+ EXPECT_DEATH({ registrar.reset(); }, "");
+
+ ASSERT_TRUE(registrar.get());
+ registrar->UnregisterHandler(&handler);
+}
+
+// Multiple registrations by different handlers on the same object ID should
+// cause a CHECK.
+TEST_F(InvalidatorRegistrarTest, MultipleRegistration) {
+ const invalidation::ObjectId id1(ipc::invalidation::ObjectSource::TEST, "a");
+ const invalidation::ObjectId id2(ipc::invalidation::ObjectSource::TEST, "a");
+
+ InvalidatorRegistrar registrar;
+
+ FakeInvalidationHandler handler1;
+ registrar.RegisterHandler(&handler1);
+
+ FakeInvalidationHandler handler2;
+ registrar.RegisterHandler(&handler2);
+
+ ObjectIdSet ids;
+ ids.insert(id1);
+ ids.insert(id2);
+ registrar.UpdateRegisteredIds(&handler1, ids);
+
+ registrar.DetachFromThreadForTest();
+ EXPECT_DEATH({ registrar.UpdateRegisteredIds(&handler2, ids); }, "");
+
+ registrar.UnregisterHandler(&handler2);
+ registrar.UnregisterHandler(&handler1);
+}
+#endif // GTEST_HAS_DEATH_TEST
+
+} // namespace
+
+} // namespace syncer
diff --git a/sync/notifier/invalidator_state.cc b/sync/notifier/invalidator_state.cc
new file mode 100644
index 0000000..b57f4d0
--- /dev/null
+++ b/sync/notifier/invalidator_state.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 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/notifier/invalidator_state.h"
+
+#include "base/logging.h"
+
+namespace syncer {
+
+const char* InvalidatorStateToString(InvalidatorState state) {
+ switch (state) {
+ case TRANSIENT_INVALIDATION_ERROR:
+ return "TRANSIENT_INVALIDATION_ERROR";
+ case INVALIDATION_CREDENTIALS_REJECTED:
+ return "INVALIDATION_CREDENTIALS_REJECTED";
+ case INVALIDATIONS_ENABLED:
+ return "INVALIDATIONS_ENABLED";
+ default:
+ NOTREACHED();
+ return "UNKNOWN_INVALIDATOR_STATE";
+ }
+}
+
+InvalidatorState FromNotifierReason(
+ notifier::NotificationsDisabledReason reason) {
+ switch (reason) {
+ case notifier::NO_NOTIFICATION_ERROR:
+ return INVALIDATIONS_ENABLED;
+ case notifier::TRANSIENT_NOTIFICATION_ERROR:
+ return TRANSIENT_INVALIDATION_ERROR;
+ case notifier::NOTIFICATION_CREDENTIALS_REJECTED:
+ return INVALIDATION_CREDENTIALS_REJECTED;
+ default:
+ NOTREACHED();
+ return DEFAULT_INVALIDATION_ERROR;
+ }
+}
+
+notifier::NotificationsDisabledReason ToNotifierReasonForTest(
+ InvalidatorState state) {
+ switch (state) {
+ case TRANSIENT_INVALIDATION_ERROR:
+ return notifier::TRANSIENT_NOTIFICATION_ERROR;
+ case INVALIDATION_CREDENTIALS_REJECTED:
+ return notifier::NOTIFICATION_CREDENTIALS_REJECTED;
+ case INVALIDATIONS_ENABLED:
+ // Fall through.
+ default:
+ NOTREACHED();
+ return notifier::TRANSIENT_NOTIFICATION_ERROR;
+ }
+}
+
+} // namespace syncer
diff --git a/sync/internal_api/public/base/invalidator_state.h b/sync/notifier/invalidator_state.h
index 3a1c7ee..a269661 100644
--- a/sync/internal_api/public/base/invalidator_state.h
+++ b/sync/notifier/invalidator_state.h
@@ -2,9 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef SYNC_INTERNAL_API_PUBLIC_BASE_INVALIDATOR_STATE_H_
-#define SYNC_INTERNAL_API_PUBLIC_BASE_INVALIDATOR_STATE_H_
+#ifndef SYNC_NOTIFIER_INVALIDATOR_STATE_H_
+#define SYNC_NOTIFIER_INVALIDATOR_STATE_H_
+#include "jingle/notifier/listener/push_client_observer.h"
#include "sync/base/sync_export.h"
namespace syncer {
@@ -25,6 +26,13 @@ enum InvalidatorState {
SYNC_EXPORT const char* InvalidatorStateToString(InvalidatorState state);
+InvalidatorState FromNotifierReason(
+ notifier::NotificationsDisabledReason reason);
+
+// Should not be called when |state| == INVALIDATIONS_ENABLED.
+SYNC_EXPORT_PRIVATE notifier::NotificationsDisabledReason
+ ToNotifierReasonForTest(InvalidatorState state);
+
} // namespace syncer
-#endif // SYNC_INTERNAL_API_PUBLIC_BASE_INVALIDATOR_STATE_H_
+#endif // SYNC_NOTIFIER_INVALIDATOR_STATE_H_
diff --git a/sync/notifier/invalidator_test_template.cc b/sync/notifier/invalidator_test_template.cc
new file mode 100644
index 0000000..0a93cf3
--- /dev/null
+++ b/sync/notifier/invalidator_test_template.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 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/notifier/invalidator_test_template.h"
+
+namespace syncer {
+namespace internal {
+
+BoundFakeInvalidationHandler::BoundFakeInvalidationHandler(
+ const Invalidator& invalidator)
+ : invalidator_(invalidator),
+ last_retrieved_state_(DEFAULT_INVALIDATION_ERROR) {}
+
+BoundFakeInvalidationHandler::~BoundFakeInvalidationHandler() {}
+
+InvalidatorState BoundFakeInvalidationHandler::GetLastRetrievedState() const {
+ return last_retrieved_state_;
+}
+
+void BoundFakeInvalidationHandler::OnInvalidatorStateChange(
+ InvalidatorState state) {
+ FakeInvalidationHandler::OnInvalidatorStateChange(state);
+ last_retrieved_state_ = invalidator_.GetInvalidatorState();
+}
+
+} // namespace internal
+} // namespace syncer
diff --git a/sync/notifier/invalidator_test_template.h b/sync/notifier/invalidator_test_template.h
new file mode 100644
index 0000000..c153088
--- /dev/null
+++ b/sync/notifier/invalidator_test_template.h
@@ -0,0 +1,377 @@
+// Copyright 2014 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.
+
+// This class defines tests that implementations of Invalidator should pass in
+// order to be conformant. Here's how you use it to test your implementation.
+//
+// Say your class is called MyInvalidator. Then you need to define a class
+// called MyInvalidatorTestDelegate in my_sync_notifier_unittest.cc like this:
+//
+// class MyInvalidatorTestDelegate {
+// public:
+// MyInvalidatorTestDelegate() ...
+//
+// ~MyInvalidatorTestDelegate() {
+// // DestroyInvalidator() may not be explicitly called by tests.
+// DestroyInvalidator();
+// }
+//
+// // Create the Invalidator implementation with the given parameters.
+// void CreateInvalidator(
+// const std::string& initial_state,
+// const base::WeakPtr<InvalidationStateTracker>&
+// invalidation_state_tracker) {
+// ...
+// }
+//
+// // Should return the Invalidator implementation. Only called after
+// // CreateInvalidator and before DestroyInvalidator.
+// MyInvalidator* GetInvalidator() {
+// ...
+// }
+//
+// // Destroy the Invalidator implementation.
+// void DestroyInvalidator() {
+// ...
+// }
+//
+// // Called after a call to SetUniqueId(), or UpdateCredentials() on the
+// // Invalidator implementation. Should block until the effects of the
+// // call are visible on the current thread.
+// void WaitForInvalidator() {
+// ...
+// }
+//
+// // The Trigger* functions below should block until the effects of
+// // the call are visible on the current thread.
+//
+// // Should cause OnInvalidatorStateChange() to be called on all
+// // observers of the Invalidator implementation with the given
+// // parameters.
+// void TriggerOnInvalidatorStateChange(InvalidatorState state) {
+// ...
+// }
+//
+// // Should cause OnIncomingInvalidation() to be called on all
+// // observers of the Invalidator implementation with the given
+// // parameters.
+// void TriggerOnIncomingInvalidation(
+// const ObjectIdInvalidationMap& invalidation_map) {
+// ...
+// }
+// };
+//
+// The InvalidatorTest test harness will have a member variable of
+// this delegate type and will call its functions in the various
+// tests.
+//
+// Then you simply #include this file as well as gtest.h and add the
+// following statement to my_sync_notifier_unittest.cc:
+//
+// INSTANTIATE_TYPED_TEST_CASE_P(
+// MyInvalidator, InvalidatorTest, MyInvalidatorTestDelegate);
+//
+// Easy!
+
+#ifndef SYNC_NOTIFIER_INVALIDATOR_TEST_TEMPLATE_H_
+#define SYNC_NOTIFIER_INVALIDATOR_TEST_TEMPLATE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "google/cacheinvalidation/include/types.h"
+#include "google/cacheinvalidation/types.pb.h"
+#include "sync/internal_api/public/base/object_id_invalidation_map_test_util.h"
+#include "sync/notifier/fake_invalidation_handler.h"
+#include "sync/notifier/fake_invalidation_state_tracker.h"
+#include "sync/notifier/invalidator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+template <typename InvalidatorTestDelegate>
+class InvalidatorTest : public testing::Test {
+ protected:
+ InvalidatorTest()
+ : id1(ipc::invalidation::ObjectSource::TEST, "a"),
+ id2(ipc::invalidation::ObjectSource::TEST, "b"),
+ id3(ipc::invalidation::ObjectSource::TEST, "c"),
+ id4(ipc::invalidation::ObjectSource::TEST, "d") {
+ }
+
+ Invalidator* CreateAndInitializeInvalidator() {
+ this->delegate_.CreateInvalidator("fake_invalidator_client_id",
+ "fake_initial_state",
+ this->fake_tracker_.AsWeakPtr());
+ Invalidator* const invalidator = this->delegate_.GetInvalidator();
+
+ this->delegate_.WaitForInvalidator();
+ invalidator->UpdateCredentials("foo@bar.com", "fake_token");
+ this->delegate_.WaitForInvalidator();
+
+ return invalidator;
+ }
+
+ FakeInvalidationStateTracker fake_tracker_;
+ InvalidatorTestDelegate delegate_;
+
+ const invalidation::ObjectId id1;
+ const invalidation::ObjectId id2;
+ const invalidation::ObjectId id3;
+ const invalidation::ObjectId id4;
+};
+
+TYPED_TEST_CASE_P(InvalidatorTest);
+
+// Initialize the invalidator, register a handler, register some IDs for that
+// handler, and then unregister the handler, dispatching invalidations in
+// between. The handler should only see invalidations when its registered and
+// its IDs are registered.
+TYPED_TEST_P(InvalidatorTest, Basic) {
+ Invalidator* const invalidator = this->CreateAndInitializeInvalidator();
+
+ FakeInvalidationHandler handler;
+
+ invalidator->RegisterHandler(&handler);
+
+ ObjectIdInvalidationMap invalidation_map;
+ invalidation_map.Insert(Invalidation::Init(this->id1, 1, "1"));
+ invalidation_map.Insert(Invalidation::Init(this->id2, 2, "2"));
+ invalidation_map.Insert(Invalidation::Init(this->id3, 3, "3"));
+
+ // Should be ignored since no IDs are registered to |handler|.
+ this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
+ EXPECT_EQ(0, handler.GetInvalidationCount());
+
+ ObjectIdSet ids;
+ ids.insert(this->id1);
+ ids.insert(this->id2);
+ invalidator->UpdateRegisteredIds(&handler, ids);
+
+ this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
+ EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetInvalidatorState());
+
+ ObjectIdInvalidationMap expected_invalidations;
+ expected_invalidations.Insert(Invalidation::Init(this->id1, 1, "1"));
+ expected_invalidations.Insert(Invalidation::Init(this->id2, 2, "2"));
+
+ this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
+ EXPECT_EQ(1, handler.GetInvalidationCount());
+ EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap()));
+
+ ids.erase(this->id1);
+ ids.insert(this->id3);
+ invalidator->UpdateRegisteredIds(&handler, ids);
+
+ expected_invalidations = ObjectIdInvalidationMap();
+ expected_invalidations.Insert(Invalidation::Init(this->id2, 2, "2"));
+ expected_invalidations.Insert(Invalidation::Init(this->id3, 3, "3"));
+
+ // Removed object IDs should not be notified, newly-added ones should.
+ this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
+ EXPECT_EQ(2, handler.GetInvalidationCount());
+ EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap()));
+
+ this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR,
+ handler.GetInvalidatorState());
+
+ this->delegate_.TriggerOnInvalidatorStateChange(
+ INVALIDATION_CREDENTIALS_REJECTED);
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED,
+ handler.GetInvalidatorState());
+
+ invalidator->UnregisterHandler(&handler);
+
+ // Should be ignored since |handler| isn't registered anymore.
+ this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
+ EXPECT_EQ(2, handler.GetInvalidationCount());
+}
+
+// Register handlers and some IDs for those handlers, register a handler with
+// no IDs, and register a handler with some IDs but unregister it. Then,
+// dispatch some invalidations and invalidations. Handlers that are registered
+// should get invalidations, and the ones that have registered IDs should
+// receive invalidations for those IDs.
+TYPED_TEST_P(InvalidatorTest, MultipleHandlers) {
+ Invalidator* const invalidator = this->CreateAndInitializeInvalidator();
+
+ FakeInvalidationHandler handler1;
+ FakeInvalidationHandler handler2;
+ FakeInvalidationHandler handler3;
+ FakeInvalidationHandler handler4;
+
+ invalidator->RegisterHandler(&handler1);
+ invalidator->RegisterHandler(&handler2);
+ invalidator->RegisterHandler(&handler3);
+ invalidator->RegisterHandler(&handler4);
+
+ {
+ ObjectIdSet ids;
+ ids.insert(this->id1);
+ ids.insert(this->id2);
+ invalidator->UpdateRegisteredIds(&handler1, ids);
+ }
+
+ {
+ ObjectIdSet ids;
+ ids.insert(this->id3);
+ invalidator->UpdateRegisteredIds(&handler2, ids);
+ }
+
+ // Don't register any IDs for handler3.
+
+ {
+ ObjectIdSet ids;
+ ids.insert(this->id4);
+ invalidator->UpdateRegisteredIds(&handler4, ids);
+ }
+
+ invalidator->UnregisterHandler(&handler4);
+
+ this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
+ EXPECT_EQ(INVALIDATIONS_ENABLED, handler1.GetInvalidatorState());
+ EXPECT_EQ(INVALIDATIONS_ENABLED, handler2.GetInvalidatorState());
+ EXPECT_EQ(INVALIDATIONS_ENABLED, handler3.GetInvalidatorState());
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler4.GetInvalidatorState());
+
+ {
+ ObjectIdInvalidationMap invalidation_map;
+ invalidation_map.Insert(Invalidation::Init(this->id1, 1, "1"));
+ invalidation_map.Insert(Invalidation::Init(this->id2, 2, "2"));
+ invalidation_map.Insert(Invalidation::Init(this->id3, 3, "3"));
+ invalidation_map.Insert(Invalidation::Init(this->id4, 4, "4"));
+
+ this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
+
+ ObjectIdInvalidationMap expected_invalidations;
+ expected_invalidations.Insert(Invalidation::Init(this->id1, 1, "1"));
+ expected_invalidations.Insert(Invalidation::Init(this->id2, 2, "2"));
+
+ EXPECT_EQ(1, handler1.GetInvalidationCount());
+ EXPECT_THAT(expected_invalidations, Eq(handler1.GetLastInvalidationMap()));
+
+ expected_invalidations = ObjectIdInvalidationMap();
+ expected_invalidations.Insert(Invalidation::Init(this->id3, 3, "3"));
+
+ EXPECT_EQ(1, handler2.GetInvalidationCount());
+ EXPECT_THAT(expected_invalidations, Eq(handler2.GetLastInvalidationMap()));
+
+ EXPECT_EQ(0, handler3.GetInvalidationCount());
+ EXPECT_EQ(0, handler4.GetInvalidationCount());
+ }
+
+ this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler1.GetInvalidatorState());
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler2.GetInvalidatorState());
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler3.GetInvalidatorState());
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler4.GetInvalidatorState());
+
+ invalidator->UnregisterHandler(&handler3);
+ invalidator->UnregisterHandler(&handler2);
+ invalidator->UnregisterHandler(&handler1);
+}
+
+// Make sure that passing an empty set to UpdateRegisteredIds clears the
+// corresponding entries for the handler.
+TYPED_TEST_P(InvalidatorTest, EmptySetUnregisters) {
+ Invalidator* const invalidator = this->CreateAndInitializeInvalidator();
+
+ FakeInvalidationHandler handler1;
+
+ // Control observer.
+ FakeInvalidationHandler handler2;
+
+ invalidator->RegisterHandler(&handler1);
+ invalidator->RegisterHandler(&handler2);
+
+ {
+ ObjectIdSet ids;
+ ids.insert(this->id1);
+ ids.insert(this->id2);
+ invalidator->UpdateRegisteredIds(&handler1, ids);
+ }
+
+ {
+ ObjectIdSet ids;
+ ids.insert(this->id3);
+ invalidator->UpdateRegisteredIds(&handler2, ids);
+ }
+
+ // Unregister the IDs for the first observer. It should not receive any
+ // further invalidations.
+ invalidator->UpdateRegisteredIds(&handler1, ObjectIdSet());
+
+ this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
+ EXPECT_EQ(INVALIDATIONS_ENABLED, handler1.GetInvalidatorState());
+ EXPECT_EQ(INVALIDATIONS_ENABLED, handler2.GetInvalidatorState());
+
+ {
+ ObjectIdInvalidationMap invalidation_map;
+ invalidation_map.Insert(Invalidation::Init(this->id1, 1, "1"));
+ invalidation_map.Insert(Invalidation::Init(this->id2, 2, "2"));
+ invalidation_map.Insert(Invalidation::Init(this->id3, 3, "3"));
+ this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
+ EXPECT_EQ(0, handler1.GetInvalidationCount());
+ EXPECT_EQ(1, handler2.GetInvalidationCount());
+ }
+
+ this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler1.GetInvalidatorState());
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler2.GetInvalidatorState());
+
+ invalidator->UnregisterHandler(&handler2);
+ invalidator->UnregisterHandler(&handler1);
+}
+
+namespace internal {
+
+// A FakeInvalidationHandler that is "bound" to a specific
+// Invalidator. This is for cross-referencing state information with
+// the bound Invalidator.
+class BoundFakeInvalidationHandler : public FakeInvalidationHandler {
+ public:
+ explicit BoundFakeInvalidationHandler(const Invalidator& invalidator);
+ virtual ~BoundFakeInvalidationHandler();
+
+ // Returns the last return value of GetInvalidatorState() on the
+ // bound invalidator from the last time the invalidator state
+ // changed.
+ InvalidatorState GetLastRetrievedState() const;
+
+ // InvalidationHandler implementation.
+ virtual void OnInvalidatorStateChange(InvalidatorState state) OVERRIDE;
+
+ private:
+ const Invalidator& invalidator_;
+ InvalidatorState last_retrieved_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(BoundFakeInvalidationHandler);
+};
+
+} // namespace internal
+
+TYPED_TEST_P(InvalidatorTest, GetInvalidatorStateAlwaysCurrent) {
+ Invalidator* const invalidator = this->CreateAndInitializeInvalidator();
+
+ internal::BoundFakeInvalidationHandler handler(*invalidator);
+ invalidator->RegisterHandler(&handler);
+
+ this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
+ EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetInvalidatorState());
+ EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetLastRetrievedState());
+
+ this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler.GetInvalidatorState());
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler.GetLastRetrievedState());
+
+ invalidator->UnregisterHandler(&handler);
+}
+
+REGISTER_TYPED_TEST_CASE_P(InvalidatorTest,
+ Basic, MultipleHandlers, EmptySetUnregisters,
+ GetInvalidatorStateAlwaysCurrent);
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_INVALIDATOR_TEST_TEMPLATE_H_
diff --git a/sync/notifier/non_blocking_invalidator.cc b/sync/notifier/non_blocking_invalidator.cc
new file mode 100644
index 0000000..b155c35
--- /dev/null
+++ b/sync/notifier/non_blocking_invalidator.cc
@@ -0,0 +1,367 @@
+// Copyright 2014 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/notifier/non_blocking_invalidator.h"
+
+#include <cstddef>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "jingle/notifier/listener/push_client.h"
+#include "sync/internal_api/public/util/weak_handle.h"
+#include "sync/notifier/gcm_network_channel_delegate.h"
+#include "sync/notifier/invalidation_handler.h"
+#include "sync/notifier/invalidation_notifier.h"
+#include "sync/notifier/object_id_invalidation_map.h"
+#include "sync/notifier/sync_system_resources.h"
+
+namespace syncer {
+
+struct NonBlockingInvalidator::InitializeOptions {
+ InitializeOptions(
+ NetworkChannelCreator network_channel_creator,
+ const std::string& invalidator_client_id,
+ const UnackedInvalidationsMap& saved_invalidations,
+ const std::string& invalidation_bootstrap_data,
+ const WeakHandle<InvalidationStateTracker>&
+ invalidation_state_tracker,
+ const std::string& client_info,
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter)
+ : network_channel_creator(network_channel_creator),
+ invalidator_client_id(invalidator_client_id),
+ saved_invalidations(saved_invalidations),
+ invalidation_bootstrap_data(invalidation_bootstrap_data),
+ invalidation_state_tracker(invalidation_state_tracker),
+ client_info(client_info),
+ request_context_getter(request_context_getter) {
+ }
+
+ NetworkChannelCreator network_channel_creator;
+ std::string invalidator_client_id;
+ UnackedInvalidationsMap saved_invalidations;
+ std::string invalidation_bootstrap_data;
+ WeakHandle<InvalidationStateTracker> invalidation_state_tracker;
+ std::string client_info;
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter;
+};
+
+namespace {
+// This class provides a wrapper for a logging class in order to receive
+// callbacks across threads, without having to worry about owner threads.
+class CallbackProxy {
+ public:
+ explicit CallbackProxy(
+ base::Callback<void(const base::DictionaryValue&)> callback);
+ ~CallbackProxy();
+
+ void Run(const base::DictionaryValue& value);
+
+ private:
+ static void DoRun(base::Callback<void(const base::DictionaryValue&)> callback,
+ scoped_ptr<base::DictionaryValue> value);
+
+ base::Callback<void(const base::DictionaryValue&)> callback_;
+ scoped_refptr<base::SingleThreadTaskRunner> running_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackProxy);
+};
+
+CallbackProxy::CallbackProxy(
+ base::Callback<void(const base::DictionaryValue&)> callback)
+ : callback_(callback),
+ running_thread_(base::ThreadTaskRunnerHandle::Get()) {}
+
+CallbackProxy::~CallbackProxy() {}
+
+void CallbackProxy::DoRun(
+ base::Callback<void(const base::DictionaryValue&)> callback,
+ scoped_ptr<base::DictionaryValue> value) {
+ callback.Run(*value);
+}
+
+void CallbackProxy::Run(const base::DictionaryValue& value) {
+ scoped_ptr<base::DictionaryValue> copied(value.DeepCopy());
+ running_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&CallbackProxy::DoRun, callback_, base::Passed(&copied)));
+}
+}
+
+class NonBlockingInvalidator::Core
+ : public base::RefCountedThreadSafe<NonBlockingInvalidator::Core>,
+ // InvalidationHandler to observe the InvalidationNotifier we create.
+ public InvalidationHandler {
+ public:
+ // Called on parent thread. |delegate_observer| should be initialized.
+ explicit Core(
+ const WeakHandle<NonBlockingInvalidator>& delegate_observer);
+
+ // Helpers called on I/O thread.
+ void Initialize(
+ const NonBlockingInvalidator::InitializeOptions& initialize_options);
+ void Teardown();
+ void UpdateRegisteredIds(const ObjectIdSet& ids);
+ void UpdateCredentials(const std::string& email, const std::string& token);
+ void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const;
+
+ // InvalidationHandler implementation (all called on I/O thread by
+ // InvalidationNotifier).
+ virtual void OnInvalidatorStateChange(InvalidatorState reason) OVERRIDE;
+ virtual void OnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) OVERRIDE;
+ virtual std::string GetOwnerName() const OVERRIDE;
+
+ private:
+ friend class
+ base::RefCountedThreadSafe<NonBlockingInvalidator::Core>;
+ // Called on parent or I/O thread.
+ virtual ~Core();
+
+ // The variables below should be used only on the I/O thread.
+ const WeakHandle<NonBlockingInvalidator> delegate_observer_;
+ scoped_ptr<InvalidationNotifier> invalidation_notifier_;
+ scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+NonBlockingInvalidator::Core::Core(
+ const WeakHandle<NonBlockingInvalidator>& delegate_observer)
+ : delegate_observer_(delegate_observer) {
+ DCHECK(delegate_observer_.IsInitialized());
+}
+
+NonBlockingInvalidator::Core::~Core() {
+}
+
+void NonBlockingInvalidator::Core::Initialize(
+ const NonBlockingInvalidator::InitializeOptions& initialize_options) {
+ DCHECK(initialize_options.request_context_getter.get());
+ network_task_runner_ =
+ initialize_options.request_context_getter->GetNetworkTaskRunner();
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ scoped_ptr<SyncNetworkChannel> network_channel =
+ initialize_options.network_channel_creator.Run();
+ invalidation_notifier_.reset(
+ new InvalidationNotifier(
+ network_channel.Pass(),
+ initialize_options.invalidator_client_id,
+ initialize_options.saved_invalidations,
+ initialize_options.invalidation_bootstrap_data,
+ initialize_options.invalidation_state_tracker,
+ initialize_options.client_info));
+ invalidation_notifier_->RegisterHandler(this);
+}
+
+void NonBlockingInvalidator::Core::Teardown() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ invalidation_notifier_->UnregisterHandler(this);
+ invalidation_notifier_.reset();
+ network_task_runner_ = NULL;
+}
+
+void NonBlockingInvalidator::Core::UpdateRegisteredIds(const ObjectIdSet& ids) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ invalidation_notifier_->UpdateRegisteredIds(this, ids);
+}
+
+void NonBlockingInvalidator::Core::UpdateCredentials(const std::string& email,
+ const std::string& token) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ invalidation_notifier_->UpdateCredentials(email, token);
+}
+
+void NonBlockingInvalidator::Core::RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ invalidation_notifier_->RequestDetailedStatus(callback);
+}
+
+void NonBlockingInvalidator::Core::OnInvalidatorStateChange(
+ InvalidatorState reason) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ delegate_observer_.Call(FROM_HERE,
+ &NonBlockingInvalidator::OnInvalidatorStateChange,
+ reason);
+}
+
+void NonBlockingInvalidator::Core::OnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ delegate_observer_.Call(FROM_HERE,
+ &NonBlockingInvalidator::OnIncomingInvalidation,
+ invalidation_map);
+}
+
+std::string NonBlockingInvalidator::Core::GetOwnerName() const {
+ return "Sync";
+}
+
+NonBlockingInvalidator::NonBlockingInvalidator(
+ NetworkChannelCreator network_channel_creator,
+ const std::string& invalidator_client_id,
+ const UnackedInvalidationsMap& saved_invalidations,
+ const std::string& invalidation_bootstrap_data,
+ InvalidationStateTracker* invalidation_state_tracker,
+ const std::string& client_info,
+ const scoped_refptr<net::URLRequestContextGetter>& request_context_getter)
+ : invalidation_state_tracker_(invalidation_state_tracker),
+ parent_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ network_task_runner_(request_context_getter->GetNetworkTaskRunner()),
+ weak_ptr_factory_(this) {
+ core_ = new Core(MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()));
+
+ InitializeOptions initialize_options(
+ network_channel_creator,
+ invalidator_client_id,
+ saved_invalidations,
+ invalidation_bootstrap_data,
+ MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()),
+ client_info,
+ request_context_getter);
+
+ if (!network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &NonBlockingInvalidator::Core::Initialize,
+ core_.get(),
+ initialize_options))) {
+ NOTREACHED();
+ }
+}
+
+NonBlockingInvalidator::~NonBlockingInvalidator() {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ if (!network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&NonBlockingInvalidator::Core::Teardown,
+ core_.get()))) {
+ DVLOG(1) << "Network thread stopped before invalidator is destroyed.";
+ }
+}
+
+void NonBlockingInvalidator::RegisterHandler(InvalidationHandler* handler) {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ registrar_.RegisterHandler(handler);
+}
+
+void NonBlockingInvalidator::UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids) {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ registrar_.UpdateRegisteredIds(handler, ids);
+ if (!network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &NonBlockingInvalidator::Core::UpdateRegisteredIds,
+ core_.get(),
+ registrar_.GetAllRegisteredIds()))) {
+ NOTREACHED();
+ }
+}
+
+void NonBlockingInvalidator::UnregisterHandler(InvalidationHandler* handler) {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ registrar_.UnregisterHandler(handler);
+}
+
+InvalidatorState NonBlockingInvalidator::GetInvalidatorState() const {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ return registrar_.GetInvalidatorState();
+}
+
+void NonBlockingInvalidator::UpdateCredentials(const std::string& email,
+ const std::string& token) {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ if (!network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&NonBlockingInvalidator::Core::UpdateCredentials,
+ core_.get(), email, token))) {
+ NOTREACHED();
+ }
+}
+
+void NonBlockingInvalidator::RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ base::Callback<void(const base::DictionaryValue&)> proxy_callback =
+ base::Bind(&CallbackProxy::Run, base::Owned(new CallbackProxy(callback)));
+ if (!network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&NonBlockingInvalidator::Core::RequestDetailedStatus,
+ core_.get(),
+ proxy_callback))) {
+ NOTREACHED();
+ }
+}
+
+NetworkChannelCreator
+ NonBlockingInvalidator::MakePushClientChannelCreator(
+ const notifier::NotifierOptions& notifier_options) {
+ return base::Bind(SyncNetworkChannel::CreatePushClientChannel,
+ notifier_options);
+}
+
+NetworkChannelCreator NonBlockingInvalidator::MakeGCMNetworkChannelCreator(
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ scoped_ptr<GCMNetworkChannelDelegate> delegate) {
+ return base::Bind(&SyncNetworkChannel::CreateGCMNetworkChannel,
+ request_context_getter,
+ base::Passed(&delegate));
+}
+
+void NonBlockingInvalidator::ClearAndSetNewClientId(const std::string& data) {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ invalidation_state_tracker_->ClearAndSetNewClientId(data);
+}
+
+std::string NonBlockingInvalidator::GetInvalidatorClientId() const {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ return invalidation_state_tracker_->GetInvalidatorClientId();
+}
+
+void NonBlockingInvalidator::SetBootstrapData(const std::string& data) {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ invalidation_state_tracker_->SetBootstrapData(data);
+}
+
+std::string NonBlockingInvalidator::GetBootstrapData() const {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ return invalidation_state_tracker_->GetBootstrapData();
+}
+
+void NonBlockingInvalidator::SetSavedInvalidations(
+ const UnackedInvalidationsMap& states) {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ invalidation_state_tracker_->SetSavedInvalidations(states);
+}
+
+UnackedInvalidationsMap NonBlockingInvalidator::GetSavedInvalidations() const {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ return invalidation_state_tracker_->GetSavedInvalidations();
+}
+
+void NonBlockingInvalidator::Clear() {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ invalidation_state_tracker_->Clear();
+}
+
+void NonBlockingInvalidator::OnInvalidatorStateChange(InvalidatorState state) {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ registrar_.UpdateInvalidatorState(state);
+}
+
+void NonBlockingInvalidator::OnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ DCHECK(parent_task_runner_->BelongsToCurrentThread());
+ registrar_.DispatchInvalidationsToHandlers(invalidation_map);
+}
+
+std::string NonBlockingInvalidator::GetOwnerName() const { return "Sync"; }
+
+} // namespace syncer
diff --git a/sync/notifier/non_blocking_invalidator.h b/sync/notifier/non_blocking_invalidator.h
new file mode 100644
index 0000000..d08dfb9
--- /dev/null
+++ b/sync/notifier/non_blocking_invalidator.h
@@ -0,0 +1,114 @@
+// Copyright 2014 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.
+//
+// An implementation of SyncNotifier that wraps InvalidationNotifier
+// on its own thread.
+
+#ifndef SYNC_NOTIFIER_NON_BLOCKING_INVALIDATOR_H_
+#define SYNC_NOTIFIER_NON_BLOCKING_INVALIDATOR_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "jingle/notifier/base/notifier_options.h"
+#include "sync/base/sync_export.h"
+#include "sync/notifier/invalidation_state_tracker.h"
+#include "sync/notifier/invalidator.h"
+#include "sync/notifier/invalidator_registrar.h"
+#include "sync/notifier/invalidator_state.h"
+#include "sync/notifier/unacked_invalidation_set.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace syncer {
+class SyncNetworkChannel;
+class GCMNetworkChannelDelegate;
+
+// Callback type for function that creates SyncNetworkChannel. This function
+// gets passed into NonBlockingInvalidator constructor.
+typedef base::Callback<scoped_ptr<SyncNetworkChannel>(void)>
+ NetworkChannelCreator;
+
+class SYNC_EXPORT_PRIVATE NonBlockingInvalidator
+ : public Invalidator,
+ public InvalidationStateTracker {
+ public:
+ // |invalidation_state_tracker| must be initialized and must outlive |this|.
+ NonBlockingInvalidator(
+ NetworkChannelCreator network_channel_creator,
+ const std::string& invalidator_client_id,
+ const UnackedInvalidationsMap& saved_invalidations,
+ const std::string& invalidation_bootstrap_data,
+ InvalidationStateTracker* invalidation_state_tracker,
+ const std::string& client_info,
+ const scoped_refptr<net::URLRequestContextGetter>&
+ request_context_getter);
+
+ virtual ~NonBlockingInvalidator();
+
+ // Invalidator implementation.
+ virtual void RegisterHandler(InvalidationHandler* handler) OVERRIDE;
+ virtual void UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids) OVERRIDE;
+ virtual void UnregisterHandler(InvalidationHandler* handler) OVERRIDE;
+ virtual InvalidatorState GetInvalidatorState() const OVERRIDE;
+ virtual void UpdateCredentials(
+ const std::string& email, const std::string& token) OVERRIDE;
+ virtual void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const
+ OVERRIDE;
+
+ // Static functions to construct callback that creates network channel for
+ // SyncSystemResources. The goal is to pass network channel to invalidator at
+ // the same time not exposing channel specific parameters to invalidator and
+ // channel implementation to client of invalidator.
+ static NetworkChannelCreator MakePushClientChannelCreator(
+ const notifier::NotifierOptions& notifier_options);
+ static NetworkChannelCreator MakeGCMNetworkChannelCreator(
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ scoped_ptr<GCMNetworkChannelDelegate> delegate);
+
+ // These methods are forwarded to the invalidation_state_tracker_.
+ virtual void ClearAndSetNewClientId(const std::string& data) OVERRIDE;
+ virtual std::string GetInvalidatorClientId() const OVERRIDE;
+ virtual void SetBootstrapData(const std::string& data) OVERRIDE;
+ virtual std::string GetBootstrapData() const OVERRIDE;
+ virtual void SetSavedInvalidations(
+ const UnackedInvalidationsMap& states) OVERRIDE;
+ virtual UnackedInvalidationsMap GetSavedInvalidations() const OVERRIDE;
+ virtual void Clear() OVERRIDE;
+
+ private:
+ void OnInvalidatorStateChange(InvalidatorState state);
+ void OnIncomingInvalidation(const ObjectIdInvalidationMap& invalidation_map);
+ std::string GetOwnerName() const;
+
+ friend class NonBlockingInvalidatorTestDelegate;
+
+ struct InitializeOptions;
+ class Core;
+
+ InvalidatorRegistrar registrar_;
+ InvalidationStateTracker* invalidation_state_tracker_;
+
+ // The real guts of NonBlockingInvalidator, which allows this class to live
+ // completely on the parent thread.
+ scoped_refptr<Core> core_;
+ scoped_refptr<base::SingleThreadTaskRunner> parent_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+
+ base::WeakPtrFactory<NonBlockingInvalidator> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NonBlockingInvalidator);
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_NON_BLOCKING_INVALIDATOR_H_
diff --git a/sync/notifier/non_blocking_invalidator_unittest.cc b/sync/notifier/non_blocking_invalidator_unittest.cc
new file mode 100644
index 0000000..47043ec
--- /dev/null
+++ b/sync/notifier/non_blocking_invalidator_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2014 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/notifier/non_blocking_invalidator.h"
+
+#include "base/bind_helpers.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "google/cacheinvalidation/types.pb.h"
+#include "jingle/notifier/base/fake_base_task.h"
+#include "net/url_request/url_request_test_util.h"
+#include "sync/notifier/fake_invalidation_handler.h"
+#include "sync/notifier/invalidation_state_tracker.h"
+#include "sync/notifier/invalidator_test_template.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+class NonBlockingInvalidatorTestDelegate {
+ public:
+ NonBlockingInvalidatorTestDelegate() : io_thread_("IO thread") {}
+
+ ~NonBlockingInvalidatorTestDelegate() {
+ DestroyInvalidator();
+ }
+
+ void CreateInvalidator(
+ const std::string& invalidator_client_id,
+ const std::string& initial_state,
+ const base::WeakPtr<InvalidationStateTracker>&
+ invalidation_state_tracker) {
+ DCHECK(!invalidator_.get());
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ io_thread_.StartWithOptions(options);
+ request_context_getter_ =
+ new net::TestURLRequestContextGetter(io_thread_.message_loop_proxy());
+ notifier::NotifierOptions notifier_options;
+ notifier_options.request_context_getter = request_context_getter_;
+ NetworkChannelCreator network_channel_creator =
+ NonBlockingInvalidator::MakePushClientChannelCreator(notifier_options);
+ invalidator_.reset(
+ new NonBlockingInvalidator(
+ network_channel_creator,
+ invalidator_client_id,
+ UnackedInvalidationsMap(),
+ initial_state,
+ invalidation_state_tracker.get(),
+ "fake_client_info",
+ request_context_getter_));
+ }
+
+ Invalidator* GetInvalidator() {
+ return invalidator_.get();
+ }
+
+ void DestroyInvalidator() {
+ invalidator_.reset();
+ request_context_getter_ = NULL;
+ io_thread_.Stop();
+ message_loop_.RunUntilIdle();
+ }
+
+ void WaitForInvalidator() {
+ base::RunLoop run_loop;
+ ASSERT_TRUE(
+ io_thread_.message_loop_proxy()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&base::DoNothing),
+ run_loop.QuitClosure()));
+ run_loop.Run();
+ }
+
+ void TriggerOnInvalidatorStateChange(InvalidatorState state) {
+ invalidator_->OnInvalidatorStateChange(state);
+ }
+
+ void TriggerOnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ invalidator_->OnIncomingInvalidation(invalidation_map);
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ base::Thread io_thread_;
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ scoped_ptr<NonBlockingInvalidator> invalidator_;
+};
+
+INSTANTIATE_TYPED_TEST_CASE_P(
+ NonBlockingInvalidatorTest, InvalidatorTest,
+ NonBlockingInvalidatorTestDelegate);
+
+} // namespace syncer
diff --git a/sync/notifier/p2p_invalidator.cc b/sync/notifier/p2p_invalidator.cc
new file mode 100644
index 0000000..1eb0596
--- /dev/null
+++ b/sync/notifier/p2p_invalidator.cc
@@ -0,0 +1,299 @@
+// Copyright 2014 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/notifier/p2p_invalidator.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "jingle/notifier/listener/push_client.h"
+#include "sync/notifier/invalidation_handler.h"
+#include "sync/notifier/invalidation_util.h"
+#include "sync/notifier/object_id_invalidation_map.h"
+
+namespace syncer {
+
+const char kSyncP2PNotificationChannel[] = "http://www.google.com/chrome/sync";
+
+namespace {
+
+const char kNotifySelf[] = "notifySelf";
+const char kNotifyOthers[] = "notifyOthers";
+const char kNotifyAll[] = "notifyAll";
+
+const char kSenderIdKey[] = "senderId";
+const char kNotificationTypeKey[] = "notificationType";
+const char kInvalidationsKey[] = "invalidations";
+
+} // namespace
+
+std::string P2PNotificationTargetToString(P2PNotificationTarget target) {
+ switch (target) {
+ case NOTIFY_SELF:
+ return kNotifySelf;
+ case NOTIFY_OTHERS:
+ return kNotifyOthers;
+ case NOTIFY_ALL:
+ return kNotifyAll;
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+P2PNotificationTarget P2PNotificationTargetFromString(
+ const std::string& target_str) {
+ if (target_str == kNotifySelf) {
+ return NOTIFY_SELF;
+ }
+ if (target_str == kNotifyOthers) {
+ return NOTIFY_OTHERS;
+ }
+ if (target_str == kNotifyAll) {
+ return NOTIFY_ALL;
+ }
+ LOG(WARNING) << "Could not parse " << target_str;
+ return NOTIFY_SELF;
+}
+
+P2PNotificationData::P2PNotificationData()
+ : target_(NOTIFY_SELF) {}
+
+P2PNotificationData::P2PNotificationData(
+ const std::string& sender_id,
+ P2PNotificationTarget target,
+ const ObjectIdInvalidationMap& invalidation_map)
+ : sender_id_(sender_id),
+ target_(target),
+ invalidation_map_(invalidation_map) {}
+
+P2PNotificationData::~P2PNotificationData() {}
+
+bool P2PNotificationData::IsTargeted(const std::string& id) const {
+ switch (target_) {
+ case NOTIFY_SELF:
+ return sender_id_ == id;
+ case NOTIFY_OTHERS:
+ return sender_id_ != id;
+ case NOTIFY_ALL:
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+const ObjectIdInvalidationMap&
+P2PNotificationData::GetIdInvalidationMap() const {
+ return invalidation_map_;
+}
+
+bool P2PNotificationData::Equals(const P2PNotificationData& other) const {
+ return
+ (sender_id_ == other.sender_id_) &&
+ (target_ == other.target_) &&
+ (invalidation_map_ == other.invalidation_map_);
+}
+
+std::string P2PNotificationData::ToString() const {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
+ dict->SetString(kSenderIdKey, sender_id_);
+ dict->SetString(kNotificationTypeKey,
+ P2PNotificationTargetToString(target_));
+ dict->Set(kInvalidationsKey, invalidation_map_.ToValue().release());
+ std::string json;
+ base::JSONWriter::Write(dict.get(), &json);
+ return json;
+}
+
+bool P2PNotificationData::ResetFromString(const std::string& str) {
+ scoped_ptr<base::Value> data_value(base::JSONReader::Read(str));
+ const base::DictionaryValue* data_dict = NULL;
+ if (!data_value.get() || !data_value->GetAsDictionary(&data_dict)) {
+ LOG(WARNING) << "Could not parse " << str << " as a dictionary";
+ return false;
+ }
+ if (!data_dict->GetString(kSenderIdKey, &sender_id_)) {
+ LOG(WARNING) << "Could not find string value for " << kSenderIdKey;
+ }
+ std::string target_str;
+ if (!data_dict->GetString(kNotificationTypeKey, &target_str)) {
+ LOG(WARNING) << "Could not find string value for "
+ << kNotificationTypeKey;
+ }
+ target_ = P2PNotificationTargetFromString(target_str);
+ const base::ListValue* invalidation_map_list = NULL;
+ if (!data_dict->GetList(kInvalidationsKey, &invalidation_map_list) ||
+ !invalidation_map_.ResetFromValue(*invalidation_map_list)) {
+ LOG(WARNING) << "Could not parse " << kInvalidationsKey;
+ }
+ return true;
+}
+
+P2PInvalidator::P2PInvalidator(scoped_ptr<notifier::PushClient> push_client,
+ const std::string& invalidator_client_id,
+ P2PNotificationTarget send_notification_target)
+ : push_client_(push_client.Pass()),
+ invalidator_client_id_(invalidator_client_id),
+ logged_in_(false),
+ notifications_enabled_(false),
+ send_notification_target_(send_notification_target) {
+ DCHECK(send_notification_target_ == NOTIFY_OTHERS ||
+ send_notification_target_ == NOTIFY_ALL);
+ push_client_->AddObserver(this);
+}
+
+P2PInvalidator::~P2PInvalidator() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ push_client_->RemoveObserver(this);
+}
+
+void P2PInvalidator::RegisterHandler(InvalidationHandler* handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ registrar_.RegisterHandler(handler);
+}
+
+void P2PInvalidator::UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ObjectIdSet new_ids;
+ const ObjectIdSet& old_ids = registrar_.GetRegisteredIds(handler);
+ std::set_difference(ids.begin(), ids.end(),
+ old_ids.begin(), old_ids.end(),
+ std::inserter(new_ids, new_ids.end()),
+ ObjectIdLessThan());
+ registrar_.UpdateRegisteredIds(handler, ids);
+ const P2PNotificationData notification_data(
+ invalidator_client_id_,
+ send_notification_target_,
+ ObjectIdInvalidationMap::InvalidateAll(ids));
+ SendNotificationData(notification_data);
+}
+
+void P2PInvalidator::UnregisterHandler(InvalidationHandler* handler) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ registrar_.UnregisterHandler(handler);
+}
+
+InvalidatorState P2PInvalidator::GetInvalidatorState() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return registrar_.GetInvalidatorState();
+}
+
+void P2PInvalidator::UpdateCredentials(
+ const std::string& email, const std::string& token) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ notifier::Subscription subscription;
+ subscription.channel = kSyncP2PNotificationChannel;
+ // There may be some subtle issues around case sensitivity of the
+ // from field, but it doesn't matter too much since this is only
+ // used in p2p mode (which is only used in testing).
+ subscription.from = email;
+ push_client_->UpdateSubscriptions(
+ notifier::SubscriptionList(1, subscription));
+ // If already logged in, the new credentials will take effect on the
+ // next reconnection.
+ push_client_->UpdateCredentials(email, token);
+ logged_in_ = true;
+}
+
+void P2PInvalidator::RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // TODO(mferreria): Make the P2P Invalidator work.
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ callback.Run(*value);
+}
+
+void P2PInvalidator::SendInvalidation(const ObjectIdSet& ids) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ObjectIdInvalidationMap invalidation_map =
+ ObjectIdInvalidationMap::InvalidateAll(ids);
+ const P2PNotificationData notification_data(
+ invalidator_client_id_, send_notification_target_, invalidation_map);
+ SendNotificationData(notification_data);
+}
+
+void P2PInvalidator::OnNotificationsEnabled() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ bool just_turned_on = (notifications_enabled_ == false);
+ notifications_enabled_ = true;
+ registrar_.UpdateInvalidatorState(INVALIDATIONS_ENABLED);
+ if (just_turned_on) {
+ const P2PNotificationData notification_data(
+ invalidator_client_id_,
+ NOTIFY_SELF,
+ ObjectIdInvalidationMap::InvalidateAll(
+ registrar_.GetAllRegisteredIds()));
+ SendNotificationData(notification_data);
+ }
+}
+
+void P2PInvalidator::OnNotificationsDisabled(
+ notifier::NotificationsDisabledReason reason) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ registrar_.UpdateInvalidatorState(FromNotifierReason(reason));
+}
+
+void P2PInvalidator::OnIncomingNotification(
+ const notifier::Notification& notification) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "Received notification " << notification.ToString();
+ if (!logged_in_) {
+ DVLOG(1) << "Not logged in yet -- not emitting notification";
+ return;
+ }
+ if (!notifications_enabled_) {
+ DVLOG(1) << "Notifications not on -- not emitting notification";
+ return;
+ }
+ if (notification.channel != kSyncP2PNotificationChannel) {
+ LOG(WARNING) << "Notification from unexpected source "
+ << notification.channel;
+ }
+ P2PNotificationData notification_data;
+ if (!notification_data.ResetFromString(notification.data)) {
+ LOG(WARNING) << "Could not parse notification data from "
+ << notification.data;
+ notification_data = P2PNotificationData(
+ invalidator_client_id_,
+ NOTIFY_ALL,
+ ObjectIdInvalidationMap::InvalidateAll(
+ registrar_.GetAllRegisteredIds()));
+ }
+ if (!notification_data.IsTargeted(invalidator_client_id_)) {
+ DVLOG(1) << "Not a target of the notification -- "
+ << "not emitting notification";
+ return;
+ }
+ registrar_.DispatchInvalidationsToHandlers(
+ notification_data.GetIdInvalidationMap());
+}
+
+void P2PInvalidator::SendNotificationDataForTest(
+ const P2PNotificationData& notification_data) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ SendNotificationData(notification_data);
+}
+
+void P2PInvalidator::SendNotificationData(
+ const P2PNotificationData& notification_data) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (notification_data.GetIdInvalidationMap().Empty()) {
+ DVLOG(1) << "Not sending XMPP notification with empty state map: "
+ << notification_data.ToString();
+ return;
+ }
+ notifier::Notification notification;
+ notification.channel = kSyncP2PNotificationChannel;
+ notification.data = notification_data.ToString();
+ DVLOG(1) << "Sending XMPP notification: " << notification.ToString();
+ push_client_->SendNotification(notification);
+}
+
+} // namespace syncer
diff --git a/sync/notifier/p2p_invalidator.h b/sync/notifier/p2p_invalidator.h
new file mode 100644
index 0000000..8132d8c
--- /dev/null
+++ b/sync/notifier/p2p_invalidator.h
@@ -0,0 +1,150 @@
+// Copyright 2014 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.
+//
+// An invalidator that uses p2p invalidations based on XMPP push
+// notifications. Used only for sync integration tests.
+
+#ifndef SYNC_NOTIFIER_P2P_INVALIDATOR_H_
+#define SYNC_NOTIFIER_P2P_INVALIDATOR_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "jingle/notifier/base/notifier_options.h"
+#include "jingle/notifier/listener/push_client.h"
+#include "jingle/notifier/listener/push_client_observer.h"
+#include "sync/base/sync_export.h"
+#include "sync/internal_api/public/base/model_type.h"
+#include "sync/notifier/invalidator.h"
+#include "sync/notifier/invalidator_registrar.h"
+#include "sync/notifier/invalidator_state.h"
+#include "sync/notifier/object_id_invalidation_map.h"
+
+namespace notifier {
+class PushClient;
+} // namespace notifier
+
+namespace syncer {
+
+// The channel to use for sync notifications.
+SYNC_EXPORT extern const char kSyncP2PNotificationChannel[];
+
+// The intended recipient(s) of a P2P notification.
+enum P2PNotificationTarget {
+ NOTIFY_SELF,
+ FIRST_NOTIFICATION_TARGET = NOTIFY_SELF,
+ NOTIFY_OTHERS,
+ NOTIFY_ALL,
+ LAST_NOTIFICATION_TARGET = NOTIFY_ALL
+};
+
+SYNC_EXPORT_PRIVATE std::string P2PNotificationTargetToString(
+ P2PNotificationTarget target);
+
+// If |target_str| can't be parsed, assumes NOTIFY_SELF.
+SYNC_EXPORT_PRIVATE P2PNotificationTarget P2PNotificationTargetFromString(
+ const std::string& target_str);
+
+// Helper notification data class that can be serialized to and
+// deserialized from a string.
+class SYNC_EXPORT_PRIVATE P2PNotificationData {
+ public:
+ // Initializes with an empty sender ID, target set to NOTIFY_SELF,
+ // and empty changed types.
+ P2PNotificationData();
+ P2PNotificationData(const std::string& sender_id,
+ P2PNotificationTarget target,
+ const ObjectIdInvalidationMap& invalidation_map);
+
+ ~P2PNotificationData();
+
+ // Returns true if the given ID is targeted by this notification.
+ bool IsTargeted(const std::string& id) const;
+
+ const ObjectIdInvalidationMap& GetIdInvalidationMap() const;
+
+ bool Equals(const P2PNotificationData& other) const;
+
+ std::string ToString() const;
+
+ // Returns whether parsing |str| was successful. If parsing was
+ // unsuccessful, the state of the notification is undefined.
+ bool ResetFromString(const std::string& str);
+
+ private:
+ // The unique ID of the client that sent the notification.
+ std::string sender_id_;
+ // The intendent recipient(s) of the notification.
+ P2PNotificationTarget target_;
+ // The invalidation map for the notification.
+ ObjectIdInvalidationMap invalidation_map_;
+};
+
+class SYNC_EXPORT_PRIVATE P2PInvalidator
+ : public Invalidator,
+ public NON_EXPORTED_BASE(notifier::PushClientObserver) {
+ public:
+ // The |send_notification_target| parameter was added to allow us to send
+ // self-notifications in some cases, but not others. The value should be
+ // either NOTIFY_ALL to send notifications to all clients, or NOTIFY_OTHERS
+ // to send notifications to all clients except for the one that triggered the
+ // notification. See crbug.com/97780.
+ P2PInvalidator(scoped_ptr<notifier::PushClient> push_client,
+ const std::string& invalidator_client_id,
+ P2PNotificationTarget send_notification_target);
+
+ virtual ~P2PInvalidator();
+
+ // Invalidator implementation.
+ virtual void RegisterHandler(InvalidationHandler* handler) OVERRIDE;
+ virtual void UpdateRegisteredIds(InvalidationHandler* handler,
+ const ObjectIdSet& ids) OVERRIDE;
+ virtual void UnregisterHandler(InvalidationHandler* handler) OVERRIDE;
+ virtual InvalidatorState GetInvalidatorState() const OVERRIDE;
+ virtual void UpdateCredentials(
+ const std::string& email, const std::string& token) OVERRIDE;
+ virtual void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const
+ OVERRIDE;
+
+ // PushClientObserver implementation.
+ virtual void OnNotificationsEnabled() OVERRIDE;
+ virtual void OnNotificationsDisabled(
+ notifier::NotificationsDisabledReason reason) OVERRIDE;
+ virtual void OnIncomingNotification(
+ const notifier::Notification& notification) OVERRIDE;
+
+ void SendInvalidation(const ObjectIdSet& ids);
+
+ void SendNotificationDataForTest(
+ const P2PNotificationData& notification_data);
+
+ private:
+ void SendNotificationData(const P2PNotificationData& notification_data);
+
+ base::ThreadChecker thread_checker_;
+
+ InvalidatorRegistrar registrar_;
+
+ // The push client.
+ scoped_ptr<notifier::PushClient> push_client_;
+ // Our unique ID.
+ std::string invalidator_client_id_;
+ // Whether we have called UpdateCredentials() yet.
+ bool logged_in_;
+ bool notifications_enabled_;
+ // Which set of clients should be sent notifications.
+ P2PNotificationTarget send_notification_target_;
+
+ DISALLOW_COPY_AND_ASSIGN(P2PInvalidator);
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_P2P_INVALIDATOR_H_
diff --git a/sync/notifier/p2p_invalidator_unittest.cc b/sync/notifier/p2p_invalidator_unittest.cc
new file mode 100644
index 0000000..9f0b273
--- /dev/null
+++ b/sync/notifier/p2p_invalidator_unittest.cc
@@ -0,0 +1,355 @@
+// Copyright 2014 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/notifier/p2p_invalidator.h"
+
+#include <cstddef>
+
+#include "jingle/notifier/listener/fake_push_client.h"
+#include "sync/internal_api/public/base/model_type.h"
+#include "sync/notifier/fake_invalidation_handler.h"
+#include "sync/notifier/invalidator_test_template.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+class P2PInvalidatorTestDelegate {
+ public:
+ P2PInvalidatorTestDelegate() : fake_push_client_(NULL) {}
+
+ ~P2PInvalidatorTestDelegate() {
+ DestroyInvalidator();
+ }
+
+ void CreateInvalidator(
+ const std::string& invalidator_client_id,
+ const std::string& initial_state,
+ const base::WeakPtr<InvalidationStateTracker>&
+ invalidation_state_tracker) {
+ DCHECK(!fake_push_client_);
+ DCHECK(!invalidator_.get());
+ fake_push_client_ = new notifier::FakePushClient();
+ invalidator_.reset(
+ new P2PInvalidator(
+ scoped_ptr<notifier::PushClient>(fake_push_client_),
+ invalidator_client_id,
+ NOTIFY_OTHERS));
+ }
+
+ P2PInvalidator* GetInvalidator() {
+ return invalidator_.get();
+ }
+
+ notifier::FakePushClient* GetPushClient() {
+ return fake_push_client_;
+ }
+
+ void DestroyInvalidator() {
+ invalidator_.reset();
+ fake_push_client_ = NULL;
+ }
+
+ void WaitForInvalidator() {
+ // Do Nothing.
+ }
+
+ void TriggerOnInvalidatorStateChange(InvalidatorState state) {
+ if (state == INVALIDATIONS_ENABLED) {
+ fake_push_client_->EnableNotifications();
+ } else {
+ fake_push_client_->DisableNotifications(ToNotifierReasonForTest(state));
+ }
+ }
+
+ void TriggerOnIncomingInvalidation(
+ const ObjectIdInvalidationMap& invalidation_map) {
+ const P2PNotificationData notification_data(
+ std::string(), NOTIFY_ALL, invalidation_map);
+ notifier::Notification notification;
+ notification.channel = kSyncP2PNotificationChannel;
+ notification.data = notification_data.ToString();
+ fake_push_client_->SimulateIncomingNotification(notification);
+ }
+
+ private:
+ // Owned by |invalidator_|.
+ notifier::FakePushClient* fake_push_client_;
+ scoped_ptr<P2PInvalidator> invalidator_;
+};
+
+class P2PInvalidatorTest : public testing::Test {
+ protected:
+ P2PInvalidatorTest()
+ : next_sent_notification_to_reflect_(0) {
+ delegate_.CreateInvalidator("sender",
+ "fake_state",
+ base::WeakPtr<InvalidationStateTracker>());
+ delegate_.GetInvalidator()->RegisterHandler(&fake_handler_);
+ }
+
+ virtual ~P2PInvalidatorTest() {
+ delegate_.GetInvalidator()->UnregisterHandler(&fake_handler_);
+ }
+
+ ObjectIdInvalidationMap MakeInvalidationMap(ModelTypeSet types) {
+ ObjectIdInvalidationMap invalidations;
+ ObjectIdSet ids = ModelTypeSetToObjectIdSet(types);
+ return ObjectIdInvalidationMap::InvalidateAll(ids);
+ }
+
+ // Simulate receiving all the notifications we sent out since last
+ // time this was called.
+ void ReflectSentNotifications() {
+ const std::vector<notifier::Notification>& sent_notifications =
+ delegate_.GetPushClient()->sent_notifications();
+ for(size_t i = next_sent_notification_to_reflect_;
+ i < sent_notifications.size(); ++i) {
+ delegate_.GetInvalidator()->OnIncomingNotification(sent_notifications[i]);
+ }
+ next_sent_notification_to_reflect_ = sent_notifications.size();
+ }
+
+ FakeInvalidationHandler fake_handler_;
+ P2PInvalidatorTestDelegate delegate_;
+
+ private:
+ size_t next_sent_notification_to_reflect_;
+};
+
+// Make sure the P2PNotificationTarget <-> string conversions work.
+TEST_F(P2PInvalidatorTest, P2PNotificationTarget) {
+ for (int i = FIRST_NOTIFICATION_TARGET;
+ i <= LAST_NOTIFICATION_TARGET; ++i) {
+ P2PNotificationTarget target = static_cast<P2PNotificationTarget>(i);
+ const std::string& target_str = P2PNotificationTargetToString(target);
+ EXPECT_FALSE(target_str.empty());
+ EXPECT_EQ(target, P2PNotificationTargetFromString(target_str));
+ }
+ EXPECT_EQ(NOTIFY_SELF, P2PNotificationTargetFromString("unknown"));
+}
+
+// Make sure notification targeting works correctly.
+TEST_F(P2PInvalidatorTest, P2PNotificationDataIsTargeted) {
+ {
+ const P2PNotificationData notification_data(
+ "sender", NOTIFY_SELF, ObjectIdInvalidationMap());
+ EXPECT_TRUE(notification_data.IsTargeted("sender"));
+ EXPECT_FALSE(notification_data.IsTargeted("other1"));
+ EXPECT_FALSE(notification_data.IsTargeted("other2"));
+ }
+ {
+ const P2PNotificationData notification_data(
+ "sender", NOTIFY_OTHERS, ObjectIdInvalidationMap());
+ EXPECT_FALSE(notification_data.IsTargeted("sender"));
+ EXPECT_TRUE(notification_data.IsTargeted("other1"));
+ EXPECT_TRUE(notification_data.IsTargeted("other2"));
+ }
+ {
+ const P2PNotificationData notification_data(
+ "sender", NOTIFY_ALL, ObjectIdInvalidationMap());
+ EXPECT_TRUE(notification_data.IsTargeted("sender"));
+ EXPECT_TRUE(notification_data.IsTargeted("other1"));
+ EXPECT_TRUE(notification_data.IsTargeted("other2"));
+ }
+}
+
+// Make sure the P2PNotificationData <-> string conversions work for a
+// default-constructed P2PNotificationData.
+TEST_F(P2PInvalidatorTest, P2PNotificationDataDefault) {
+ const P2PNotificationData notification_data;
+ EXPECT_TRUE(notification_data.IsTargeted(std::string()));
+ EXPECT_FALSE(notification_data.IsTargeted("other1"));
+ EXPECT_FALSE(notification_data.IsTargeted("other2"));
+ EXPECT_TRUE(notification_data.GetIdInvalidationMap().Empty());
+ const std::string& notification_data_str = notification_data.ToString();
+ EXPECT_EQ(
+ "{\"invalidations\":[],\"notificationType\":\"notifySelf\","
+ "\"senderId\":\"\"}", notification_data_str);
+
+ P2PNotificationData notification_data_parsed;
+ EXPECT_TRUE(notification_data_parsed.ResetFromString(notification_data_str));
+ EXPECT_TRUE(notification_data.Equals(notification_data_parsed));
+}
+
+// Make sure the P2PNotificationData <-> string conversions work for a
+// non-default-constructed P2PNotificationData.
+TEST_F(P2PInvalidatorTest, P2PNotificationDataNonDefault) {
+ ObjectIdInvalidationMap invalidation_map =
+ ObjectIdInvalidationMap::InvalidateAll(
+ ModelTypeSetToObjectIdSet(ModelTypeSet(BOOKMARKS, THEMES)));
+ const P2PNotificationData notification_data("sender",
+ NOTIFY_ALL,
+ invalidation_map);
+ EXPECT_TRUE(notification_data.IsTargeted("sender"));
+ EXPECT_TRUE(notification_data.IsTargeted("other1"));
+ EXPECT_TRUE(notification_data.IsTargeted("other2"));
+ EXPECT_EQ(invalidation_map, notification_data.GetIdInvalidationMap());
+ const std::string& notification_data_str = notification_data.ToString();
+ EXPECT_EQ(
+ "{\"invalidations\":["
+ "{\"isUnknownVersion\":true,"
+ "\"objectId\":{\"name\":\"BOOKMARK\",\"source\":1004}},"
+ "{\"isUnknownVersion\":true,"
+ "\"objectId\":{\"name\":\"THEME\",\"source\":1004}}"
+ "],\"notificationType\":\"notifyAll\","
+ "\"senderId\":\"sender\"}", notification_data_str);
+
+ P2PNotificationData notification_data_parsed;
+ EXPECT_TRUE(notification_data_parsed.ResetFromString(notification_data_str));
+ EXPECT_TRUE(notification_data.Equals(notification_data_parsed));
+}
+
+// Set up the P2PInvalidator, simulate a successful connection, and send
+// a notification with the default target (NOTIFY_OTHERS). The
+// observer should receive only a notification from the call to
+// UpdateEnabledTypes().
+TEST_F(P2PInvalidatorTest, NotificationsBasic) {
+ const ModelTypeSet enabled_types(BOOKMARKS, PREFERENCES);
+
+ P2PInvalidator* const invalidator = delegate_.GetInvalidator();
+ notifier::FakePushClient* const push_client = delegate_.GetPushClient();
+
+ invalidator->UpdateRegisteredIds(&fake_handler_,
+ ModelTypeSetToObjectIdSet(enabled_types));
+
+ const char kEmail[] = "foo@bar.com";
+ const char kToken[] = "token";
+ invalidator->UpdateCredentials(kEmail, kToken);
+ {
+ notifier::Subscription expected_subscription;
+ expected_subscription.channel = kSyncP2PNotificationChannel;
+ expected_subscription.from = kEmail;
+ EXPECT_TRUE(notifier::SubscriptionListsEqual(
+ push_client->subscriptions(),
+ notifier::SubscriptionList(1, expected_subscription)));
+ }
+ EXPECT_EQ(kEmail, push_client->email());
+ EXPECT_EQ(kToken, push_client->token());
+
+ ReflectSentNotifications();
+ push_client->EnableNotifications();
+ EXPECT_EQ(INVALIDATIONS_ENABLED, fake_handler_.GetInvalidatorState());
+
+ ReflectSentNotifications();
+ EXPECT_EQ(1, fake_handler_.GetInvalidationCount());
+ EXPECT_THAT(
+ MakeInvalidationMap(enabled_types),
+ Eq(fake_handler_.GetLastInvalidationMap()));
+
+ // Sent with target NOTIFY_OTHERS so should not be propagated to
+ // |fake_handler_|.
+ invalidator->SendInvalidation(
+ ModelTypeSetToObjectIdSet(ModelTypeSet(THEMES, APPS)));
+
+ ReflectSentNotifications();
+ EXPECT_EQ(1, fake_handler_.GetInvalidationCount());
+}
+
+// Set up the P2PInvalidator and send out notifications with various
+// target settings. The notifications received by the observer should
+// be consistent with the target settings.
+TEST_F(P2PInvalidatorTest, SendNotificationData) {
+ const ModelTypeSet enabled_types(BOOKMARKS, PREFERENCES, THEMES);
+ const ModelTypeSet changed_types(THEMES, APPS);
+ const ModelTypeSet expected_types(THEMES);
+
+ const ObjectIdInvalidationMap& invalidation_map =
+ MakeInvalidationMap(changed_types);
+
+ P2PInvalidator* const invalidator = delegate_.GetInvalidator();
+ notifier::FakePushClient* const push_client = delegate_.GetPushClient();
+
+ invalidator->UpdateRegisteredIds(&fake_handler_,
+ ModelTypeSetToObjectIdSet(enabled_types));
+
+ invalidator->UpdateCredentials("foo@bar.com", "fake_token");
+
+ ReflectSentNotifications();
+ push_client->EnableNotifications();
+ EXPECT_EQ(INVALIDATIONS_ENABLED, fake_handler_.GetInvalidatorState());
+
+ ReflectSentNotifications();
+ EXPECT_EQ(1, fake_handler_.GetInvalidationCount());
+ EXPECT_EQ(ModelTypeSetToObjectIdSet(enabled_types),
+ fake_handler_.GetLastInvalidationMap().GetObjectIds());
+
+ // Should be dropped.
+ invalidator->SendNotificationDataForTest(P2PNotificationData());
+ ReflectSentNotifications();
+ EXPECT_EQ(1, fake_handler_.GetInvalidationCount());
+
+ const ObjectIdSet& expected_ids = ModelTypeSetToObjectIdSet(expected_types);
+
+ // Should be propagated.
+ invalidator->SendNotificationDataForTest(
+ P2PNotificationData("sender", NOTIFY_SELF, invalidation_map));
+ ReflectSentNotifications();
+ EXPECT_EQ(2, fake_handler_.GetInvalidationCount());
+ EXPECT_EQ(expected_ids,
+ fake_handler_.GetLastInvalidationMap().GetObjectIds());
+
+ // Should be dropped.
+ invalidator->SendNotificationDataForTest(
+ P2PNotificationData("sender2", NOTIFY_SELF, invalidation_map));
+ ReflectSentNotifications();
+ EXPECT_EQ(2, fake_handler_.GetInvalidationCount());
+
+ // Should be dropped.
+ invalidator->SendNotificationDataForTest(
+ P2PNotificationData("sender", NOTIFY_SELF, ObjectIdInvalidationMap()));
+ ReflectSentNotifications();
+ EXPECT_EQ(2, fake_handler_.GetInvalidationCount());
+
+ // Should be dropped.
+ invalidator->SendNotificationDataForTest(
+ P2PNotificationData("sender", NOTIFY_OTHERS, invalidation_map));
+ ReflectSentNotifications();
+ EXPECT_EQ(2, fake_handler_.GetInvalidationCount());
+
+ // Should be propagated.
+ invalidator->SendNotificationDataForTest(
+ P2PNotificationData("sender2", NOTIFY_OTHERS, invalidation_map));
+ ReflectSentNotifications();
+ EXPECT_EQ(3, fake_handler_.GetInvalidationCount());
+ EXPECT_EQ(expected_ids,
+ fake_handler_.GetLastInvalidationMap().GetObjectIds());
+
+ // Should be dropped.
+ invalidator->SendNotificationDataForTest(
+ P2PNotificationData("sender2", NOTIFY_OTHERS, ObjectIdInvalidationMap()));
+ ReflectSentNotifications();
+ EXPECT_EQ(3, fake_handler_.GetInvalidationCount());
+
+ // Should be propagated.
+ invalidator->SendNotificationDataForTest(
+ P2PNotificationData("sender", NOTIFY_ALL, invalidation_map));
+ ReflectSentNotifications();
+ EXPECT_EQ(4, fake_handler_.GetInvalidationCount());
+ EXPECT_EQ(expected_ids,
+ fake_handler_.GetLastInvalidationMap().GetObjectIds());
+
+ // Should be propagated.
+ invalidator->SendNotificationDataForTest(
+ P2PNotificationData("sender2", NOTIFY_ALL, invalidation_map));
+ ReflectSentNotifications();
+ EXPECT_EQ(5, fake_handler_.GetInvalidationCount());
+ EXPECT_EQ(expected_ids,
+ fake_handler_.GetLastInvalidationMap().GetObjectIds());
+
+ // Should be dropped.
+ invalidator->SendNotificationDataForTest(
+ P2PNotificationData("sender2", NOTIFY_ALL, ObjectIdInvalidationMap()));
+ ReflectSentNotifications();
+ EXPECT_EQ(5, fake_handler_.GetInvalidationCount());
+}
+
+INSTANTIATE_TYPED_TEST_CASE_P(
+ P2PInvalidatorTest, InvalidatorTest,
+ P2PInvalidatorTestDelegate);
+
+} // namespace
+
+} // namespace syncer
diff --git a/sync/notifier/push_client_channel.cc b/sync/notifier/push_client_channel.cc
new file mode 100644
index 0000000..0bd07c9
--- /dev/null
+++ b/sync/notifier/push_client_channel.cc
@@ -0,0 +1,161 @@
+// Copyright 2014 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/notifier/push_client_channel.h"
+
+#include "base/stl_util.h"
+#include "google/cacheinvalidation/client_gateway.pb.h"
+#include "google/cacheinvalidation/types.pb.h"
+#include "jingle/notifier/listener/push_client.h"
+
+namespace syncer {
+
+namespace {
+
+const char kBotJid[] = "tango@bot.talk.google.com";
+const char kChannelName[] = "tango_raw";
+
+} // namespace
+
+PushClientChannel::PushClientChannel(
+ scoped_ptr<notifier::PushClient> push_client)
+ : push_client_(push_client.Pass()),
+ scheduling_hash_(0),
+ sent_messages_count_(0) {
+ push_client_->AddObserver(this);
+ notifier::Subscription subscription;
+ subscription.channel = kChannelName;
+ subscription.from = "";
+ notifier::SubscriptionList subscriptions;
+ subscriptions.push_back(subscription);
+ push_client_->UpdateSubscriptions(subscriptions);
+}
+
+PushClientChannel::~PushClientChannel() {
+ push_client_->RemoveObserver(this);
+}
+
+void PushClientChannel::UpdateCredentials(
+ const std::string& email, const std::string& token) {
+ push_client_->UpdateCredentials(email, token);
+}
+
+int PushClientChannel::GetInvalidationClientType() {
+#if defined(OS_IOS)
+ return ipc::invalidation::ClientType::CHROME_SYNC_IOS;
+#else
+ return ipc::invalidation::ClientType::CHROME_SYNC;
+#endif
+}
+
+void PushClientChannel::RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) {
+ callback.Run(*CollectDebugData());
+}
+
+void PushClientChannel::SendMessage(const std::string& message) {
+ std::string encoded_message;
+ EncodeMessage(&encoded_message, message, service_context_, scheduling_hash_);
+
+ notifier::Recipient recipient;
+ recipient.to = kBotJid;
+ notifier::Notification notification;
+ notification.channel = kChannelName;
+ notification.recipients.push_back(recipient);
+ notification.data = encoded_message;
+ push_client_->SendNotification(notification);
+ sent_messages_count_++;
+}
+
+void PushClientChannel::OnNotificationsEnabled() {
+ NotifyStateChange(INVALIDATIONS_ENABLED);
+}
+
+void PushClientChannel::OnNotificationsDisabled(
+ notifier::NotificationsDisabledReason reason) {
+ NotifyStateChange(FromNotifierReason(reason));
+}
+
+void PushClientChannel::OnIncomingNotification(
+ const notifier::Notification& notification) {
+ std::string message;
+ std::string service_context;
+ int64 scheduling_hash;
+ if (!DecodeMessage(
+ notification.data, &message, &service_context, &scheduling_hash)) {
+ DLOG(ERROR) << "Could not parse ClientGatewayMessage";
+ return;
+ }
+ if (DeliverIncomingMessage(message)) {
+ service_context_ = service_context;
+ scheduling_hash_ = scheduling_hash;
+ }
+}
+
+const std::string& PushClientChannel::GetServiceContextForTest() const {
+ return service_context_;
+}
+
+int64 PushClientChannel::GetSchedulingHashForTest() const {
+ return scheduling_hash_;
+}
+
+std::string PushClientChannel::EncodeMessageForTest(
+ const std::string& message,
+ const std::string& service_context,
+ int64 scheduling_hash) {
+ std::string encoded_message;
+ EncodeMessage(&encoded_message, message, service_context, scheduling_hash);
+ return encoded_message;
+}
+
+bool PushClientChannel::DecodeMessageForTest(const std::string& data,
+ std::string* message,
+ std::string* service_context,
+ int64* scheduling_hash) {
+ return DecodeMessage(data, message, service_context, scheduling_hash);
+}
+
+void PushClientChannel::EncodeMessage(std::string* encoded_message,
+ const std::string& message,
+ const std::string& service_context,
+ int64 scheduling_hash) {
+ ipc::invalidation::ClientGatewayMessage envelope;
+ envelope.set_is_client_to_server(true);
+ if (!service_context.empty()) {
+ envelope.set_service_context(service_context);
+ envelope.set_rpc_scheduling_hash(scheduling_hash);
+ }
+ envelope.set_network_message(message);
+ envelope.SerializeToString(encoded_message);
+}
+
+bool PushClientChannel::DecodeMessage(const std::string& data,
+ std::string* message,
+ std::string* service_context,
+ int64* scheduling_hash) {
+ ipc::invalidation::ClientGatewayMessage envelope;
+ if (!envelope.ParseFromString(data)) {
+ return false;
+ }
+ *message = envelope.network_message();
+ if (envelope.has_service_context()) {
+ *service_context = envelope.service_context();
+ }
+ if (envelope.has_rpc_scheduling_hash()) {
+ *scheduling_hash = envelope.rpc_scheduling_hash();
+ }
+ return true;
+}
+
+scoped_ptr<base::DictionaryValue> PushClientChannel::CollectDebugData() const {
+ scoped_ptr<base::DictionaryValue> status(new base::DictionaryValue);
+ status->SetString("PushClientChannel.NetworkChannel", "Push Client");
+ status->SetInteger("PushClientChannel.SentMessages", sent_messages_count_);
+ status->SetInteger("PushClientChannel.ReceivedMessages",
+ SyncNetworkChannel::GetReceivedMessagesCount());
+ return status.Pass();
+}
+
+} // namespace syncer
diff --git a/sync/notifier/push_client_channel.h b/sync/notifier/push_client_channel.h
new file mode 100644
index 0000000..b3e4bf2
--- /dev/null
+++ b/sync/notifier/push_client_channel.h
@@ -0,0 +1,91 @@
+// Copyright 2014 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.
+
+#ifndef SYNC_NOTIFIER_PUSH_CLIENT_CHANNEL_H_
+#define SYNC_NOTIFIER_PUSH_CLIENT_CHANNEL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "jingle/notifier/listener/push_client_observer.h"
+#include "sync/base/sync_export.h"
+#include "sync/notifier/sync_system_resources.h"
+
+namespace notifier {
+class PushClient;
+} // namespace notifier
+
+namespace syncer {
+
+// A PushClientChannel is an implementation of NetworkChannel that
+// routes messages through a PushClient.
+class SYNC_EXPORT_PRIVATE PushClientChannel
+ : public SyncNetworkChannel,
+ public NON_EXPORTED_BASE(notifier::PushClientObserver) {
+ public:
+ // |push_client| is guaranteed to be destroyed only when this object
+ // is destroyed.
+ explicit PushClientChannel(scoped_ptr<notifier::PushClient> push_client);
+
+ virtual ~PushClientChannel();
+
+ // invalidation::NetworkChannel implementation.
+ virtual void SendMessage(const std::string& message) OVERRIDE;
+ virtual void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) OVERRIDE;
+
+ // SyncNetworkChannel implementation.
+ // If not connected, connects with the given credentials. If
+ // already connected, the next connection attempt will use the given
+ // credentials.
+ virtual void UpdateCredentials(const std::string& email,
+ const std::string& token) OVERRIDE;
+ virtual int GetInvalidationClientType() OVERRIDE;
+
+ // notifier::PushClient::Observer implementation.
+ virtual void OnNotificationsEnabled() OVERRIDE;
+ virtual void OnNotificationsDisabled(
+ notifier::NotificationsDisabledReason reason) OVERRIDE;
+ virtual void OnIncomingNotification(
+ const notifier::Notification& notification) OVERRIDE;
+
+ const std::string& GetServiceContextForTest() const;
+
+ int64 GetSchedulingHashForTest() const;
+
+ static std::string EncodeMessageForTest(const std::string& message,
+ const std::string& service_context,
+ int64 scheduling_hash);
+
+ static bool DecodeMessageForTest(const std::string& notification,
+ std::string* message,
+ std::string* service_context,
+ int64* scheduling_hash);
+
+ private:
+ static void EncodeMessage(std::string* encoded_message,
+ const std::string& message,
+ const std::string& service_context,
+ int64 scheduling_hash);
+ static bool DecodeMessage(const std::string& data,
+ std::string* message,
+ std::string* service_context,
+ int64* scheduling_hash);
+ scoped_ptr<base::DictionaryValue> CollectDebugData() const;
+
+ scoped_ptr<notifier::PushClient> push_client_;
+ std::string service_context_;
+ int64 scheduling_hash_;
+
+ // This count is saved for displaying statatistics.
+ int sent_messages_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(PushClientChannel);
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_PUSH_CLIENT_CHANNEL_H_
diff --git a/sync/notifier/push_client_channel_unittest.cc b/sync/notifier/push_client_channel_unittest.cc
new file mode 100644
index 0000000..e58d20b
--- /dev/null
+++ b/sync/notifier/push_client_channel_unittest.cc
@@ -0,0 +1,252 @@
+// Copyright 2014 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 <string>
+
+#include "base/compiler_specific.h"
+#include "jingle/notifier/listener/fake_push_client.h"
+#include "jingle/notifier/listener/notification_defines.h"
+#include "sync/notifier/push_client_channel.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+namespace {
+
+class PushClientChannelTest
+ : public ::testing::Test,
+ public SyncNetworkChannel::Observer {
+ protected:
+ PushClientChannelTest()
+ : fake_push_client_(new notifier::FakePushClient()),
+ push_client_channel_(
+ scoped_ptr<notifier::PushClient>(fake_push_client_)),
+ last_invalidator_state_(DEFAULT_INVALIDATION_ERROR) {
+ push_client_channel_.AddObserver(this);
+ push_client_channel_.SetMessageReceiver(
+ invalidation::NewPermanentCallback(
+ this, &PushClientChannelTest::OnIncomingMessage));
+ push_client_channel_.SetSystemResources(NULL);
+ }
+
+ virtual ~PushClientChannelTest() {
+ push_client_channel_.RemoveObserver(this);
+ }
+
+ virtual void OnNetworkChannelStateChanged(
+ InvalidatorState invalidator_state) OVERRIDE {
+ last_invalidator_state_ = invalidator_state;
+ }
+
+ void OnIncomingMessage(std::string incoming_message) {
+ last_message_ = incoming_message;
+ }
+
+ notifier::FakePushClient* fake_push_client_;
+ PushClientChannel push_client_channel_;
+ std::string last_message_;
+ InvalidatorState last_invalidator_state_;
+};
+
+const char kMessage[] = "message";
+const char kServiceContext[] = "service context";
+const int64 kSchedulingHash = 100;
+
+// Make sure the channel subscribes to the correct notifications
+// channel on construction.
+TEST_F(PushClientChannelTest, Subscriptions) {
+ notifier::Subscription expected_subscription;
+ expected_subscription.channel = "tango_raw";
+ EXPECT_TRUE(notifier::SubscriptionListsEqual(
+ fake_push_client_->subscriptions(),
+ notifier::SubscriptionList(1, expected_subscription)));
+}
+
+// Call UpdateCredentials on the channel. It should propagate it to
+// the push client.
+TEST_F(PushClientChannelTest, UpdateCredentials) {
+ const char kEmail[] = "foo@bar.com";
+ const char kToken[] = "token";
+ EXPECT_TRUE(fake_push_client_->email().empty());
+ EXPECT_TRUE(fake_push_client_->token().empty());
+ push_client_channel_.UpdateCredentials(kEmail, kToken);
+ EXPECT_EQ(kEmail, fake_push_client_->email());
+ EXPECT_EQ(kToken, fake_push_client_->token());
+}
+
+// Simulate push client state changes on the push client. It should
+// propagate to the channel.
+TEST_F(PushClientChannelTest, OnPushClientStateChange) {
+ EXPECT_EQ(DEFAULT_INVALIDATION_ERROR, last_invalidator_state_);
+ fake_push_client_->EnableNotifications();
+ EXPECT_EQ(INVALIDATIONS_ENABLED, last_invalidator_state_);
+ fake_push_client_->DisableNotifications(
+ notifier::TRANSIENT_NOTIFICATION_ERROR);
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, last_invalidator_state_);
+ fake_push_client_->DisableNotifications(
+ notifier::NOTIFICATION_CREDENTIALS_REJECTED);
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, last_invalidator_state_);
+}
+
+// Call SendMessage on the channel. It should propagate it to the
+// push client.
+TEST_F(PushClientChannelTest, SendMessage) {
+ EXPECT_TRUE(fake_push_client_->sent_notifications().empty());
+ push_client_channel_.SendMessage(kMessage);
+ ASSERT_EQ(1u, fake_push_client_->sent_notifications().size());
+ std::string expected_encoded_message =
+ PushClientChannel::EncodeMessageForTest(
+ kMessage,
+ push_client_channel_.GetServiceContextForTest(),
+ push_client_channel_.GetSchedulingHashForTest());
+ ASSERT_EQ(expected_encoded_message,
+ fake_push_client_->sent_notifications()[0].data);
+}
+
+// Encode a message with some context and then decode it. The decoded info
+// should match the original info.
+TEST_F(PushClientChannelTest, EncodeDecode) {
+ const std::string& data = PushClientChannel::EncodeMessageForTest(
+ kMessage, kServiceContext, kSchedulingHash);
+ std::string message;
+ std::string service_context;
+ int64 scheduling_hash = 0LL;
+ EXPECT_TRUE(PushClientChannel::DecodeMessageForTest(
+ data, &message, &service_context, &scheduling_hash));
+ EXPECT_EQ(kMessage, message);
+ EXPECT_EQ(kServiceContext, service_context);
+ EXPECT_EQ(kSchedulingHash, scheduling_hash);
+}
+
+// Encode a message with no context and then decode it. The decoded message
+// should match the original message, but the context and hash should be
+// untouched.
+TEST_F(PushClientChannelTest, EncodeDecodeNoContext) {
+ const std::string& data = PushClientChannel::EncodeMessageForTest(
+ kMessage, std::string(), kSchedulingHash);
+ std::string message;
+ std::string service_context = kServiceContext;
+ int64 scheduling_hash = kSchedulingHash + 1;
+ EXPECT_TRUE(PushClientChannel::DecodeMessageForTest(
+ data, &message, &service_context, &scheduling_hash));
+ EXPECT_EQ(kMessage, message);
+ EXPECT_EQ(kServiceContext, service_context);
+ EXPECT_EQ(kSchedulingHash + 1, scheduling_hash);
+}
+
+// Decode an empty notification. It should result in an empty message
+// but should leave the context and hash untouched.
+TEST_F(PushClientChannelTest, DecodeEmpty) {
+ std::string message = kMessage;
+ std::string service_context = kServiceContext;
+ int64 scheduling_hash = kSchedulingHash;
+ EXPECT_TRUE(PushClientChannel::DecodeMessageForTest(
+ std::string(), &message, &service_context, &scheduling_hash));
+ EXPECT_TRUE(message.empty());
+ EXPECT_EQ(kServiceContext, service_context);
+ EXPECT_EQ(kSchedulingHash, scheduling_hash);
+}
+
+// Try to decode a garbage notification. It should leave all its
+// arguments untouched and return false.
+TEST_F(PushClientChannelTest, DecodeGarbage) {
+ std::string data = "garbage";
+ std::string message = kMessage;
+ std::string service_context = kServiceContext;
+ int64 scheduling_hash = kSchedulingHash;
+ EXPECT_FALSE(PushClientChannel::DecodeMessageForTest(
+ data, &message, &service_context, &scheduling_hash));
+ EXPECT_EQ(kMessage, message);
+ EXPECT_EQ(kServiceContext, service_context);
+ EXPECT_EQ(kSchedulingHash, scheduling_hash);
+}
+
+// Simulate an incoming notification. It should be decoded properly
+// by the channel.
+TEST_F(PushClientChannelTest, OnIncomingMessage) {
+ notifier::Notification notification;
+ notification.data =
+ PushClientChannel::EncodeMessageForTest(
+ kMessage, kServiceContext, kSchedulingHash);
+ fake_push_client_->SimulateIncomingNotification(notification);
+
+ EXPECT_EQ(kServiceContext,
+ push_client_channel_.GetServiceContextForTest());
+ EXPECT_EQ(kSchedulingHash,
+ push_client_channel_.GetSchedulingHashForTest());
+ EXPECT_EQ(kMessage, last_message_);
+}
+
+// Simulate an incoming notification with no receiver. It should be dropped by
+// the channel.
+TEST_F(PushClientChannelTest, OnIncomingMessageNoReceiver) {
+ push_client_channel_.SetMessageReceiver(NULL);
+
+ notifier::Notification notification;
+ notification.data = PushClientChannel::EncodeMessageForTest(
+ kMessage, kServiceContext, kSchedulingHash);
+ fake_push_client_->SimulateIncomingNotification(notification);
+
+ EXPECT_TRUE(push_client_channel_.GetServiceContextForTest().empty());
+ EXPECT_EQ(static_cast<int64>(0),
+ push_client_channel_.GetSchedulingHashForTest());
+ EXPECT_TRUE(last_message_.empty());
+}
+
+// Simulate an incoming garbage notification. It should be dropped by
+// the channel.
+TEST_F(PushClientChannelTest, OnIncomingMessageGarbage) {
+ notifier::Notification notification;
+ notification.data = "garbage";
+ fake_push_client_->SimulateIncomingNotification(notification);
+ EXPECT_TRUE(push_client_channel_.GetServiceContextForTest().empty());
+ EXPECT_EQ(static_cast<int64>(0),
+ push_client_channel_.GetSchedulingHashForTest());
+ EXPECT_TRUE(last_message_.empty());
+}
+
+// Send a message, simulate an incoming message with context, and then
+// send the same message again. The first sent message should not
+// have any context, but the second sent message should have the
+// context from the incoming emssage.
+TEST_F(PushClientChannelTest, PersistedMessageState) {
+ push_client_channel_.SendMessage(kMessage);
+ ASSERT_EQ(1u, fake_push_client_->sent_notifications().size());
+ {
+ std::string message;
+ std::string service_context;
+ int64 scheduling_hash = 0LL;
+ EXPECT_TRUE(PushClientChannel::DecodeMessageForTest(
+ fake_push_client_->sent_notifications()[0].data,
+ &message,
+ &service_context,
+ &scheduling_hash));
+ EXPECT_EQ(kMessage, message);
+ EXPECT_TRUE(service_context.empty());
+ EXPECT_EQ(0LL, scheduling_hash);
+ }
+
+ notifier::Notification notification;
+ notification.data = PushClientChannel::EncodeMessageForTest(
+ kMessage, kServiceContext, kSchedulingHash);
+ fake_push_client_->SimulateIncomingNotification(notification);
+
+ push_client_channel_.SendMessage(kMessage);
+ ASSERT_EQ(2u, fake_push_client_->sent_notifications().size());
+ {
+ std::string message;
+ std::string service_context;
+ int64 scheduling_hash = 0LL;
+ EXPECT_TRUE(PushClientChannel::DecodeMessageForTest(
+ fake_push_client_->sent_notifications()[1].data,
+ &message,
+ &service_context,
+ &scheduling_hash));
+ EXPECT_EQ(kMessage, message);
+ EXPECT_EQ(kServiceContext, service_context);
+ EXPECT_EQ(kSchedulingHash, scheduling_hash);
+ }
+}
+
+} // namespace
+} // namespace syncer
diff --git a/sync/notifier/state_writer.h b/sync/notifier/state_writer.h
new file mode 100644
index 0000000..d008fee
--- /dev/null
+++ b/sync/notifier/state_writer.h
@@ -0,0 +1,25 @@
+// Copyright 2014 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.
+//
+// Simple interface for something that persists state.
+
+#ifndef SYNC_NOTIFIER_STATE_WRITER_H_
+#define SYNC_NOTIFIER_STATE_WRITER_H_
+
+#include <string>
+
+#include "sync/base/sync_export.h"
+
+namespace syncer {
+
+class SYNC_EXPORT_PRIVATE StateWriter {
+ public:
+ virtual ~StateWriter() {}
+
+ virtual void WriteState(const std::string& state) = 0;
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_STATE_WRITER_H_
diff --git a/sync/notifier/sync_invalidation_listener.cc b/sync/notifier/sync_invalidation_listener.cc
new file mode 100644
index 0000000..ddf4233
--- /dev/null
+++ b/sync/notifier/sync_invalidation_listener.cc
@@ -0,0 +1,443 @@
+// Copyright 2014 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/notifier/sync_invalidation_listener.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/tracked_objects.h"
+#include "google/cacheinvalidation/include/invalidation-client.h"
+#include "google/cacheinvalidation/include/types.h"
+#include "jingle/notifier/listener/push_client.h"
+#include "sync/notifier/invalidation_util.h"
+#include "sync/notifier/object_id_invalidation_map.h"
+#include "sync/notifier/registration_manager.h"
+
+namespace {
+
+const char kApplicationName[] = "chrome-sync";
+
+} // namespace
+
+namespace syncer {
+
+SyncInvalidationListener::Delegate::~Delegate() {}
+
+SyncInvalidationListener::SyncInvalidationListener(
+ scoped_ptr<SyncNetworkChannel> network_channel)
+ : sync_network_channel_(network_channel.Pass()),
+ sync_system_resources_(sync_network_channel_.get(), this),
+ delegate_(NULL),
+ ticl_state_(DEFAULT_INVALIDATION_ERROR),
+ push_client_state_(DEFAULT_INVALIDATION_ERROR),
+ weak_ptr_factory_(this) {
+ DCHECK(CalledOnValidThread());
+ sync_network_channel_->AddObserver(this);
+}
+
+SyncInvalidationListener::~SyncInvalidationListener() {
+ DCHECK(CalledOnValidThread());
+ sync_network_channel_->RemoveObserver(this);
+ Stop();
+ DCHECK(!delegate_);
+}
+
+void SyncInvalidationListener::Start(
+ const CreateInvalidationClientCallback&
+ create_invalidation_client_callback,
+ const std::string& client_id, const std::string& client_info,
+ const std::string& invalidation_bootstrap_data,
+ const UnackedInvalidationsMap& initial_unacked_invalidations,
+ const WeakHandle<InvalidationStateTracker>& invalidation_state_tracker,
+ Delegate* delegate) {
+ DCHECK(CalledOnValidThread());
+ Stop();
+
+ sync_system_resources_.set_platform(client_info);
+ sync_system_resources_.Start();
+
+ // The Storage resource is implemented as a write-through cache. We populate
+ // it with the initial state on startup, so subsequent writes go to disk and
+ // update the in-memory cache, while reads just return the cached state.
+ sync_system_resources_.storage()->SetInitialState(
+ invalidation_bootstrap_data);
+
+ unacked_invalidations_map_ = initial_unacked_invalidations;
+ invalidation_state_tracker_ = invalidation_state_tracker;
+ DCHECK(invalidation_state_tracker_.IsInitialized());
+
+ DCHECK(!delegate_);
+ DCHECK(delegate);
+ delegate_ = delegate;
+
+ invalidation_client_.reset(create_invalidation_client_callback.Run(
+ &sync_system_resources_,
+ sync_network_channel_->GetInvalidationClientType(),
+ client_id,
+ kApplicationName,
+ this));
+ invalidation_client_->Start();
+
+ registration_manager_.reset(
+ new RegistrationManager(invalidation_client_.get()));
+}
+
+void SyncInvalidationListener::UpdateCredentials(
+ const std::string& email, const std::string& token) {
+ DCHECK(CalledOnValidThread());
+ sync_network_channel_->UpdateCredentials(email, token);
+}
+
+void SyncInvalidationListener::UpdateRegisteredIds(const ObjectIdSet& ids) {
+ DCHECK(CalledOnValidThread());
+ registered_ids_ = ids;
+ // |ticl_state_| can go to INVALIDATIONS_ENABLED even without a
+ // working XMPP connection (as observed by us), so check it instead
+ // of GetState() (see http://crbug.com/139424).
+ if (ticl_state_ == INVALIDATIONS_ENABLED && registration_manager_) {
+ DoRegistrationUpdate();
+ }
+}
+
+void SyncInvalidationListener::Ready(
+ invalidation::InvalidationClient* client) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(client, invalidation_client_.get());
+ ticl_state_ = INVALIDATIONS_ENABLED;
+ EmitStateChange();
+ DoRegistrationUpdate();
+}
+
+void SyncInvalidationListener::Invalidate(
+ invalidation::InvalidationClient* client,
+ const invalidation::Invalidation& invalidation,
+ const invalidation::AckHandle& ack_handle) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(client, invalidation_client_.get());
+ client->Acknowledge(ack_handle);
+
+ const invalidation::ObjectId& id = invalidation.object_id();
+
+ std::string payload;
+ // payload() CHECK()'s has_payload(), so we must check it ourselves first.
+ if (invalidation.has_payload())
+ payload = invalidation.payload();
+
+ DVLOG(2) << "Received invalidation with version " << invalidation.version()
+ << " for " << ObjectIdToString(id);
+
+ ObjectIdInvalidationMap invalidations;
+ Invalidation inv = Invalidation::Init(id, invalidation.version(), payload);
+ inv.set_ack_handler(GetThisAsAckHandler());
+ invalidations.Insert(inv);
+
+ DispatchInvalidations(invalidations);
+}
+
+void SyncInvalidationListener::InvalidateUnknownVersion(
+ invalidation::InvalidationClient* client,
+ const invalidation::ObjectId& object_id,
+ const invalidation::AckHandle& ack_handle) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(client, invalidation_client_.get());
+ DVLOG(1) << "InvalidateUnknownVersion";
+ client->Acknowledge(ack_handle);
+
+ ObjectIdInvalidationMap invalidations;
+ Invalidation unknown_version = Invalidation::InitUnknownVersion(object_id);
+ unknown_version.set_ack_handler(GetThisAsAckHandler());
+ invalidations.Insert(unknown_version);
+
+ DispatchInvalidations(invalidations);
+}
+
+// This should behave as if we got an invalidation with version
+// UNKNOWN_OBJECT_VERSION for all known data types.
+void SyncInvalidationListener::InvalidateAll(
+ invalidation::InvalidationClient* client,
+ const invalidation::AckHandle& ack_handle) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(client, invalidation_client_.get());
+ DVLOG(1) << "InvalidateAll";
+ client->Acknowledge(ack_handle);
+
+ ObjectIdInvalidationMap invalidations;
+ for (ObjectIdSet::iterator it = registered_ids_.begin();
+ it != registered_ids_.end(); ++it) {
+ Invalidation unknown_version = Invalidation::InitUnknownVersion(*it);
+ unknown_version.set_ack_handler(GetThisAsAckHandler());
+ invalidations.Insert(unknown_version);
+ }
+
+ DispatchInvalidations(invalidations);
+}
+
+// If a handler is registered, emit right away. Otherwise, save it for later.
+void SyncInvalidationListener::DispatchInvalidations(
+ const ObjectIdInvalidationMap& invalidations) {
+ DCHECK(CalledOnValidThread());
+
+ ObjectIdInvalidationMap to_save = invalidations;
+ ObjectIdInvalidationMap to_emit =
+ invalidations.GetSubsetWithObjectIds(registered_ids_);
+
+ SaveInvalidations(to_save);
+ EmitSavedInvalidations(to_emit);
+}
+
+void SyncInvalidationListener::SaveInvalidations(
+ const ObjectIdInvalidationMap& to_save) {
+ ObjectIdSet objects_to_save = to_save.GetObjectIds();
+ for (ObjectIdSet::const_iterator it = objects_to_save.begin();
+ it != objects_to_save.end(); ++it) {
+ UnackedInvalidationsMap::iterator lookup =
+ unacked_invalidations_map_.find(*it);
+ if (lookup == unacked_invalidations_map_.end()) {
+ lookup = unacked_invalidations_map_.insert(
+ std::make_pair(*it, UnackedInvalidationSet(*it))).first;
+ }
+ lookup->second.AddSet(to_save.ForObject(*it));
+ }
+
+ invalidation_state_tracker_.Call(
+ FROM_HERE,
+ &InvalidationStateTracker::SetSavedInvalidations,
+ unacked_invalidations_map_);
+}
+
+void SyncInvalidationListener::EmitSavedInvalidations(
+ const ObjectIdInvalidationMap& to_emit) {
+ DVLOG(2) << "Emitting invalidations: " << to_emit.ToString();
+ delegate_->OnInvalidate(to_emit);
+}
+
+void SyncInvalidationListener::InformRegistrationStatus(
+ invalidation::InvalidationClient* client,
+ const invalidation::ObjectId& object_id,
+ InvalidationListener::RegistrationState new_state) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(client, invalidation_client_.get());
+ DVLOG(1) << "InformRegistrationStatus: "
+ << ObjectIdToString(object_id) << " " << new_state;
+
+ if (new_state != InvalidationListener::REGISTERED) {
+ // Let |registration_manager_| handle the registration backoff policy.
+ registration_manager_->MarkRegistrationLost(object_id);
+ }
+}
+
+void SyncInvalidationListener::InformRegistrationFailure(
+ invalidation::InvalidationClient* client,
+ const invalidation::ObjectId& object_id,
+ bool is_transient,
+ const std::string& error_message) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(client, invalidation_client_.get());
+ DVLOG(1) << "InformRegistrationFailure: "
+ << ObjectIdToString(object_id)
+ << "is_transient=" << is_transient
+ << ", message=" << error_message;
+
+ if (is_transient) {
+ // We don't care about |unknown_hint|; we let
+ // |registration_manager_| handle the registration backoff policy.
+ registration_manager_->MarkRegistrationLost(object_id);
+ } else {
+ // Non-transient failures require an action to resolve. This could happen
+ // because:
+ // - the server doesn't yet recognize the data type, which could happen for
+ // brand-new data types.
+ // - the user has changed his password and hasn't updated it yet locally.
+ // Either way, block future registration attempts for |object_id|. However,
+ // we don't forget any saved invalidation state since we may use it once the
+ // error is addressed.
+ registration_manager_->DisableId(object_id);
+ }
+}
+
+void SyncInvalidationListener::ReissueRegistrations(
+ invalidation::InvalidationClient* client,
+ const std::string& prefix,
+ int prefix_length) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(client, invalidation_client_.get());
+ DVLOG(1) << "AllRegistrationsLost";
+ registration_manager_->MarkAllRegistrationsLost();
+}
+
+void SyncInvalidationListener::InformError(
+ invalidation::InvalidationClient* client,
+ const invalidation::ErrorInfo& error_info) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(client, invalidation_client_.get());
+ LOG(ERROR) << "Ticl error " << error_info.error_reason() << ": "
+ << error_info.error_message()
+ << " (transient = " << error_info.is_transient() << ")";
+ if (error_info.error_reason() == invalidation::ErrorReason::AUTH_FAILURE) {
+ ticl_state_ = INVALIDATION_CREDENTIALS_REJECTED;
+ } else {
+ ticl_state_ = TRANSIENT_INVALIDATION_ERROR;
+ }
+ EmitStateChange();
+}
+
+void SyncInvalidationListener::Acknowledge(
+ const invalidation::ObjectId& id,
+ const syncer::AckHandle& handle) {
+ UnackedInvalidationsMap::iterator lookup =
+ unacked_invalidations_map_.find(id);
+ if (lookup == unacked_invalidations_map_.end()) {
+ DLOG(WARNING) << "Received acknowledgement for untracked object ID";
+ return;
+ }
+ lookup->second.Acknowledge(handle);
+ invalidation_state_tracker_.Call(
+ FROM_HERE,
+ &InvalidationStateTracker::SetSavedInvalidations,
+ unacked_invalidations_map_);
+}
+
+void SyncInvalidationListener::Drop(
+ const invalidation::ObjectId& id,
+ const syncer::AckHandle& handle) {
+ UnackedInvalidationsMap::iterator lookup =
+ unacked_invalidations_map_.find(id);
+ if (lookup == unacked_invalidations_map_.end()) {
+ DLOG(WARNING) << "Received drop for untracked object ID";
+ return;
+ }
+ lookup->second.Drop(handle);
+ invalidation_state_tracker_.Call(
+ FROM_HERE,
+ &InvalidationStateTracker::SetSavedInvalidations,
+ unacked_invalidations_map_);
+}
+
+void SyncInvalidationListener::WriteState(const std::string& state) {
+ DCHECK(CalledOnValidThread());
+ DVLOG(1) << "WriteState";
+ invalidation_state_tracker_.Call(
+ FROM_HERE, &InvalidationStateTracker::SetBootstrapData, state);
+}
+
+void SyncInvalidationListener::DoRegistrationUpdate() {
+ DCHECK(CalledOnValidThread());
+ const ObjectIdSet& unregistered_ids =
+ registration_manager_->UpdateRegisteredIds(registered_ids_);
+ for (ObjectIdSet::iterator it = unregistered_ids.begin();
+ it != unregistered_ids.end(); ++it) {
+ unacked_invalidations_map_.erase(*it);
+ }
+ invalidation_state_tracker_.Call(
+ FROM_HERE,
+ &InvalidationStateTracker::SetSavedInvalidations,
+ unacked_invalidations_map_);
+
+ ObjectIdInvalidationMap object_id_invalidation_map;
+ for (UnackedInvalidationsMap::iterator map_it =
+ unacked_invalidations_map_.begin();
+ map_it != unacked_invalidations_map_.end(); ++map_it) {
+ if (registered_ids_.find(map_it->first) == registered_ids_.end()) {
+ continue;
+ }
+ map_it->second.ExportInvalidations(
+ GetThisAsAckHandler(),
+ &object_id_invalidation_map);
+ }
+
+ // There's no need to run these through DispatchInvalidations(); they've
+ // already been saved to storage (that's where we found them) so all we need
+ // to do now is emit them.
+ EmitSavedInvalidations(object_id_invalidation_map);
+}
+
+void SyncInvalidationListener::RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const {
+ DCHECK(CalledOnValidThread());
+ sync_network_channel_->RequestDetailedStatus(callback);
+ callback.Run(*CollectDebugData());
+}
+
+scoped_ptr<base::DictionaryValue>
+SyncInvalidationListener::CollectDebugData() const {
+ scoped_ptr<base::DictionaryValue> return_value(new base::DictionaryValue());
+ return_value->SetString(
+ "SyncInvalidationListener.PushClientState",
+ std::string(InvalidatorStateToString(push_client_state_)));
+ return_value->SetString("SyncInvalidationListener.TiclState",
+ std::string(InvalidatorStateToString(ticl_state_)));
+ scoped_ptr<base::DictionaryValue> unacked_map(new base::DictionaryValue());
+ for (UnackedInvalidationsMap::const_iterator it =
+ unacked_invalidations_map_.begin();
+ it != unacked_invalidations_map_.end();
+ ++it) {
+ unacked_map->Set((it->first).name(), (it->second).ToValue().release());
+ }
+ return_value->Set("SyncInvalidationListener.UnackedInvalidationsMap",
+ unacked_map.release());
+ return return_value.Pass();
+}
+
+void SyncInvalidationListener::StopForTest() {
+ DCHECK(CalledOnValidThread());
+ Stop();
+}
+
+void SyncInvalidationListener::Stop() {
+ DCHECK(CalledOnValidThread());
+ if (!invalidation_client_) {
+ return;
+ }
+
+ registration_manager_.reset();
+ sync_system_resources_.Stop();
+ invalidation_client_->Stop();
+
+ invalidation_client_.reset();
+ delegate_ = NULL;
+
+ ticl_state_ = DEFAULT_INVALIDATION_ERROR;
+ push_client_state_ = DEFAULT_INVALIDATION_ERROR;
+}
+
+InvalidatorState SyncInvalidationListener::GetState() const {
+ DCHECK(CalledOnValidThread());
+ if (ticl_state_ == INVALIDATION_CREDENTIALS_REJECTED ||
+ push_client_state_ == INVALIDATION_CREDENTIALS_REJECTED) {
+ // If either the ticl or the push client rejected our credentials,
+ // return INVALIDATION_CREDENTIALS_REJECTED.
+ return INVALIDATION_CREDENTIALS_REJECTED;
+ }
+ if (ticl_state_ == INVALIDATIONS_ENABLED &&
+ push_client_state_ == INVALIDATIONS_ENABLED) {
+ // If the ticl is ready and the push client notifications are
+ // enabled, return INVALIDATIONS_ENABLED.
+ return INVALIDATIONS_ENABLED;
+ }
+ // Otherwise, we have a transient error.
+ return TRANSIENT_INVALIDATION_ERROR;
+}
+
+void SyncInvalidationListener::EmitStateChange() {
+ DCHECK(CalledOnValidThread());
+ delegate_->OnInvalidatorStateChange(GetState());
+}
+
+WeakHandle<AckHandler> SyncInvalidationListener::GetThisAsAckHandler() {
+ DCHECK(CalledOnValidThread());
+ return WeakHandle<AckHandler>(weak_ptr_factory_.GetWeakPtr());
+}
+
+void SyncInvalidationListener::OnNetworkChannelStateChanged(
+ InvalidatorState invalidator_state) {
+ DCHECK(CalledOnValidThread());
+ push_client_state_ = invalidator_state;
+ EmitStateChange();
+}
+
+} // namespace syncer
diff --git a/sync/notifier/sync_invalidation_listener.h b/sync/notifier/sync_invalidation_listener.h
new file mode 100644
index 0000000..93bc0a5
--- /dev/null
+++ b/sync/notifier/sync_invalidation_listener.h
@@ -0,0 +1,196 @@
+// Copyright 2014 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.
+//
+// A simple wrapper around invalidation::InvalidationClient that
+// handles all the startup/shutdown details and hookups.
+
+#ifndef SYNC_NOTIFIER_SYNC_INVALIDATION_LISTENER_H_
+#define SYNC_NOTIFIER_SYNC_INVALIDATION_LISTENER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "google/cacheinvalidation/include/invalidation-listener.h"
+#include "sync/base/sync_export.h"
+#include "sync/internal_api/public/util/weak_handle.h"
+#include "sync/notifier/ack_handler.h"
+#include "sync/notifier/invalidation_state_tracker.h"
+#include "sync/notifier/invalidator_state.h"
+#include "sync/notifier/state_writer.h"
+#include "sync/notifier/sync_system_resources.h"
+#include "sync/notifier/unacked_invalidation_set.h"
+
+namespace buzz {
+class XmppTaskParentInterface;
+} // namespace buzz
+
+namespace notifier {
+class PushClient;
+} // namespace notifier
+
+namespace syncer {
+
+class ObjectIdInvalidationMap;
+class RegistrationManager;
+
+// SyncInvalidationListener is not thread-safe and lives on the sync
+// thread.
+class SYNC_EXPORT_PRIVATE SyncInvalidationListener
+ : public NON_EXPORTED_BASE(invalidation::InvalidationListener),
+ public StateWriter,
+ public SyncNetworkChannel::Observer,
+ public AckHandler,
+ public base::NonThreadSafe {
+ public:
+ typedef base::Callback<invalidation::InvalidationClient*(
+ invalidation::SystemResources*,
+ int,
+ const invalidation::string&,
+ const invalidation::string&,
+ invalidation::InvalidationListener*)> CreateInvalidationClientCallback;
+
+ class SYNC_EXPORT_PRIVATE Delegate {
+ public:
+ virtual ~Delegate();
+
+ virtual void OnInvalidate(
+ const ObjectIdInvalidationMap& invalidations) = 0;
+
+ virtual void OnInvalidatorStateChange(InvalidatorState state) = 0;
+ };
+
+ explicit SyncInvalidationListener(
+ scoped_ptr<SyncNetworkChannel> network_channel);
+
+ // Calls Stop().
+ virtual ~SyncInvalidationListener();
+
+ // Does not take ownership of |delegate| or |state_writer|.
+ // |invalidation_state_tracker| must be initialized.
+ void Start(
+ const CreateInvalidationClientCallback&
+ create_invalidation_client_callback,
+ const std::string& client_id, const std::string& client_info,
+ const std::string& invalidation_bootstrap_data,
+ const UnackedInvalidationsMap& initial_object_states,
+ const WeakHandle<InvalidationStateTracker>& invalidation_state_tracker,
+ Delegate* delegate);
+
+ void UpdateCredentials(const std::string& email, const std::string& token);
+
+ // Update the set of object IDs that we're interested in getting
+ // notifications for. May be called at any time.
+ void UpdateRegisteredIds(const ObjectIdSet& ids);
+
+ // invalidation::InvalidationListener implementation.
+ virtual void Ready(
+ invalidation::InvalidationClient* client) OVERRIDE;
+ virtual void Invalidate(
+ invalidation::InvalidationClient* client,
+ const invalidation::Invalidation& invalidation,
+ const invalidation::AckHandle& ack_handle) OVERRIDE;
+ virtual void InvalidateUnknownVersion(
+ invalidation::InvalidationClient* client,
+ const invalidation::ObjectId& object_id,
+ const invalidation::AckHandle& ack_handle) OVERRIDE;
+ virtual void InvalidateAll(
+ invalidation::InvalidationClient* client,
+ const invalidation::AckHandle& ack_handle) OVERRIDE;
+ virtual void InformRegistrationStatus(
+ invalidation::InvalidationClient* client,
+ const invalidation::ObjectId& object_id,
+ invalidation::InvalidationListener::RegistrationState reg_state) OVERRIDE;
+ virtual void InformRegistrationFailure(
+ invalidation::InvalidationClient* client,
+ const invalidation::ObjectId& object_id,
+ bool is_transient,
+ const std::string& error_message) OVERRIDE;
+ virtual void ReissueRegistrations(
+ invalidation::InvalidationClient* client,
+ const std::string& prefix,
+ int prefix_length) OVERRIDE;
+ virtual void InformError(
+ invalidation::InvalidationClient* client,
+ const invalidation::ErrorInfo& error_info) OVERRIDE;
+
+ // AckHandler implementation.
+ virtual void Acknowledge(
+ const invalidation::ObjectId& id,
+ const syncer::AckHandle& handle) OVERRIDE;
+ virtual void Drop(
+ const invalidation::ObjectId& id,
+ const syncer::AckHandle& handle) OVERRIDE;
+
+ // StateWriter implementation.
+ virtual void WriteState(const std::string& state) OVERRIDE;
+
+ // SyncNetworkChannel::Observer implementation.
+ virtual void OnNetworkChannelStateChanged(
+ InvalidatorState invalidator_state) OVERRIDE;
+
+ void DoRegistrationUpdate();
+
+ void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) const;
+
+ void StopForTest();
+
+ private:
+ void Stop();
+
+ InvalidatorState GetState() const;
+
+ void EmitStateChange();
+
+ // Sends invalidations to their appropriate destination.
+ //
+ // If there are no observers registered for them, they will be saved for
+ // later.
+ //
+ // If there are observers registered, they will be saved (to make sure we
+ // don't drop them until they've been acted on) and emitted to the observers.
+ void DispatchInvalidations(const ObjectIdInvalidationMap& invalidations);
+
+ // Saves invalidations.
+ //
+ // This call isn't synchronous so we can't guarantee these invalidations will
+ // be safely on disk by the end of the call, but it should ensure that the
+ // data makes it to disk eventually.
+ void SaveInvalidations(const ObjectIdInvalidationMap& to_save);
+
+ // Emits previously saved invalidations to their registered observers.
+ void EmitSavedInvalidations(const ObjectIdInvalidationMap& to_emit);
+
+ // Generate a Dictionary with all the debugging information.
+ scoped_ptr<base::DictionaryValue> CollectDebugData() const;
+
+ WeakHandle<AckHandler> GetThisAsAckHandler();
+
+ scoped_ptr<SyncNetworkChannel> sync_network_channel_;
+ SyncSystemResources sync_system_resources_;
+ UnackedInvalidationsMap unacked_invalidations_map_;
+ WeakHandle<InvalidationStateTracker> invalidation_state_tracker_;
+ Delegate* delegate_;
+ scoped_ptr<invalidation::InvalidationClient> invalidation_client_;
+ scoped_ptr<RegistrationManager> registration_manager_;
+ // Stored to pass to |registration_manager_| on start.
+ ObjectIdSet registered_ids_;
+
+ // The states of the ticl and the push client.
+ InvalidatorState ticl_state_;
+ InvalidatorState push_client_state_;
+
+ base::WeakPtrFactory<SyncInvalidationListener> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncInvalidationListener);
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_SYNC_INVALIDATION_LISTENER_H_
diff --git a/sync/notifier/sync_invalidation_listener_unittest.cc b/sync/notifier/sync_invalidation_listener_unittest.cc
new file mode 100644
index 0000000..dbf5fd3
--- /dev/null
+++ b/sync/notifier/sync_invalidation_listener_unittest.cc
@@ -0,0 +1,1129 @@
+// Copyright 2014 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 <cstddef>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "google/cacheinvalidation/include/invalidation-client.h"
+#include "google/cacheinvalidation/include/types.h"
+#include "jingle/notifier/listener/fake_push_client.h"
+#include "sync/internal_api/public/util/weak_handle.h"
+#include "sync/notifier/dropped_invalidation_tracker.h"
+#include "sync/notifier/fake_invalidation_state_tracker.h"
+#include "sync/notifier/invalidation_util.h"
+#include "sync/notifier/object_id_invalidation_map.h"
+#include "sync/notifier/push_client_channel.h"
+#include "sync/notifier/sync_invalidation_listener.h"
+#include "sync/notifier/unacked_invalidation_set_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+using invalidation::AckHandle;
+using invalidation::ObjectId;
+
+const char kClientId[] = "client_id";
+const char kClientInfo[] = "client_info";
+
+const char kState[] = "state";
+const char kNewState[] = "new_state";
+
+const char kPayload1[] = "payload1";
+const char kPayload2[] = "payload2";
+
+const int64 kVersion1 = 1LL;
+const int64 kVersion2 = 2LL;
+
+const int kChromeSyncSourceId = 1004;
+
+struct AckHandleLessThan {
+ bool operator()(const AckHandle& lhs, const AckHandle& rhs) const {
+ return lhs.handle_data() < rhs.handle_data();
+ }
+};
+
+typedef std::set<AckHandle, AckHandleLessThan> AckHandleSet;
+
+// Fake invalidation::InvalidationClient implementation that keeps
+// track of registered IDs and acked handles.
+class FakeInvalidationClient : public invalidation::InvalidationClient {
+ public:
+ FakeInvalidationClient() : started_(false) {}
+ virtual ~FakeInvalidationClient() {}
+
+ const ObjectIdSet& GetRegisteredIds() const {
+ return registered_ids_;
+ }
+
+ void ClearAckedHandles() {
+ acked_handles_.clear();
+ }
+
+ bool IsAckedHandle(const AckHandle& ack_handle) const {
+ return (acked_handles_.find(ack_handle) != acked_handles_.end());
+ }
+
+ // invalidation::InvalidationClient implementation.
+
+ virtual void Start() OVERRIDE {
+ started_ = true;
+ }
+
+ virtual void Stop() OVERRIDE {
+ started_ = false;
+ }
+
+ virtual void Register(const ObjectId& object_id) OVERRIDE {
+ if (!started_) {
+ ADD_FAILURE();
+ return;
+ }
+ registered_ids_.insert(object_id);
+ }
+
+ virtual void Register(
+ const invalidation::vector<ObjectId>& object_ids) OVERRIDE {
+ if (!started_) {
+ ADD_FAILURE();
+ return;
+ }
+ registered_ids_.insert(object_ids.begin(), object_ids.end());
+ }
+
+ virtual void Unregister(const ObjectId& object_id) OVERRIDE {
+ if (!started_) {
+ ADD_FAILURE();
+ return;
+ }
+ registered_ids_.erase(object_id);
+ }
+
+ virtual void Unregister(
+ const invalidation::vector<ObjectId>& object_ids) OVERRIDE {
+ if (!started_) {
+ ADD_FAILURE();
+ return;
+ }
+ for (invalidation::vector<ObjectId>::const_iterator
+ it = object_ids.begin(); it != object_ids.end(); ++it) {
+ registered_ids_.erase(*it);
+ }
+ }
+
+ virtual void Acknowledge(const AckHandle& ack_handle) OVERRIDE {
+ if (!started_) {
+ ADD_FAILURE();
+ return;
+ }
+ acked_handles_.insert(ack_handle);
+ }
+
+ private:
+ bool started_;
+ ObjectIdSet registered_ids_;
+ AckHandleSet acked_handles_;
+};
+
+// Fake delegate tkat keeps track of invalidation counts, payloads,
+// and state.
+class FakeDelegate : public SyncInvalidationListener::Delegate {
+ public:
+ explicit FakeDelegate(SyncInvalidationListener* listener)
+ : state_(TRANSIENT_INVALIDATION_ERROR),
+ drop_handlers_deleter_(&drop_handlers_) {}
+ virtual ~FakeDelegate() {}
+
+ size_t GetInvalidationCount(const ObjectId& id) const {
+ Map::const_iterator it = invalidations_.find(id);
+ if (it == invalidations_.end()) {
+ return 0;
+ } else {
+ return it->second.size();
+ }
+ }
+
+ int64 GetVersion(const ObjectId& id) const {
+ Map::const_iterator it = invalidations_.find(id);
+ if (it == invalidations_.end()) {
+ ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id);
+ return 0;
+ } else {
+ return it->second.back().version();
+ }
+ }
+
+ std::string GetPayload(const ObjectId& id) const {
+ Map::const_iterator it = invalidations_.find(id);
+ if (it == invalidations_.end()) {
+ ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id);
+ return 0;
+ } else {
+ return it->second.back().payload();
+ }
+ }
+
+ bool IsUnknownVersion(const ObjectId& id) const {
+ Map::const_iterator it = invalidations_.find(id);
+ if (it == invalidations_.end()) {
+ ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id);
+ return false;
+ } else {
+ return it->second.back().is_unknown_version();
+ }
+ }
+
+ bool StartsWithUnknownVersion(const ObjectId& id) const {
+ Map::const_iterator it = invalidations_.find(id);
+ if (it == invalidations_.end()) {
+ ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id);
+ return false;
+ } else {
+ return it->second.front().is_unknown_version();
+ }
+ }
+
+ InvalidatorState GetInvalidatorState() const {
+ return state_;
+ }
+
+ DroppedInvalidationTracker* GetDropTrackerForObject(const ObjectId& id) {
+ DropHandlers::iterator it = drop_handlers_.find(id);
+ if (it == drop_handlers_.end()) {
+ drop_handlers_.insert(
+ std::make_pair(id, new DroppedInvalidationTracker(id)));
+ return drop_handlers_.find(id)->second;
+ } else {
+ return it->second;
+ }
+ }
+
+ void AcknowledgeNthInvalidation(const ObjectId& id, size_t n) {
+ List& list = invalidations_[id];
+ List::iterator it = list.begin() + n;
+ it->Acknowledge();
+ }
+
+ void AcknowledgeAll(const ObjectId& id) {
+ List& list = invalidations_[id];
+ for (List::iterator it = list.begin(); it != list.end(); ++it) {
+ it->Acknowledge();
+ }
+ }
+
+ void DropNthInvalidation(const ObjectId& id, size_t n) {
+ DroppedInvalidationTracker* drop_tracker = GetDropTrackerForObject(id);
+ List& list = invalidations_[id];
+ List::iterator it = list.begin() + n;
+ it->Drop(drop_tracker);
+ }
+
+ void RecoverFromDropEvent(const ObjectId& id) {
+ DroppedInvalidationTracker* drop_tracker = GetDropTrackerForObject(id);
+ drop_tracker->RecordRecoveryFromDropEvent();
+ }
+
+ // SyncInvalidationListener::Delegate implementation.
+ virtual void OnInvalidate(
+ const ObjectIdInvalidationMap& invalidation_map) OVERRIDE {
+ ObjectIdSet ids = invalidation_map.GetObjectIds();
+ for (ObjectIdSet::iterator it = ids.begin(); it != ids.end(); ++it) {
+ const SingleObjectInvalidationSet& incoming =
+ invalidation_map.ForObject(*it);
+ List& list = invalidations_[*it];
+ list.insert(list.end(), incoming.begin(), incoming.end());
+ }
+ }
+
+ virtual void OnInvalidatorStateChange(InvalidatorState state) OVERRIDE {
+ state_ = state;
+ }
+
+ private:
+ typedef std::vector<Invalidation> List;
+ typedef std::map<ObjectId, List, ObjectIdLessThan> Map;
+ typedef std::map<ObjectId,
+ DroppedInvalidationTracker*,
+ ObjectIdLessThan> DropHandlers;
+
+ Map invalidations_;
+ InvalidatorState state_;
+ DropHandlers drop_handlers_;
+ STLValueDeleter<DropHandlers> drop_handlers_deleter_;
+};
+
+invalidation::InvalidationClient* CreateFakeInvalidationClient(
+ FakeInvalidationClient** fake_invalidation_client,
+ invalidation::SystemResources* resources,
+ int client_type,
+ const invalidation::string& client_name,
+ const invalidation::string& application_name,
+ invalidation::InvalidationListener* listener) {
+ *fake_invalidation_client = new FakeInvalidationClient();
+ return *fake_invalidation_client;
+}
+
+class SyncInvalidationListenerTest : public testing::Test {
+ protected:
+ SyncInvalidationListenerTest()
+ : kBookmarksId_(kChromeSyncSourceId, "BOOKMARK"),
+ kPreferencesId_(kChromeSyncSourceId, "PREFERENCE"),
+ kExtensionsId_(kChromeSyncSourceId, "EXTENSION"),
+ kAppsId_(kChromeSyncSourceId, "APP"),
+ fake_push_client_(new notifier::FakePushClient()),
+ fake_invalidation_client_(NULL),
+ listener_(scoped_ptr<SyncNetworkChannel>(new PushClientChannel(
+ scoped_ptr<notifier::PushClient>(fake_push_client_)))),
+ fake_delegate_(&listener_) {}
+
+ virtual void SetUp() {
+ StartClient();
+
+ registered_ids_.insert(kBookmarksId_);
+ registered_ids_.insert(kPreferencesId_);
+ listener_.UpdateRegisteredIds(registered_ids_);
+ }
+
+ virtual void TearDown() {
+ StopClient();
+ }
+
+ // Restart client without re-registering IDs.
+ void RestartClient() {
+ StopClient();
+ StartClient();
+ }
+
+ void StartClient() {
+ fake_invalidation_client_ = NULL;
+ listener_.Start(base::Bind(&CreateFakeInvalidationClient,
+ &fake_invalidation_client_),
+ kClientId, kClientInfo, kState,
+ fake_tracker_.GetSavedInvalidations(),
+ MakeWeakHandle(fake_tracker_.AsWeakPtr()),
+ &fake_delegate_);
+ DCHECK(fake_invalidation_client_);
+ }
+
+ void StopClient() {
+ // listener_.StopForTest() stops the invalidation scheduler, which
+ // deletes any pending tasks without running them. Some tasks
+ // "run and delete" another task, so they must be run in order to
+ // avoid leaking the inner task. listener_.StopForTest() does not
+ // schedule any tasks, so it's both necessary and sufficient to
+ // drain the task queue before calling it.
+ FlushPendingWrites();
+ fake_invalidation_client_ = NULL;
+ listener_.StopForTest();
+ }
+
+ size_t GetInvalidationCount(const ObjectId& id) const {
+ return fake_delegate_.GetInvalidationCount(id);
+ }
+
+ int64 GetVersion(const ObjectId& id) const {
+ return fake_delegate_.GetVersion(id);
+ }
+
+ std::string GetPayload(const ObjectId& id) const {
+ return fake_delegate_.GetPayload(id);
+ }
+
+ bool IsUnknownVersion(const ObjectId& id) const {
+ return fake_delegate_.IsUnknownVersion(id);
+ }
+
+ bool StartsWithUnknownVersion(const ObjectId& id) const {
+ return fake_delegate_.StartsWithUnknownVersion(id);
+ }
+
+ void AcknowledgeNthInvalidation(const ObjectId& id, size_t n) {
+ fake_delegate_.AcknowledgeNthInvalidation(id, n);
+ }
+
+ void DropNthInvalidation(const ObjectId& id, size_t n) {
+ return fake_delegate_.DropNthInvalidation(id, n);
+ }
+
+ void RecoverFromDropEvent(const ObjectId& id) {
+ return fake_delegate_.RecoverFromDropEvent(id);
+ }
+
+ void AcknowledgeAll(const ObjectId& id) {
+ fake_delegate_.AcknowledgeAll(id);
+ }
+
+ InvalidatorState GetInvalidatorState() const {
+ return fake_delegate_.GetInvalidatorState();
+ }
+
+ std::string GetInvalidatorClientId() const {
+ return fake_tracker_.GetInvalidatorClientId();
+ }
+
+ std::string GetBootstrapData() const {
+ return fake_tracker_.GetBootstrapData();
+ }
+
+ UnackedInvalidationsMap GetSavedInvalidations() {
+ // Allow any queued writes to go through first.
+ FlushPendingWrites();
+ return fake_tracker_.GetSavedInvalidations();
+ }
+
+ SingleObjectInvalidationSet GetSavedInvalidationsForType(const ObjectId& id) {
+ const UnackedInvalidationsMap& saved_state = GetSavedInvalidations();
+ UnackedInvalidationsMap::const_iterator it =
+ saved_state.find(kBookmarksId_);
+ if (it == saved_state.end()) {
+ ADD_FAILURE() << "No state saved for ID " << ObjectIdToString(id);
+ return SingleObjectInvalidationSet();
+ }
+ ObjectIdInvalidationMap map;
+ it->second.ExportInvalidations(WeakHandle<AckHandler>(), &map);
+ if (map.Empty()) {
+ return SingleObjectInvalidationSet();
+ } else {
+ return map.ForObject(id);
+ }
+ }
+
+ ObjectIdSet GetRegisteredIds() const {
+ return fake_invalidation_client_->GetRegisteredIds();
+ }
+
+ // |payload| can be NULL.
+ void FireInvalidate(const ObjectId& object_id,
+ int64 version, const char* payload) {
+ invalidation::Invalidation inv;
+ if (payload) {
+ inv = invalidation::Invalidation(object_id, version, payload);
+ } else {
+ inv = invalidation::Invalidation(object_id, version);
+ }
+ const AckHandle ack_handle("fakedata");
+ fake_invalidation_client_->ClearAckedHandles();
+ listener_.Invalidate(fake_invalidation_client_, inv, ack_handle);
+ EXPECT_TRUE(fake_invalidation_client_->IsAckedHandle(ack_handle));
+ }
+
+ // |payload| can be NULL, but not |type_name|.
+ void FireInvalidateUnknownVersion(const ObjectId& object_id) {
+ const AckHandle ack_handle("fakedata_unknown");
+ fake_invalidation_client_->ClearAckedHandles();
+ listener_.InvalidateUnknownVersion(fake_invalidation_client_,
+ object_id,
+ ack_handle);
+ EXPECT_TRUE(fake_invalidation_client_->IsAckedHandle(ack_handle));
+ }
+
+ void FireInvalidateAll() {
+ const AckHandle ack_handle("fakedata_all");
+ fake_invalidation_client_->ClearAckedHandles();
+ listener_.InvalidateAll(fake_invalidation_client_, ack_handle);
+ EXPECT_TRUE(fake_invalidation_client_->IsAckedHandle(ack_handle));
+ }
+
+ void WriteState(const std::string& new_state) {
+ listener_.WriteState(new_state);
+
+ // Pump message loop to trigger
+ // InvalidationStateTracker::WriteState().
+ FlushPendingWrites();
+ }
+
+ void FlushPendingWrites() {
+ message_loop_.RunUntilIdle();
+ }
+
+ void EnableNotifications() {
+ fake_push_client_->EnableNotifications();
+ }
+
+ void DisableNotifications(notifier::NotificationsDisabledReason reason) {
+ fake_push_client_->DisableNotifications(reason);
+ }
+
+ const ObjectId kBookmarksId_;
+ const ObjectId kPreferencesId_;
+ const ObjectId kExtensionsId_;
+ const ObjectId kAppsId_;
+
+ ObjectIdSet registered_ids_;
+
+ private:
+ base::MessageLoop message_loop_;
+ notifier::FakePushClient* const fake_push_client_;
+
+ protected:
+ // A derrived test needs direct access to this.
+ FakeInvalidationStateTracker fake_tracker_;
+
+ // Tests need to access these directly.
+ FakeInvalidationClient* fake_invalidation_client_;
+ SyncInvalidationListener listener_;
+
+ private:
+ FakeDelegate fake_delegate_;
+};
+
+// Write a new state to the client. It should propagate to the
+// tracker.
+TEST_F(SyncInvalidationListenerTest, WriteState) {
+ WriteState(kNewState);
+
+ EXPECT_EQ(kNewState, GetBootstrapData());
+}
+
+// Invalidation tests.
+
+// Fire an invalidation without a payload. It should be processed,
+// the payload should remain empty, and the version should be updated.
+TEST_F(SyncInvalidationListenerTest, InvalidateNoPayload) {
+ const ObjectId& id = kBookmarksId_;
+
+ FireInvalidate(id, kVersion1, NULL);
+
+ ASSERT_EQ(1U, GetInvalidationCount(id));
+ ASSERT_FALSE(IsUnknownVersion(id));
+ EXPECT_EQ(kVersion1, GetVersion(id));
+ EXPECT_EQ("", GetPayload(id));
+}
+
+// Fire an invalidation with an empty payload. It should be
+// processed, the payload should remain empty, and the version should
+// be updated.
+TEST_F(SyncInvalidationListenerTest, InvalidateEmptyPayload) {
+ const ObjectId& id = kBookmarksId_;
+
+ FireInvalidate(id, kVersion1, "");
+
+ ASSERT_EQ(1U, GetInvalidationCount(id));
+ ASSERT_FALSE(IsUnknownVersion(id));
+ EXPECT_EQ(kVersion1, GetVersion(id));
+ EXPECT_EQ("", GetPayload(id));
+}
+
+// Fire an invalidation with a payload. It should be processed, and
+// both the payload and the version should be updated.
+TEST_F(SyncInvalidationListenerTest, InvalidateWithPayload) {
+ const ObjectId& id = kPreferencesId_;
+
+ FireInvalidate(id, kVersion1, kPayload1);
+
+ ASSERT_EQ(1U, GetInvalidationCount(id));
+ ASSERT_FALSE(IsUnknownVersion(id));
+ EXPECT_EQ(kVersion1, GetVersion(id));
+ EXPECT_EQ(kPayload1, GetPayload(id));
+}
+
+// Fire ten invalidations in a row. All should be received.
+TEST_F(SyncInvalidationListenerTest, ManyInvalidations_NoDrop) {
+ const int kRepeatCount = 10;
+ const ObjectId& id = kPreferencesId_;
+ int64 initial_version = kVersion1;
+ for (int64 i = initial_version; i < initial_version + kRepeatCount; ++i) {
+ FireInvalidate(id, i, kPayload1);
+ }
+ ASSERT_EQ(static_cast<size_t>(kRepeatCount), GetInvalidationCount(id));
+ ASSERT_FALSE(IsUnknownVersion(id));
+ EXPECT_EQ(kPayload1, GetPayload(id));
+ EXPECT_EQ(initial_version + kRepeatCount - 1, GetVersion(id));
+}
+
+// Fire an invalidation for an unregistered object ID with a payload. It should
+// still be processed, and both the payload and the version should be updated.
+TEST_F(SyncInvalidationListenerTest, InvalidateBeforeRegistration_Simple) {
+ const ObjectId kUnregisteredId(kChromeSyncSourceId, "unregistered");
+ const ObjectId& id = kUnregisteredId;
+ ObjectIdSet ids;
+ ids.insert(id);
+
+ EXPECT_EQ(0U, GetInvalidationCount(id));
+
+ FireInvalidate(id, kVersion1, kPayload1);
+
+ ASSERT_EQ(0U, GetInvalidationCount(id));
+
+ EnableNotifications();
+ listener_.Ready(fake_invalidation_client_);
+ listener_.UpdateRegisteredIds(ids);
+
+ ASSERT_EQ(1U, GetInvalidationCount(id));
+ ASSERT_FALSE(IsUnknownVersion(id));
+ EXPECT_EQ(kVersion1, GetVersion(id));
+ EXPECT_EQ(kPayload1, GetPayload(id));
+}
+
+// Fire ten invalidations before an object registers. Some invalidations will
+// be dropped an replaced with an unknown version invalidation.
+TEST_F(SyncInvalidationListenerTest, InvalidateBeforeRegistration_Drop) {
+ const int kRepeatCount =
+ UnackedInvalidationSet::kMaxBufferedInvalidations + 1;
+ const ObjectId kUnregisteredId(kChromeSyncSourceId, "unregistered");
+ const ObjectId& id = kUnregisteredId;
+ ObjectIdSet ids;
+ ids.insert(id);
+
+ EXPECT_EQ(0U, GetInvalidationCount(id));
+
+ int64 initial_version = kVersion1;
+ for (int64 i = initial_version; i < initial_version + kRepeatCount; ++i) {
+ FireInvalidate(id, i, kPayload1);
+ }
+
+ EnableNotifications();
+ listener_.Ready(fake_invalidation_client_);
+ listener_.UpdateRegisteredIds(ids);
+
+ ASSERT_EQ(UnackedInvalidationSet::kMaxBufferedInvalidations,
+ GetInvalidationCount(id));
+ ASSERT_FALSE(IsUnknownVersion(id));
+ EXPECT_EQ(initial_version + kRepeatCount - 1, GetVersion(id));
+ EXPECT_EQ(kPayload1, GetPayload(id));
+ EXPECT_TRUE(StartsWithUnknownVersion(id));
+}
+
+// Fire an invalidation, then fire another one with a lower version. Both
+// should be received.
+TEST_F(SyncInvalidationListenerTest, InvalidateVersion) {
+ const ObjectId& id = kPreferencesId_;
+
+ FireInvalidate(id, kVersion2, kPayload2);
+
+ ASSERT_EQ(1U, GetInvalidationCount(id));
+ ASSERT_FALSE(IsUnknownVersion(id));
+ EXPECT_EQ(kVersion2, GetVersion(id));
+ EXPECT_EQ(kPayload2, GetPayload(id));
+
+ FireInvalidate(id, kVersion1, kPayload1);
+
+ ASSERT_EQ(2U, GetInvalidationCount(id));
+ ASSERT_FALSE(IsUnknownVersion(id));
+
+ EXPECT_EQ(kVersion1, GetVersion(id));
+ EXPECT_EQ(kPayload1, GetPayload(id));
+}
+
+// Fire an invalidation with an unknown version.
+TEST_F(SyncInvalidationListenerTest, InvalidateUnknownVersion) {
+ const ObjectId& id = kBookmarksId_;
+
+ FireInvalidateUnknownVersion(id);
+
+ ASSERT_EQ(1U, GetInvalidationCount(id));
+ EXPECT_TRUE(IsUnknownVersion(id));
+}
+
+// Fire an invalidation for all enabled IDs.
+TEST_F(SyncInvalidationListenerTest, InvalidateAll) {
+ FireInvalidateAll();
+
+ for (ObjectIdSet::const_iterator it = registered_ids_.begin();
+ it != registered_ids_.end(); ++it) {
+ ASSERT_EQ(1U, GetInvalidationCount(*it));
+ EXPECT_TRUE(IsUnknownVersion(*it));
+ }
+}
+
+// Test a simple scenario for multiple IDs.
+TEST_F(SyncInvalidationListenerTest, InvalidateMultipleIds) {
+ FireInvalidate(kBookmarksId_, 3, NULL);
+
+ ASSERT_EQ(1U, GetInvalidationCount(kBookmarksId_));
+ ASSERT_FALSE(IsUnknownVersion(kBookmarksId_));
+ EXPECT_EQ(3, GetVersion(kBookmarksId_));
+ EXPECT_EQ("", GetPayload(kBookmarksId_));
+
+ // kExtensionId is not registered, so the invalidation should not get through.
+ FireInvalidate(kExtensionsId_, 2, NULL);
+ ASSERT_EQ(0U, GetInvalidationCount(kExtensionsId_));
+}
+
+// Registration tests.
+
+// With IDs already registered, enable notifications then ready the
+// client. The IDs should be registered only after the client is
+// readied.
+TEST_F(SyncInvalidationListenerTest, RegisterEnableReady) {
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ EnableNotifications();
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+}
+
+// With IDs already registered, ready the client then enable
+// notifications. The IDs should be registered after the client is
+// readied.
+TEST_F(SyncInvalidationListenerTest, RegisterReadyEnable) {
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+
+ EnableNotifications();
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+}
+
+// Unregister the IDs, enable notifications, re-register the IDs, then
+// ready the client. The IDs should be registered only after the
+// client is readied.
+TEST_F(SyncInvalidationListenerTest, EnableRegisterReady) {
+ listener_.UpdateRegisteredIds(ObjectIdSet());
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ EnableNotifications();
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.UpdateRegisteredIds(registered_ids_);
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+}
+
+// Unregister the IDs, enable notifications, ready the client, then
+// re-register the IDs. The IDs should be registered only after the
+// client is readied.
+TEST_F(SyncInvalidationListenerTest, EnableReadyRegister) {
+ listener_.UpdateRegisteredIds(ObjectIdSet());
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ EnableNotifications();
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.UpdateRegisteredIds(registered_ids_);
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+}
+
+// Unregister the IDs, ready the client, enable notifications, then
+// re-register the IDs. The IDs should be registered only after the
+// client is readied.
+TEST_F(SyncInvalidationListenerTest, ReadyEnableRegister) {
+ listener_.UpdateRegisteredIds(ObjectIdSet());
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ EnableNotifications();
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.UpdateRegisteredIds(registered_ids_);
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+}
+
+// Unregister the IDs, ready the client, re-register the IDs, then
+// enable notifications. The IDs should be registered only after the
+// client is readied.
+//
+// This test is important: see http://crbug.com/139424.
+TEST_F(SyncInvalidationListenerTest, ReadyRegisterEnable) {
+ listener_.UpdateRegisteredIds(ObjectIdSet());
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.UpdateRegisteredIds(registered_ids_);
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+
+ EnableNotifications();
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+}
+
+// With IDs already registered, ready the client, restart the client,
+// then re-ready it. The IDs should still be registered.
+TEST_F(SyncInvalidationListenerTest, RegisterTypesPreserved) {
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+
+ RestartClient();
+
+ EXPECT_TRUE(GetRegisteredIds().empty());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(registered_ids_, GetRegisteredIds());
+}
+
+// Make sure that state is correctly purged from the local invalidation state
+// map cache when an ID is unregistered.
+TEST_F(SyncInvalidationListenerTest, UnregisterCleansUpStateMapCache) {
+ const ObjectId& id = kBookmarksId_;
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_TRUE(GetSavedInvalidations().empty());
+ FireInvalidate(id, 1, "hello");
+ EXPECT_EQ(1U, GetSavedInvalidations().size());
+ EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), id));
+ FireInvalidate(kPreferencesId_, 2, "world");
+ EXPECT_EQ(2U, GetSavedInvalidations().size());
+
+ EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), id));
+ EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), kPreferencesId_));
+
+ ObjectIdSet ids;
+ ids.insert(id);
+ listener_.UpdateRegisteredIds(ids);
+ EXPECT_EQ(1U, GetSavedInvalidations().size());
+ EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), id));
+}
+
+TEST_F(SyncInvalidationListenerTest, DuplicateInvaldiations_Simple) {
+ const ObjectId& id = kBookmarksId_;
+ listener_.Ready(fake_invalidation_client_);
+
+ // Send a stream of invalidations, including two copies of the second.
+ FireInvalidate(id, 1, "one");
+ FireInvalidate(id, 2, "two");
+ FireInvalidate(id, 3, "three");
+ FireInvalidate(id, 2, "two");
+
+ // Expect that the duplicate was discarded.
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ EXPECT_EQ(3U, list.GetSize());
+ SingleObjectInvalidationSet::const_iterator it = list.begin();
+ EXPECT_EQ(1, it->version());
+ it++;
+ EXPECT_EQ(2, it->version());
+ it++;
+ EXPECT_EQ(3, it->version());
+}
+
+TEST_F(SyncInvalidationListenerTest, DuplicateInvalidations_NearBufferLimit) {
+ const size_t kPairsToSend = UnackedInvalidationSet::kMaxBufferedInvalidations;
+ const ObjectId& id = kBookmarksId_;
+ listener_.Ready(fake_invalidation_client_);
+
+ // We will have enough buffer space in the state tracker for all these
+ // invalidations only if duplicates are ignored.
+ for (size_t i = 0; i < kPairsToSend; ++i) {
+ FireInvalidate(id, i, "payload");
+ FireInvalidate(id, i, "payload");
+ }
+
+ // Expect that the state map ignored duplicates.
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ EXPECT_EQ(kPairsToSend, list.GetSize());
+ EXPECT_FALSE(list.begin()->is_unknown_version());
+
+ // Expect that all invalidations (including duplicates) were emitted.
+ EXPECT_EQ(kPairsToSend*2, GetInvalidationCount(id));
+
+ // Acknowledge all invalidations to clear the internal state.
+ AcknowledgeAll(id);
+ EXPECT_TRUE(GetSavedInvalidationsForType(id).IsEmpty());
+}
+
+TEST_F(SyncInvalidationListenerTest, DuplicateInvalidations_UnknownVersion) {
+ const ObjectId& id = kBookmarksId_;
+ listener_.Ready(fake_invalidation_client_);
+
+ FireInvalidateUnknownVersion(id);
+ FireInvalidateUnknownVersion(id);
+
+ {
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ EXPECT_EQ(1U, list.GetSize());
+ }
+
+ // Acknowledge the second. There should be no effect on the stored list.
+ ASSERT_EQ(2U, GetInvalidationCount(id));
+ AcknowledgeNthInvalidation(id, 1);
+ {
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ EXPECT_EQ(1U, list.GetSize());
+ }
+
+ // Acknowledge the first. This should remove the invalidation from the list.
+ ASSERT_EQ(2U, GetInvalidationCount(id));
+ AcknowledgeNthInvalidation(id, 0);
+ {
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ EXPECT_EQ(0U, list.GetSize());
+ }
+}
+
+// Make sure that acknowledgements erase items from the local store.
+TEST_F(SyncInvalidationListenerTest, AcknowledgementsCleanUpStateMapCache) {
+ const ObjectId& id = kBookmarksId_;
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_TRUE(GetSavedInvalidations().empty());
+ FireInvalidate(id, 10, "hello");
+ FireInvalidate(id, 20, "world");
+ FireInvalidateUnknownVersion(id);
+
+ // Expect that all three invalidations have been saved to permanent storage.
+ {
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ ASSERT_EQ(3U, list.GetSize());
+ EXPECT_TRUE(list.begin()->is_unknown_version());
+ EXPECT_EQ(20, list.back().version());
+ }
+
+ // Acknowledge the second sent invaldiation (version 20) and verify it was
+ // removed from storage.
+ AcknowledgeNthInvalidation(id, 1);
+ {
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ ASSERT_EQ(2U, list.GetSize());
+ EXPECT_TRUE(list.begin()->is_unknown_version());
+ EXPECT_EQ(10, list.back().version());
+ }
+
+ // Acknowledge the last sent invalidation (unknown version) and verify it was
+ // removed from storage.
+ AcknowledgeNthInvalidation(id, 2);
+ {
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ ASSERT_EQ(1U, list.GetSize());
+ EXPECT_FALSE(list.begin()->is_unknown_version());
+ EXPECT_EQ(10, list.back().version());
+ }
+}
+
+// Make sure that drops erase items from the local store.
+TEST_F(SyncInvalidationListenerTest, DropsCleanUpStateMapCache) {
+ const ObjectId& id = kBookmarksId_;
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_TRUE(GetSavedInvalidations().empty());
+ FireInvalidate(id, 10, "hello");
+ FireInvalidate(id, 20, "world");
+ FireInvalidateUnknownVersion(id);
+
+ // Expect that all three invalidations have been saved to permanent storage.
+ {
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ ASSERT_EQ(3U, list.GetSize());
+ EXPECT_TRUE(list.begin()->is_unknown_version());
+ EXPECT_EQ(20, list.back().version());
+ }
+
+ // Drop the second sent invalidation (version 20) and verify it was removed
+ // from storage. Also verify we still have an unknown version invalidation.
+ DropNthInvalidation(id, 1);
+ {
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ ASSERT_EQ(2U, list.GetSize());
+ EXPECT_TRUE(list.begin()->is_unknown_version());
+ EXPECT_EQ(10, list.back().version());
+ }
+
+ // Drop the remaining invalidation. Verify an unknown version is all that
+ // remains.
+ DropNthInvalidation(id, 0);
+ {
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
+ ASSERT_EQ(1U, list.GetSize());
+ EXPECT_TRUE(list.begin()->is_unknown_version());
+ }
+
+ // Announce that the delegate has recovered from the drop. Verify no
+ // invalidations remain saved.
+ RecoverFromDropEvent(id);
+ EXPECT_TRUE(GetSavedInvalidationsForType(id).IsEmpty());
+
+ RecoverFromDropEvent(id);
+}
+
+// Without readying the client, disable notifications, then enable
+// them. The listener should still think notifications are disabled.
+TEST_F(SyncInvalidationListenerTest, EnableNotificationsNotReady) {
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR,
+ GetInvalidatorState());
+
+ DisableNotifications(
+ notifier::TRANSIENT_NOTIFICATION_ERROR);
+
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());
+
+ DisableNotifications(notifier::NOTIFICATION_CREDENTIALS_REJECTED);
+
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());
+
+ EnableNotifications();
+
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());
+}
+
+// Enable notifications then Ready the invalidation client. The
+// delegate should then be ready.
+TEST_F(SyncInvalidationListenerTest, EnableNotificationsThenReady) {
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());
+
+ EnableNotifications();
+
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
+}
+
+// Ready the invalidation client then enable notifications. The
+// delegate should then be ready.
+TEST_F(SyncInvalidationListenerTest, ReadyThenEnableNotifications) {
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());
+
+ EnableNotifications();
+
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
+}
+
+// Enable notifications and ready the client. Then disable
+// notifications with an auth error and re-enable notifications. The
+// delegate should go into an auth error mode and then back out.
+TEST_F(SyncInvalidationListenerTest, PushClientAuthError) {
+ EnableNotifications();
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
+
+ DisableNotifications(
+ notifier::NOTIFICATION_CREDENTIALS_REJECTED);
+
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());
+
+ EnableNotifications();
+
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
+}
+
+// Enable notifications and ready the client. Then simulate an auth
+// error from the invalidation client. Simulate some notification
+// events, then re-ready the client. The delegate should go into an
+// auth error mode and come out of it only after the client is ready.
+TEST_F(SyncInvalidationListenerTest, InvalidationClientAuthError) {
+ EnableNotifications();
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
+
+ listener_.InformError(
+ fake_invalidation_client_,
+ invalidation::ErrorInfo(
+ invalidation::ErrorReason::AUTH_FAILURE,
+ false /* is_transient */,
+ "auth error",
+ invalidation::ErrorContext()));
+
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());
+
+ DisableNotifications(notifier::TRANSIENT_NOTIFICATION_ERROR);
+
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());
+
+ DisableNotifications(notifier::TRANSIENT_NOTIFICATION_ERROR);
+
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());
+
+ EnableNotifications();
+
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());
+
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
+}
+
+// A variant of SyncInvalidationListenerTest that starts with some initial
+// state. We make not attempt to abstract away the contents of this state. The
+// tests that make use of this harness depend on its implementation details.
+class SyncInvalidationListenerTest_WithInitialState
+ : public SyncInvalidationListenerTest {
+ public:
+ virtual void SetUp() {
+ UnackedInvalidationSet bm_state(kBookmarksId_);
+ UnackedInvalidationSet ext_state(kExtensionsId_);
+
+ Invalidation bm_unknown = Invalidation::InitUnknownVersion(kBookmarksId_);
+ Invalidation bm_v100 = Invalidation::Init(kBookmarksId_, 100, "hundred");
+ bm_state.Add(bm_unknown);
+ bm_state.Add(bm_v100);
+
+ Invalidation ext_v10 = Invalidation::Init(kExtensionsId_, 10, "ten");
+ Invalidation ext_v20 = Invalidation::Init(kExtensionsId_, 20, "twenty");
+ ext_state.Add(ext_v10);
+ ext_state.Add(ext_v20);
+
+ initial_state.insert(std::make_pair(kBookmarksId_, bm_state));
+ initial_state.insert(std::make_pair(kExtensionsId_, ext_state));
+
+ fake_tracker_.SetSavedInvalidations(initial_state);
+
+ SyncInvalidationListenerTest::SetUp();
+ }
+
+ UnackedInvalidationsMap initial_state;
+};
+
+// Verify that saved invalidations are forwarded when handlers register.
+TEST_F(SyncInvalidationListenerTest_WithInitialState,
+ ReceiveSavedInvalidations) {
+ EnableNotifications();
+ listener_.Ready(fake_invalidation_client_);
+
+ EXPECT_THAT(initial_state, test_util::Eq(GetSavedInvalidations()));
+
+ ASSERT_EQ(2U, GetInvalidationCount(kBookmarksId_));
+ EXPECT_EQ(100, GetVersion(kBookmarksId_));
+
+ ASSERT_EQ(0U, GetInvalidationCount(kExtensionsId_));
+
+ FireInvalidate(kExtensionsId_, 30, "thirty");
+
+ ObjectIdSet ids = GetRegisteredIds();
+ ids.insert(kExtensionsId_);
+ listener_.UpdateRegisteredIds(ids);
+
+ ASSERT_EQ(3U, GetInvalidationCount(kExtensionsId_));
+ EXPECT_EQ(30, GetVersion(kExtensionsId_));
+}
+
+} // namespace
+
+} // namespace syncer
diff --git a/sync/notifier/sync_system_resources.cc b/sync/notifier/sync_system_resources.cc
new file mode 100644
index 0000000..5df110d
--- /dev/null
+++ b/sync/notifier/sync_system_resources.cc
@@ -0,0 +1,333 @@
+// Copyright 2014 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/notifier/sync_system_resources.h"
+
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "google/cacheinvalidation/deps/callback.h"
+#include "google/cacheinvalidation/include/types.h"
+#include "jingle/notifier/listener/push_client.h"
+#include "sync/notifier/gcm_network_channel.h"
+#include "sync/notifier/gcm_network_channel_delegate.h"
+#include "sync/notifier/invalidation_util.h"
+#include "sync/notifier/push_client_channel.h"
+
+namespace syncer {
+
+SyncLogger::SyncLogger() {}
+SyncLogger::~SyncLogger() {}
+
+void SyncLogger::Log(LogLevel level, const char* file, int line,
+ const char* format, ...) {
+ logging::LogSeverity log_severity = -2; // VLOG(2)
+ bool emit_log = false;
+ switch (level) {
+ case FINE_LEVEL:
+ log_severity = -2; // VLOG(2)
+ emit_log = VLOG_IS_ON(2);
+ break;
+ case INFO_LEVEL:
+ log_severity = -1; // VLOG(1)
+ emit_log = VLOG_IS_ON(1);
+ break;
+ case WARNING_LEVEL:
+ log_severity = logging::LOG_WARNING;
+ emit_log = LOG_IS_ON(WARNING);
+ break;
+ case SEVERE_LEVEL:
+ log_severity = logging::LOG_ERROR;
+ emit_log = LOG_IS_ON(ERROR);
+ break;
+ }
+ if (emit_log) {
+ va_list ap;
+ va_start(ap, format);
+ std::string result;
+ base::StringAppendV(&result, format, ap);
+ logging::LogMessage(file, line, log_severity).stream() << result;
+ va_end(ap);
+ }
+}
+
+void SyncLogger::SetSystemResources(invalidation::SystemResources* resources) {
+ // Do nothing.
+}
+
+SyncInvalidationScheduler::SyncInvalidationScheduler()
+ : created_on_loop_(base::MessageLoop::current()),
+ is_started_(false),
+ is_stopped_(false),
+ weak_factory_(this) {
+ CHECK(created_on_loop_);
+}
+
+SyncInvalidationScheduler::~SyncInvalidationScheduler() {
+ CHECK_EQ(created_on_loop_, base::MessageLoop::current());
+ CHECK(is_stopped_);
+}
+
+void SyncInvalidationScheduler::Start() {
+ CHECK_EQ(created_on_loop_, base::MessageLoop::current());
+ CHECK(!is_started_);
+ is_started_ = true;
+ is_stopped_ = false;
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+void SyncInvalidationScheduler::Stop() {
+ CHECK_EQ(created_on_loop_, base::MessageLoop::current());
+ is_stopped_ = true;
+ is_started_ = false;
+ weak_factory_.InvalidateWeakPtrs();
+ STLDeleteElements(&posted_tasks_);
+ posted_tasks_.clear();
+}
+
+void SyncInvalidationScheduler::Schedule(invalidation::TimeDelta delay,
+ invalidation::Closure* task) {
+ DCHECK(invalidation::IsCallbackRepeatable(task));
+ CHECK_EQ(created_on_loop_, base::MessageLoop::current());
+
+ if (!is_started_) {
+ delete task;
+ return;
+ }
+
+ posted_tasks_.insert(task);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(&SyncInvalidationScheduler::RunPostedTask,
+ weak_factory_.GetWeakPtr(), task),
+ delay);
+}
+
+bool SyncInvalidationScheduler::IsRunningOnThread() const {
+ return created_on_loop_ == base::MessageLoop::current();
+}
+
+invalidation::Time SyncInvalidationScheduler::GetCurrentTime() const {
+ CHECK_EQ(created_on_loop_, base::MessageLoop::current());
+ return base::Time::Now();
+}
+
+void SyncInvalidationScheduler::SetSystemResources(
+ invalidation::SystemResources* resources) {
+ // Do nothing.
+}
+
+void SyncInvalidationScheduler::RunPostedTask(invalidation::Closure* task) {
+ CHECK_EQ(created_on_loop_, base::MessageLoop::current());
+ task->Run();
+ posted_tasks_.erase(task);
+ delete task;
+}
+
+SyncNetworkChannel::SyncNetworkChannel()
+ : invalidator_state_(DEFAULT_INVALIDATION_ERROR),
+ received_messages_count_(0) {}
+
+SyncNetworkChannel::~SyncNetworkChannel() {
+ STLDeleteElements(&network_status_receivers_);
+}
+
+void SyncNetworkChannel::SetMessageReceiver(
+ invalidation::MessageCallback* incoming_receiver) {
+ incoming_receiver_.reset(incoming_receiver);
+}
+
+void SyncNetworkChannel::AddNetworkStatusReceiver(
+ invalidation::NetworkStatusCallback* network_status_receiver) {
+ network_status_receiver->Run(invalidator_state_ == INVALIDATIONS_ENABLED);
+ network_status_receivers_.push_back(network_status_receiver);
+}
+
+void SyncNetworkChannel::SetSystemResources(
+ invalidation::SystemResources* resources) {
+ // Do nothing.
+}
+
+void SyncNetworkChannel::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void SyncNetworkChannel::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+scoped_ptr<SyncNetworkChannel> SyncNetworkChannel::CreatePushClientChannel(
+ const notifier::NotifierOptions& notifier_options) {
+ scoped_ptr<notifier::PushClient> push_client(
+ notifier::PushClient::CreateDefaultOnIOThread(notifier_options));
+ return scoped_ptr<SyncNetworkChannel>(
+ new PushClientChannel(push_client.Pass()));
+}
+
+scoped_ptr<SyncNetworkChannel> SyncNetworkChannel::CreateGCMNetworkChannel(
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ scoped_ptr<GCMNetworkChannelDelegate> delegate) {
+ return scoped_ptr<SyncNetworkChannel>(new GCMNetworkChannel(
+ request_context_getter, delegate.Pass()));
+}
+
+void SyncNetworkChannel::NotifyStateChange(InvalidatorState invalidator_state) {
+ // Remember state for future NetworkStatusReceivers.
+ invalidator_state_ = invalidator_state;
+ // Notify NetworkStatusReceivers in cacheinvalidation.
+ for (NetworkStatusReceiverList::const_iterator it =
+ network_status_receivers_.begin();
+ it != network_status_receivers_.end(); ++it) {
+ (*it)->Run(invalidator_state_ == INVALIDATIONS_ENABLED);
+ }
+ // Notify observers.
+ FOR_EACH_OBSERVER(Observer, observers_,
+ OnNetworkChannelStateChanged(invalidator_state_));
+}
+
+bool SyncNetworkChannel::DeliverIncomingMessage(const std::string& message) {
+ if (!incoming_receiver_) {
+ DLOG(ERROR) << "No receiver for incoming notification";
+ return false;
+ }
+ received_messages_count_++;
+ incoming_receiver_->Run(message);
+ return true;
+}
+
+int SyncNetworkChannel::GetReceivedMessagesCount() const {
+ return received_messages_count_;
+}
+
+SyncStorage::SyncStorage(StateWriter* state_writer,
+ invalidation::Scheduler* scheduler)
+ : state_writer_(state_writer),
+ scheduler_(scheduler) {
+ DCHECK(state_writer_);
+ DCHECK(scheduler_);
+}
+
+SyncStorage::~SyncStorage() {}
+
+void SyncStorage::WriteKey(const std::string& key, const std::string& value,
+ invalidation::WriteKeyCallback* done) {
+ CHECK(state_writer_);
+ // TODO(ghc): actually write key,value associations, and don't invoke the
+ // callback until the operation completes.
+ state_writer_->WriteState(value);
+ cached_state_ = value;
+ // According to the cache invalidation API folks, we can do this as
+ // long as we make sure to clear the persistent state that we start
+ // up the cache invalidation client with. However, we musn't do it
+ // right away, as we may be called under a lock that the callback
+ // uses.
+ scheduler_->Schedule(
+ invalidation::Scheduler::NoDelay(),
+ invalidation::NewPermanentCallback(
+ this, &SyncStorage::RunAndDeleteWriteKeyCallback,
+ done));
+}
+
+void SyncStorage::ReadKey(const std::string& key,
+ invalidation::ReadKeyCallback* done) {
+ DCHECK(scheduler_->IsRunningOnThread()) << "not running on scheduler thread";
+ RunAndDeleteReadKeyCallback(done, cached_state_);
+}
+
+void SyncStorage::DeleteKey(const std::string& key,
+ invalidation::DeleteKeyCallback* done) {
+ // TODO(ghc): Implement.
+ LOG(WARNING) << "ignoring call to DeleteKey(" << key << ", callback)";
+}
+
+void SyncStorage::ReadAllKeys(invalidation::ReadAllKeysCallback* done) {
+ // TODO(ghc): Implement.
+ LOG(WARNING) << "ignoring call to ReadAllKeys(callback)";
+}
+
+void SyncStorage::SetSystemResources(
+ invalidation::SystemResources* resources) {
+ // Do nothing.
+}
+
+void SyncStorage::RunAndDeleteWriteKeyCallback(
+ invalidation::WriteKeyCallback* callback) {
+ callback->Run(
+ invalidation::Status(invalidation::Status::SUCCESS, std::string()));
+ delete callback;
+}
+
+void SyncStorage::RunAndDeleteReadKeyCallback(
+ invalidation::ReadKeyCallback* callback, const std::string& value) {
+ callback->Run(std::make_pair(
+ invalidation::Status(invalidation::Status::SUCCESS, std::string()),
+ value));
+ delete callback;
+}
+
+SyncSystemResources::SyncSystemResources(
+ SyncNetworkChannel* sync_network_channel,
+ StateWriter* state_writer)
+ : is_started_(false),
+ logger_(new SyncLogger()),
+ internal_scheduler_(new SyncInvalidationScheduler()),
+ listener_scheduler_(new SyncInvalidationScheduler()),
+ storage_(new SyncStorage(state_writer, internal_scheduler_.get())),
+ sync_network_channel_(sync_network_channel) {
+}
+
+SyncSystemResources::~SyncSystemResources() {
+ Stop();
+}
+
+void SyncSystemResources::Start() {
+ internal_scheduler_->Start();
+ listener_scheduler_->Start();
+ is_started_ = true;
+}
+
+void SyncSystemResources::Stop() {
+ internal_scheduler_->Stop();
+ listener_scheduler_->Stop();
+}
+
+bool SyncSystemResources::IsStarted() const {
+ return is_started_;
+}
+
+void SyncSystemResources::set_platform(const std::string& platform) {
+ platform_ = platform;
+}
+
+std::string SyncSystemResources::platform() const {
+ return platform_;
+}
+
+SyncLogger* SyncSystemResources::logger() {
+ return logger_.get();
+}
+
+SyncStorage* SyncSystemResources::storage() {
+ return storage_.get();
+}
+
+SyncNetworkChannel* SyncSystemResources::network() {
+ return sync_network_channel_;
+}
+
+SyncInvalidationScheduler* SyncSystemResources::internal_scheduler() {
+ return internal_scheduler_.get();
+}
+
+SyncInvalidationScheduler* SyncSystemResources::listener_scheduler() {
+ return listener_scheduler_.get();
+}
+
+} // namespace syncer
diff --git a/sync/notifier/sync_system_resources.h b/sync/notifier/sync_system_resources.h
new file mode 100644
index 0000000..ebddccf
--- /dev/null
+++ b/sync/notifier/sync_system_resources.h
@@ -0,0 +1,241 @@
+// Copyright 2014 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.
+//
+// Simple system resources class that uses the current message loop
+// for scheduling. Assumes the current message loop is already
+// running.
+
+#ifndef SYNC_NOTIFIER_SYNC_SYSTEM_RESOURCES_H_
+#define SYNC_NOTIFIER_SYNC_SYSTEM_RESOURCES_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/values.h"
+#include "google/cacheinvalidation/include/system-resources.h"
+#include "jingle/notifier/base/notifier_options.h"
+#include "sync/base/sync_export.h"
+#include "sync/notifier/invalidator_state.h"
+#include "sync/notifier/state_writer.h"
+
+namespace syncer {
+
+class GCMNetworkChannelDelegate;
+
+class SyncLogger : public invalidation::Logger {
+ public:
+ SyncLogger();
+
+ virtual ~SyncLogger();
+
+ // invalidation::Logger implementation.
+ virtual void Log(LogLevel level, const char* file, int line,
+ const char* format, ...) OVERRIDE;
+
+ virtual void SetSystemResources(
+ invalidation::SystemResources* resources) OVERRIDE;
+};
+
+class SyncInvalidationScheduler : public invalidation::Scheduler {
+ public:
+ SyncInvalidationScheduler();
+
+ virtual ~SyncInvalidationScheduler();
+
+ // Start and stop the scheduler.
+ void Start();
+ void Stop();
+
+ // invalidation::Scheduler implementation.
+ virtual void Schedule(invalidation::TimeDelta delay,
+ invalidation::Closure* task) OVERRIDE;
+
+ virtual bool IsRunningOnThread() const OVERRIDE;
+
+ virtual invalidation::Time GetCurrentTime() const OVERRIDE;
+
+ virtual void SetSystemResources(
+ invalidation::SystemResources* resources) OVERRIDE;
+
+ private:
+ // Runs the task, deletes it, and removes it from |posted_tasks_|.
+ void RunPostedTask(invalidation::Closure* task);
+
+ // Holds all posted tasks that have not yet been run.
+ std::set<invalidation::Closure*> posted_tasks_;
+
+ const base::MessageLoop* created_on_loop_;
+ bool is_started_;
+ bool is_stopped_;
+
+ base::WeakPtrFactory<SyncInvalidationScheduler> weak_factory_;
+};
+
+// SyncNetworkChannel implements common tasks needed to interact with
+// invalidation library:
+// - registering message and network status callbacks
+// - notifying observers about network channel state change
+// Implementation of particular network protocol should implement
+// SendMessage and call NotifyStateChange and DeliverIncomingMessage.
+class SYNC_EXPORT_PRIVATE SyncNetworkChannel
+ : public NON_EXPORTED_BASE(invalidation::NetworkChannel) {
+ public:
+ class Observer {
+ public:
+ // Called when network channel state changes. Possible states are:
+ // - INVALIDATIONS_ENABLED : connection is established and working
+ // - TRANSIENT_INVALIDATION_ERROR : no network, connection lost, etc.
+ // - INVALIDATION_CREDENTIALS_REJECTED : Issues with auth token
+ virtual void OnNetworkChannelStateChanged(
+ InvalidatorState invalidator_state) = 0;
+ };
+
+ SyncNetworkChannel();
+
+ virtual ~SyncNetworkChannel();
+
+ // invalidation::NetworkChannel implementation.
+ // SyncNetworkChannel doesn't implement SendMessage. It is responsibility of
+ // subclass to implement it.
+ virtual void SetMessageReceiver(
+ invalidation::MessageCallback* incoming_receiver) OVERRIDE;
+ virtual void AddNetworkStatusReceiver(
+ invalidation::NetworkStatusCallback* network_status_receiver) OVERRIDE;
+ virtual void SetSystemResources(
+ invalidation::SystemResources* resources) OVERRIDE;
+
+ // Subclass should implement UpdateCredentials to pass new token to channel
+ // library.
+ virtual void UpdateCredentials(const std::string& email,
+ const std::string& token) = 0;
+
+ // Return value from GetInvalidationClientType will be passed to
+ // invalidation::CreateInvalidationClient. Subclass should return one of the
+ // values from ipc::invalidation::ClientType enum from types.proto.
+ virtual int GetInvalidationClientType() = 0;
+
+ // Subclass should implement RequestDetailedStatus to provide debugging
+ // information.
+ virtual void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) = 0;
+
+ // Classes interested in network channel state changes should implement
+ // SyncNetworkChannel::Observer and register here.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Helper functions that know how to construct network channels from channel
+ // specific parameters.
+ static scoped_ptr<SyncNetworkChannel> CreatePushClientChannel(
+ const notifier::NotifierOptions& notifier_options);
+ static scoped_ptr<SyncNetworkChannel> CreateGCMNetworkChannel(
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+ scoped_ptr<GCMNetworkChannelDelegate> delegate);
+
+ // Get the count of how many valid received messages were received.
+ int GetReceivedMessagesCount() const;
+
+ protected:
+ // Subclass should notify about connection state through NotifyStateChange.
+ void NotifyStateChange(InvalidatorState invalidator_state);
+ // Subclass should call DeliverIncomingMessage for message to reach
+ // invalidations library.
+ bool DeliverIncomingMessage(const std::string& message);
+
+ private:
+ typedef std::vector<invalidation::NetworkStatusCallback*>
+ NetworkStatusReceiverList;
+
+ // Callbacks into invalidation library
+ scoped_ptr<invalidation::MessageCallback> incoming_receiver_;
+ NetworkStatusReceiverList network_status_receivers_;
+
+ // Last channel state for new network status receivers.
+ InvalidatorState invalidator_state_;
+
+ int received_messages_count_;
+
+ ObserverList<Observer> observers_;
+};
+
+class SyncStorage : public invalidation::Storage {
+ public:
+ SyncStorage(StateWriter* state_writer, invalidation::Scheduler* scheduler);
+
+ virtual ~SyncStorage();
+
+ void SetInitialState(const std::string& value) {
+ cached_state_ = value;
+ }
+
+ // invalidation::Storage implementation.
+ virtual void WriteKey(const std::string& key, const std::string& value,
+ invalidation::WriteKeyCallback* done) OVERRIDE;
+
+ virtual void ReadKey(const std::string& key,
+ invalidation::ReadKeyCallback* done) OVERRIDE;
+
+ virtual void DeleteKey(const std::string& key,
+ invalidation::DeleteKeyCallback* done) OVERRIDE;
+
+ virtual void ReadAllKeys(
+ invalidation::ReadAllKeysCallback* key_callback) OVERRIDE;
+
+ virtual void SetSystemResources(
+ invalidation::SystemResources* resources) OVERRIDE;
+
+ private:
+ // Runs the given storage callback with SUCCESS status and deletes it.
+ void RunAndDeleteWriteKeyCallback(
+ invalidation::WriteKeyCallback* callback);
+
+ // Runs the given callback with the given value and deletes it.
+ void RunAndDeleteReadKeyCallback(
+ invalidation::ReadKeyCallback* callback, const std::string& value);
+
+ StateWriter* state_writer_;
+ invalidation::Scheduler* scheduler_;
+ std::string cached_state_;
+};
+
+class SYNC_EXPORT_PRIVATE SyncSystemResources
+ : public NON_EXPORTED_BASE(invalidation::SystemResources) {
+ public:
+ SyncSystemResources(SyncNetworkChannel* sync_network_channel,
+ StateWriter* state_writer);
+
+ virtual ~SyncSystemResources();
+
+ // invalidation::SystemResources implementation.
+ virtual void Start() OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual bool IsStarted() const OVERRIDE;
+ virtual void set_platform(const std::string& platform);
+ virtual std::string platform() const OVERRIDE;
+ virtual SyncLogger* logger() OVERRIDE;
+ virtual SyncStorage* storage() OVERRIDE;
+ virtual SyncNetworkChannel* network() OVERRIDE;
+ virtual SyncInvalidationScheduler* internal_scheduler() OVERRIDE;
+ virtual SyncInvalidationScheduler* listener_scheduler() OVERRIDE;
+
+ private:
+ bool is_started_;
+ std::string platform_;
+ scoped_ptr<SyncLogger> logger_;
+ scoped_ptr<SyncInvalidationScheduler> internal_scheduler_;
+ scoped_ptr<SyncInvalidationScheduler> listener_scheduler_;
+ scoped_ptr<SyncStorage> storage_;
+ // sync_network_channel_ is owned by SyncInvalidationListener.
+ SyncNetworkChannel* sync_network_channel_;
+};
+
+} // namespace syncer
+
+#endif // SYNC_NOTIFIER_SYNC_SYSTEM_RESOURCES_H_
diff --git a/sync/notifier/sync_system_resources_unittest.cc b/sync/notifier/sync_system_resources_unittest.cc
new file mode 100644
index 0000000..1cbf964
--- /dev/null
+++ b/sync/notifier/sync_system_resources_unittest.cc
@@ -0,0 +1,249 @@
+// Copyright 2014 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/notifier/sync_system_resources.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/message_loop/message_loop.h"
+
+#include "google/cacheinvalidation/include/types.h"
+#include "jingle/notifier/listener/fake_push_client.h"
+#include "sync/notifier/push_client_channel.h"
+#include "sync/notifier/state_writer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+namespace {
+
+using ::testing::_;
+using ::testing::SaveArg;
+
+class MockStateWriter : public StateWriter {
+ public:
+ MOCK_METHOD1(WriteState, void(const std::string&));
+};
+
+class MockClosure {
+ public:
+ MOCK_CONST_METHOD0(Run, void(void));
+ base::Closure* CreateClosure() {
+ return new base::Closure(
+ base::Bind(&MockClosure::Run, base::Unretained(this)));
+ }
+};
+
+class MockStorageCallback {
+ public:
+ MOCK_CONST_METHOD1(Run, void(invalidation::Status));
+ base::Callback<void(invalidation::Status)>* CreateCallback() {
+ return new base::Callback<void(invalidation::Status)>(
+ base::Bind(&MockStorageCallback::Run, base::Unretained(this)));
+ }
+};
+
+class SyncSystemResourcesTest : public testing::Test {
+ protected:
+ SyncSystemResourcesTest()
+ : push_client_channel_(
+ scoped_ptr<notifier::PushClient>(new notifier::FakePushClient())),
+ sync_system_resources_(&push_client_channel_, &mock_state_writer_) {}
+
+ virtual ~SyncSystemResourcesTest() {}
+
+ void ScheduleShouldNotRun() {
+ {
+ // Owned by ScheduleImmediately.
+ MockClosure mock_closure;
+ base::Closure* should_not_run = mock_closure.CreateClosure();
+ EXPECT_CALL(mock_closure, Run()).Times(0);
+ sync_system_resources_.internal_scheduler()->Schedule(
+ invalidation::Scheduler::NoDelay(), should_not_run);
+ }
+ {
+ // Owned by ScheduleOnListenerThread.
+ MockClosure mock_closure;
+ base::Closure* should_not_run = mock_closure.CreateClosure();
+ EXPECT_CALL(mock_closure, Run()).Times(0);
+ sync_system_resources_.listener_scheduler()->Schedule(
+ invalidation::Scheduler::NoDelay(), should_not_run);
+ }
+ {
+ // Owned by ScheduleWithDelay.
+ MockClosure mock_closure;
+ base::Closure* should_not_run = mock_closure.CreateClosure();
+ EXPECT_CALL(mock_closure, Run()).Times(0);
+ sync_system_resources_.internal_scheduler()->Schedule(
+ invalidation::TimeDelta::FromSeconds(0), should_not_run);
+ }
+ }
+
+ // Needed by |sync_system_resources_|.
+ base::MessageLoop message_loop_;
+ MockStateWriter mock_state_writer_;
+ PushClientChannel push_client_channel_;
+ SyncSystemResources sync_system_resources_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SyncSystemResourcesTest);
+};
+
+// Make sure current_time() doesn't crash or leak.
+TEST_F(SyncSystemResourcesTest, CurrentTime) {
+ invalidation::Time current_time =
+ sync_system_resources_.internal_scheduler()->GetCurrentTime();
+ DVLOG(1) << "current_time returned: " << current_time.ToInternalValue();
+}
+
+// Make sure Log() doesn't crash or leak.
+TEST_F(SyncSystemResourcesTest, Log) {
+ sync_system_resources_.logger()->Log(SyncLogger::INFO_LEVEL,
+ __FILE__, __LINE__, "%s %d",
+ "test string", 5);
+}
+
+TEST_F(SyncSystemResourcesTest, ScheduleBeforeStart) {
+ ScheduleShouldNotRun();
+ sync_system_resources_.Start();
+}
+
+TEST_F(SyncSystemResourcesTest, ScheduleAfterStop) {
+ sync_system_resources_.Start();
+ sync_system_resources_.Stop();
+ ScheduleShouldNotRun();
+}
+
+TEST_F(SyncSystemResourcesTest, ScheduleAndStop) {
+ sync_system_resources_.Start();
+ ScheduleShouldNotRun();
+ sync_system_resources_.Stop();
+}
+
+TEST_F(SyncSystemResourcesTest, ScheduleAndDestroy) {
+ sync_system_resources_.Start();
+ ScheduleShouldNotRun();
+}
+
+TEST_F(SyncSystemResourcesTest, ScheduleImmediately) {
+ sync_system_resources_.Start();
+ MockClosure mock_closure;
+ EXPECT_CALL(mock_closure, Run());
+ sync_system_resources_.internal_scheduler()->Schedule(
+ invalidation::Scheduler::NoDelay(), mock_closure.CreateClosure());
+ message_loop_.RunUntilIdle();
+}
+
+TEST_F(SyncSystemResourcesTest, ScheduleOnListenerThread) {
+ sync_system_resources_.Start();
+ MockClosure mock_closure;
+ EXPECT_CALL(mock_closure, Run());
+ sync_system_resources_.listener_scheduler()->Schedule(
+ invalidation::Scheduler::NoDelay(), mock_closure.CreateClosure());
+ EXPECT_TRUE(
+ sync_system_resources_.internal_scheduler()->IsRunningOnThread());
+ message_loop_.RunUntilIdle();
+}
+
+TEST_F(SyncSystemResourcesTest, ScheduleWithZeroDelay) {
+ sync_system_resources_.Start();
+ MockClosure mock_closure;
+ EXPECT_CALL(mock_closure, Run());
+ sync_system_resources_.internal_scheduler()->Schedule(
+ invalidation::TimeDelta::FromSeconds(0), mock_closure.CreateClosure());
+ message_loop_.RunUntilIdle();
+}
+
+// TODO(akalin): Figure out how to test with a non-zero delay.
+
+TEST_F(SyncSystemResourcesTest, WriteState) {
+ sync_system_resources_.Start();
+ EXPECT_CALL(mock_state_writer_, WriteState(_));
+ // Owned by WriteState.
+ MockStorageCallback mock_storage_callback;
+ invalidation::Status results(invalidation::Status::PERMANENT_FAILURE,
+ "fake-failure");
+ EXPECT_CALL(mock_storage_callback, Run(_))
+ .WillOnce(SaveArg<0>(&results));
+ sync_system_resources_.storage()->WriteKey(
+ std::string(), "state", mock_storage_callback.CreateCallback());
+ message_loop_.RunUntilIdle();
+ EXPECT_EQ(invalidation::Status(invalidation::Status::SUCCESS, std::string()),
+ results);
+}
+
+class TestSyncNetworkChannel : public SyncNetworkChannel {
+ public:
+ TestSyncNetworkChannel() {}
+ virtual ~TestSyncNetworkChannel() {}
+
+ using SyncNetworkChannel::NotifyStateChange;
+ using SyncNetworkChannel::DeliverIncomingMessage;
+
+ virtual void SendMessage(const std::string& message) OVERRIDE {
+ }
+
+ virtual void UpdateCredentials(const std::string& email,
+ const std::string& token) OVERRIDE {
+ }
+
+ virtual int GetInvalidationClientType() OVERRIDE {
+ return 0;
+ }
+
+ virtual void RequestDetailedStatus(
+ base::Callback<void(const base::DictionaryValue&)> callback) OVERRIDE {
+ base::DictionaryValue value;
+ callback.Run(value);
+ }
+};
+
+class SyncNetworkChannelTest
+ : public testing::Test,
+ public SyncNetworkChannel::Observer {
+ protected:
+ SyncNetworkChannelTest()
+ : last_invalidator_state_(DEFAULT_INVALIDATION_ERROR),
+ connected_(false) {
+ network_channel_.AddObserver(this);
+ network_channel_.AddNetworkStatusReceiver(
+ invalidation::NewPermanentCallback(
+ this, &SyncNetworkChannelTest::OnNetworkStatusChange));
+ }
+
+ virtual ~SyncNetworkChannelTest() {
+ network_channel_.RemoveObserver(this);
+ }
+
+ virtual void OnNetworkChannelStateChanged(
+ InvalidatorState invalidator_state) OVERRIDE {
+ last_invalidator_state_ = invalidator_state;
+ }
+
+ void OnNetworkStatusChange(bool connected) {
+ connected_ = connected;
+ }
+
+ TestSyncNetworkChannel network_channel_;
+ InvalidatorState last_invalidator_state_;
+ bool connected_;
+};
+
+// Simulate network channel state change. It should propagate to observer.
+TEST_F(SyncNetworkChannelTest, OnNetworkChannelStateChanged) {
+ EXPECT_EQ(DEFAULT_INVALIDATION_ERROR, last_invalidator_state_);
+ EXPECT_FALSE(connected_);
+ network_channel_.NotifyStateChange(INVALIDATIONS_ENABLED);
+ EXPECT_EQ(INVALIDATIONS_ENABLED, last_invalidator_state_);
+ EXPECT_TRUE(connected_);
+ network_channel_.NotifyStateChange(INVALIDATION_CREDENTIALS_REJECTED);
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, last_invalidator_state_);
+ EXPECT_FALSE(connected_);
+}
+
+} // namespace
+} // namespace syncer
diff --git a/sync/notifier/unacked_invalidation_set.cc b/sync/notifier/unacked_invalidation_set.cc
index d1d913c..6991fc0 100644
--- a/sync/notifier/unacked_invalidation_set.cc
+++ b/sync/notifier/unacked_invalidation_set.cc
@@ -7,6 +7,7 @@
#include "base/strings/string_number_conversions.h"
#include "sync/internal_api/public/base/ack_handle.h"
#include "sync/notifier/object_id_invalidation_map.h"
+#include "sync/notifier/sync_invalidation_listener.h"
namespace {
diff --git a/sync/sync_internal_api.gypi b/sync/sync_internal_api.gypi
index f0ae375..f248552 100644
--- a/sync/sync_internal_api.gypi
+++ b/sync/sync_internal_api.gypi
@@ -64,8 +64,6 @@
'internal_api/public/base/enum_set.h',
'internal_api/public/base/invalidation.cc',
'internal_api/public/base/invalidation.h',
- 'internal_api/public/base/invalidator_state.cc',
- 'internal_api/public/base/invalidator_state.h',
'internal_api/public/base/model_type.h',
'internal_api/public/base/node_ordinal.cc',
'internal_api/public/base/node_ordinal.h',
diff --git a/sync/sync_notifier.gypi b/sync/sync_notifier.gypi
index 9c6dc38..f8e9d5c 100644
--- a/sync/sync_notifier.gypi
+++ b/sync/sync_notifier.gypi
@@ -28,13 +28,16 @@
'notifier/dropped_invalidation_tracker.cc',
'notifier/dropped_invalidation_tracker.h',
'notifier/invalidation_handler.h',
- 'notifier/invalidation_state_tracker.cc',
'notifier/invalidation_state_tracker.h',
'notifier/invalidation_util.cc',
'notifier/invalidation_util.h',
'notifier/unacked_invalidation_set.cc',
'notifier/unacked_invalidation_set.h',
'notifier/invalidator.h',
+ 'notifier/invalidator_registrar.cc',
+ 'notifier/invalidator_registrar.h',
+ 'notifier/invalidator_state.cc',
+ 'notifier/invalidator_state.h',
'notifier/mock_ack_handler.cc',
'notifier/mock_ack_handler.h',
'notifier/object_id_invalidation_map.cc',
@@ -45,8 +48,24 @@
'conditions': [
['OS != "android"', {
'sources': [
+ 'notifier/gcm_network_channel.cc',
+ 'notifier/gcm_network_channel.h',
+ 'notifier/gcm_network_channel_delegate.h',
+ 'notifier/invalidation_notifier.cc',
+ 'notifier/invalidation_notifier.h',
+ 'notifier/non_blocking_invalidator.cc',
+ 'notifier/non_blocking_invalidator.h',
+ 'notifier/p2p_invalidator.cc',
+ 'notifier/p2p_invalidator.h',
+ 'notifier/push_client_channel.cc',
+ 'notifier/push_client_channel.h',
'notifier/registration_manager.cc',
'notifier/registration_manager.h',
+ 'notifier/state_writer.h',
+ 'notifier/sync_invalidation_listener.cc',
+ 'notifier/sync_invalidation_listener.h',
+ 'notifier/sync_system_resources.cc',
+ 'notifier/sync_system_resources.h',
],
}],
],
diff --git a/sync/sync_tests.gypi b/sync/sync_tests.gypi
index e92d6c4..cd4f933 100644
--- a/sync/sync_tests.gypi
+++ b/sync/sync_tests.gypi
@@ -159,6 +159,14 @@
'sync',
],
'sources': [
+ 'notifier/fake_invalidation_handler.cc',
+ 'notifier/fake_invalidation_handler.h',
+ 'notifier/fake_invalidation_state_tracker.cc',
+ 'notifier/fake_invalidation_state_tracker.h',
+ 'notifier/fake_invalidator.cc',
+ 'notifier/fake_invalidator.h',
+ 'notifier/invalidator_test_template.cc',
+ 'notifier/invalidator_test_template.h',
'notifier/unacked_invalidation_set_test_util.cc',
'notifier/unacked_invalidation_set_test_util.h',
'internal_api/public/base/object_id_invalidation_map_test_util.h',
@@ -366,9 +374,18 @@
'conditions': [
['OS != "android"', {
'sources': [
+ 'notifier/fake_invalidator_unittest.cc',
+ 'notifier/gcm_network_channel_unittest.cc',
+ 'notifier/invalidation_notifier_unittest.cc',
+ 'notifier/invalidator_registrar_unittest.cc',
+ 'notifier/non_blocking_invalidator_unittest.cc',
'notifier/object_id_invalidation_map_unittest.cc',
+ 'notifier/p2p_invalidator_unittest.cc',
+ 'notifier/push_client_channel_unittest.cc',
'notifier/registration_manager_unittest.cc',
'notifier/single_object_invalidation_set_unittest.cc',
+ 'notifier/sync_invalidation_listener_unittest.cc',
+ 'notifier/sync_system_resources_unittest.cc',
'notifier/unacked_invalidation_set_unittest.cc',
],
}],
diff --git a/sync/tools/DEPS b/sync/tools/DEPS
index ac34026..322ec26 100644
--- a/sync/tools/DEPS
+++ b/sync/tools/DEPS
@@ -1,5 +1,4 @@
include_rules = [
- "+components/invalidation",
"+jingle/notifier/base",
"+net",
"+sync/internal_api/public",
diff --git a/sync/tools/sync_client.cc b/sync/tools/sync_client.cc
index a5890cf..8edd6fa 100644
--- a/sync/tools/sync_client.cc
+++ b/sync/tools/sync_client.cc
@@ -20,7 +20,6 @@
#include "base/rand_util.h"
#include "base/task_runner.h"
#include "base/threading/thread.h"
-#include "components/invalidation/non_blocking_invalidator.h"
#include "jingle/notifier/base/notification_method.h"
#include "jingle/notifier/base/notifier_options.h"
#include "net/base/host_port_pair.h"
@@ -42,6 +41,7 @@
#include "sync/internal_api/public/util/weak_handle.h"
#include "sync/js/js_event_details.h"
#include "sync/js/js_event_handler.h"
+#include "sync/notifier/non_blocking_invalidator.h"
#include "sync/test/fake_encryptor.h"
#include "sync/tools/null_invalidation_state_tracker.h"
diff --git a/sync/tools/sync_listen_notifications.cc b/sync/tools/sync_listen_notifications.cc
index e924e53..6a9a505 100644
--- a/sync/tools/sync_listen_notifications.cc
+++ b/sync/tools/sync_listen_notifications.cc
@@ -15,7 +15,6 @@
#include "base/message_loop/message_loop.h"
#include "base/rand_util.h"
#include "base/threading/thread.h"
-#include "components/invalidation/non_blocking_invalidator.h"
#include "jingle/notifier/base/notification_method.h"
#include "jingle/notifier/base/notifier_options.h"
#include "net/base/host_port_pair.h"
@@ -28,6 +27,7 @@
#include "sync/notifier/invalidation_state_tracker.h"
#include "sync/notifier/invalidation_util.h"
#include "sync/notifier/invalidator.h"
+#include "sync/notifier/non_blocking_invalidator.h"
#include "sync/notifier/object_id_invalidation_map.h"
#include "sync/tools/null_invalidation_state_tracker.h"
diff --git a/sync/tools/sync_tools.gyp b/sync/tools/sync_tools.gyp
index 20ca8ec..7cc8352 100644
--- a/sync/tools/sync_tools.gyp
+++ b/sync/tools/sync_tools.gyp
@@ -39,7 +39,6 @@
],
'dependencies': [
'../../base/base.gyp:base',
- '../../components/components.gyp:invalidation',
'../../jingle/jingle.gyp:notifier',
'../../net/net.gyp:net',
'../../net/net.gyp:net_test_support',
@@ -60,7 +59,6 @@
],
'dependencies': [
'../../base/base.gyp:base',
- '../../components/components.gyp:invalidation',
'../../jingle/jingle.gyp:notifier',
'../../net/net.gyp:net',
'../../net/net.gyp:net_test_support',