diff options
author | ckehoe <ckehoe@chromium.org> | 2014-11-07 17:01:15 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-11-08 01:01:45 +0000 |
commit | 19b4f6371c68fe1627c2b75ca5ec8a4061796e58 (patch) | |
tree | 5d6ef26572e895fa8cf6fe5957484f427162d33c | |
parent | af5a5768de0ef5563228a0a8c0dd519c92c12a86 (diff) | |
download | chromium_src-19b4f6371c68fe1627c2b75ca5ec8a4061796e58.zip chromium_src-19b4f6371c68fe1627c2b75ca5ec8a4061796e58.tar.gz chromium_src-19b4f6371c68fe1627c2b75ca5ec8a4061796e58.tar.bz2 |
Adding GCM support to the copresence component. The Copresence server can push directives (and messages, though they are not yet handled) over GCM.
BUG=424253,425681
Review URL: https://codereview.chromium.org/710513004
Cr-Commit-Position: refs/heads/master@{#303342}
20 files changed, 570 insertions, 75 deletions
diff --git a/chrome/browser/extensions/api/copresence/copresence_api.cc b/chrome/browser/extensions/api/copresence/copresence_api.cc index 7e91674..f4ae23e 100644 --- a/chrome/browser/extensions/api/copresence/copresence_api.cc +++ b/chrome/browser/extensions/api/copresence/copresence_api.cc @@ -7,6 +7,8 @@ #include "base/lazy_instance.h" #include "base/memory/linked_ptr.h" #include "chrome/browser/copresence/chrome_whispernet_client.h" +#include "chrome/browser/services/gcm/gcm_profile_service.h" +#include "chrome/browser/services/gcm/gcm_profile_service_factory.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/extensions/api/copresence.h" #include "components/copresence/copresence_manager_impl.h" @@ -145,6 +147,11 @@ copresence::WhispernetClient* CopresenceService::GetWhispernetClient() { return whispernet_client(); } +gcm::GCMDriver* CopresenceService::GetGCMDriver() { + return gcm::GCMProfileServiceFactory::GetForProfile(browser_context_) + ->driver(); +} + template <> void BrowserContextKeyedAPIFactory<CopresenceService>::DeclareFactoryDependencies() { diff --git a/chrome/browser/extensions/api/copresence/copresence_api.h b/chrome/browser/extensions/api/copresence/copresence_api.h index b7103cf..d49c878 100644 --- a/chrome/browser/extensions/api/copresence/copresence_api.h +++ b/chrome/browser/extensions/api/copresence/copresence_api.h @@ -24,6 +24,10 @@ class CopresenceManager; class WhispernetClient; } +namespace gcm { +class GCMDriver; +} + namespace extensions { class CopresenceService : public BrowserContextKeyedAPI, @@ -73,6 +77,7 @@ class CopresenceService : public BrowserContextKeyedAPI, const std::string GetPlatformVersionString() const override; const std::string GetAPIKey(const std::string& app_id) const override; copresence::WhispernetClient* GetWhispernetClient() override; + gcm::GCMDriver* GetGCMDriver() override; // BrowserContextKeyedAPI implementation. static const char* service_name() { return "CopresenceService"; } diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 5d543bb..daa4ebf 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -699,6 +699,7 @@ 'copresence/handlers/audio/audio_directive_handler_unittest.cc', 'copresence/handlers/audio/audio_directive_list_unittest.cc', 'copresence/handlers/directive_handler_unittest.cc', + 'copresence/handlers/gcm_handler_unittest.cc', 'copresence/mediums/audio/audio_manager_unittest.cc', 'copresence/mediums/audio/audio_player_unittest.cc', 'copresence/mediums/audio/audio_recorder_unittest.cc', diff --git a/components/copresence.gypi b/components/copresence.gypi index cf5c10e..1a789ce 100644 --- a/components/copresence.gypi +++ b/components/copresence.gypi @@ -33,6 +33,8 @@ 'copresence/handlers/audio/tick_clock_ref_counted.h', 'copresence/handlers/directive_handler.cc', 'copresence/handlers/directive_handler.h', + 'copresence/handlers/gcm_handler.cc', + 'copresence/handlers/gcm_handler.h', 'copresence/mediums/audio/audio_manager.h', 'copresence/mediums/audio/audio_manager_impl.cc', 'copresence/mediums/audio/audio_manager_impl.h', @@ -59,15 +61,23 @@ { 'target_name': 'copresence_test_support', 'type': 'static_library', + 'dependencies': [ + 'copresence_proto', + ], 'include_dirs': [ '..', ], 'sources': [ 'copresence/test/audio_test_support.cc', 'copresence/test/audio_test_support.h', + 'copresence/test/fake_directive_handler.cc', + 'copresence/test/fake_directive_handler.h', 'copresence/test/stub_whispernet_client.cc', 'copresence/test/stub_whispernet_client.h', ], + 'export_dependent_settings': [ + 'copresence_proto', + ], }, { # Protobuf compiler / generate rule for copresence. @@ -82,6 +92,7 @@ 'copresence/proto/data.proto', 'copresence/proto/enums.proto', 'copresence/proto/identity.proto', + 'copresence/proto/push_message.proto', 'copresence/proto/rpcs.proto', ], 'variables': { diff --git a/components/copresence/BUILD.gn b/components/copresence/BUILD.gn index 47a5acb..7c82acb 100644 --- a/components/copresence/BUILD.gn +++ b/components/copresence/BUILD.gn @@ -17,6 +17,8 @@ static_library("copresence") { "handlers/audio/tick_clock_ref_counted.h", "handlers/directive_handler.cc", "handlers/directive_handler.h", + "handlers/gcm_handler.cc", + "handlers/gcm_handler.h", "mediums/audio/audio_manager.h", "mediums/audio/audio_manager_impl.cc", "mediums/audio/audio_manager_impl.h", diff --git a/components/copresence/DEPS b/components/copresence/DEPS index dec5262..959832a 100644 --- a/components/copresence/DEPS +++ b/components/copresence/DEPS @@ -1,5 +1,6 @@ include_rules = [ "+base", + "+components/gcm_driver", "+content/public/browser", "+content/public/test", "+google_apis", diff --git a/components/copresence/copresence_manager_impl.cc b/components/copresence/copresence_manager_impl.cc index 265b042..2d5828a 100644 --- a/components/copresence/copresence_manager_impl.cc +++ b/components/copresence/copresence_manager_impl.cc @@ -10,6 +10,7 @@ #include "base/strings/stringprintf.h" #include "base/timer/timer.h" #include "components/copresence/handlers/directive_handler.h" +#include "components/copresence/handlers/gcm_handler.h" #include "components/copresence/proto/rpcs.pb.h" #include "components/copresence/public/whispernet_client.h" #include "components/copresence/rpc/rpc_handler.h" @@ -31,16 +32,22 @@ CopresenceManagerImpl::CopresenceManagerImpl(CopresenceDelegate* delegate) &CopresenceManagerImpl::WhispernetInitComplete, // This callback gets cancelled when we are destroyed. base::Unretained(this))), + init_failed_(false), directive_handler_(new DirectiveHandler), poll_timer_(new base::RepeatingTimer<CopresenceManagerImpl>), - audio_check_timer_(new base::RepeatingTimer<CopresenceManagerImpl>), - init_failed_(false) { + audio_check_timer_(new base::RepeatingTimer<CopresenceManagerImpl>) { DCHECK(delegate_); DCHECK(delegate_->GetWhispernetClient()); delegate_->GetWhispernetClient()->Initialize( whispernet_init_callback_.callback()); - rpc_handler_.reset(new RpcHandler(delegate_, directive_handler_.get())); + if (delegate->GetGCMDriver()) + gcm_handler_.reset(new GCMHandler(delegate->GetGCMDriver(), + directive_handler_.get())); + + rpc_handler_.reset(new RpcHandler(delegate, + directive_handler_.get(), + gcm_handler_.get())); } CopresenceManagerImpl::~CopresenceManagerImpl() { diff --git a/components/copresence/copresence_manager_impl.h b/components/copresence/copresence_manager_impl.h index 12bc051..d91e7ae 100644 --- a/components/copresence/copresence_manager_impl.h +++ b/components/copresence/copresence_manager_impl.h @@ -23,6 +23,7 @@ class URLContextGetter; namespace copresence { class DirectiveHandler; +class GCMHandler; class ReportRequest; class RpcHandler; class WhispernetClient; @@ -61,16 +62,17 @@ class CopresenceManagerImpl : public CopresenceManager { // does not provide a way to unregister its init callback. base::CancelableCallback<void(bool)> whispernet_init_callback_; - // The |directive handler_| needs to destruct before |rpc_handler_|, do not - // change this order. + bool init_failed_; + + // The GCMHandler must destruct before the DirectiveHandler, + // which must destruct before the RpcHandler. Do not change this order. scoped_ptr<RpcHandler> rpc_handler_; scoped_ptr<DirectiveHandler> directive_handler_; + scoped_ptr<GCMHandler> gcm_handler_; scoped_ptr<base::Timer> poll_timer_; scoped_ptr<base::Timer> audio_check_timer_; - bool init_failed_; - DISALLOW_COPY_AND_ASSIGN(CopresenceManagerImpl); }; diff --git a/components/copresence/handlers/gcm_handler.cc b/components/copresence/handlers/gcm_handler.cc new file mode 100644 index 0000000..023380b --- /dev/null +++ b/components/copresence/handlers/gcm_handler.cc @@ -0,0 +1,155 @@ +// 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 "components/copresence/handlers/gcm_handler.h" + +#include "base/base64.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "components/copresence/handlers/directive_handler.h" +#include "components/copresence/proto/push_message.pb.h" +#include "components/gcm_driver/gcm_driver.h" + +using gcm::GCMClient; + +namespace { + +// TODO(ckehoe): Move this to a common library. +bool Base64Decode(std::string data, std::string* out) { + // Convert from URL-safe. + base::ReplaceChars(data, "-", "+", &data); + base::ReplaceChars(data, "_", "/", &data); + + // Add padding if needed. + while (data.size() % 4) + data.push_back('='); + + // Decode. + return base::Base64Decode(data, out); +} + +} // namespace + +namespace copresence { + +const char GCMHandler::kCopresenceAppId[] = + "com.google.android.gms.location.copresence"; +const char GCMHandler::kCopresenceSenderId[] = "745476177629"; +const char GCMHandler::kGcmMessageKey[] = "PUSH_MESSAGE"; + + +// Public functions. + +GCMHandler::GCMHandler(gcm::GCMDriver* gcm_driver, + DirectiveHandler* directive_handler) + : driver_(gcm_driver), + directive_handler_(directive_handler), + registration_callback_(base::Bind(&GCMHandler::RegistrationComplete, + AsWeakPtr())) { + DCHECK(driver_); + DCHECK(directive_handler_); + + driver_->AddAppHandler(kCopresenceAppId, this); + driver_->Register(kCopresenceAppId, + std::vector<std::string>(1, kCopresenceSenderId), + registration_callback_); +} + +GCMHandler::~GCMHandler() { + if (driver_) + driver_->RemoveAppHandler(kCopresenceAppId); +} + +void GCMHandler::GetGcmId(const RegistrationCallback& callback) { + if (gcm_id_.empty()) { + pending_id_requests_.push_back(callback); + } else { + callback.Run(gcm_id_); + } +} + +void GCMHandler::ShutdownHandler() { + // The GCMDriver is going away. Make sure we don't try to contact it. + driver_ = nullptr; +} + +void GCMHandler::OnMessage(const std::string& app_id, + const GCMClient::IncomingMessage& message) { + DCHECK_EQ(kCopresenceAppId, app_id); + DVLOG(2) << "Incoming GCM message"; + + const auto& content = message.data.find(kGcmMessageKey); + if (content == message.data.end()) { + LOG(ERROR) << "GCM message missing data key"; + return; + } + + std::string serialized_message; + if (!Base64Decode(content->second, &serialized_message)) { + LOG(ERROR) << "Couldn't decode GCM message"; + return; + } + + PushMessage push_message; + if (!push_message.ParseFromString(serialized_message)) { + LOG(ERROR) << "GCM message contained invalid proto"; + return; + } + + if (push_message.type() != PushMessage::REPORT) { + DVLOG(2) << "Discarding non-report GCM message"; + return; + } + + DVLOG(3) << "Processing " << push_message.report().directive_size() + << " directive(s) from GCM message"; + for (const Directive& directive : push_message.report().directive()) + directive_handler_->AddDirective(directive); + + int message_count = push_message.report().subscribed_message_size(); + LOG_IF(WARNING, message_count > 0) + << "Discarding " << message_count << " copresence messages sent via GCM"; +} + +void GCMHandler::OnMessagesDeleted(const std::string& app_id) { + DCHECK_EQ(kCopresenceAppId, app_id); + DVLOG(2) << "GCM message overflow reported"; +} + +void GCMHandler::OnSendError( + const std::string& /* app_id */, + const GCMClient::SendErrorDetails& /* send_error_details */) { + NOTREACHED() << "Copresence clients should not be sending GCM messages"; +} + +void GCMHandler::OnSendAcknowledged(const std::string& /* app_id */, + const std::string& /* message_id */) { + NOTREACHED() << "Copresence clients should not be sending GCM messages"; +} + +bool GCMHandler::CanHandle(const std::string& app_id) const { + return app_id == kCopresenceAppId; +} + + +// Private functions. + +void GCMHandler::RegistrationComplete(const std::string& registration_id, + GCMClient::Result result) { + if (result == GCMClient::SUCCESS) { + DVLOG(2) << "GCM registration successful. ID: " << registration_id; + gcm_id_ = registration_id; + } else { + LOG(ERROR) << "GCM registration failed with error " << result; + } + + for (const RegistrationCallback& callback : pending_id_requests_) { + callback.Run(result == GCMClient::SUCCESS ? + registration_id : std::string()); + } + pending_id_requests_.clear(); +} + +} // namespace copresence diff --git a/components/copresence/handlers/gcm_handler.h b/components/copresence/handlers/gcm_handler.h new file mode 100644 index 0000000..f69d8a4 --- /dev/null +++ b/components/copresence/handlers/gcm_handler.h @@ -0,0 +1,78 @@ +// 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 COMPONENTS_COPRESENCE_HANDLERS_GCM_HANDLER_H_ +#define COMPONENTS_COPRESENCE_HANDLERS_GCM_HANDLER_H_ + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "components/gcm_driver/gcm_app_handler.h" +#include "components/gcm_driver/gcm_client.h" + +namespace gcm { +class GCMDriver; +} + +namespace copresence { + +class DirectiveHandler; + +// This class handles GCM messages from the Copresence server. +class GCMHandler final : public gcm::GCMAppHandler, + public base::SupportsWeakPtr<GCMHandler> { + public: + // Callback to report that registration has completed. + // Returns an empty ID if registration failed. + using RegistrationCallback = base::Callback<void(const std::string&)>; + + static const char kCopresenceAppId[]; + static const char kCopresenceSenderId[]; + static const char kGcmMessageKey[]; + + // |gcm_driver| is required, but may disappear if we get a ShutdownHandler() + // call first. |directive_handler| must outlive us. The caller owns both. + GCMHandler(gcm::GCMDriver* gcm_driver, + DirectiveHandler* directive_handler); + ~GCMHandler() override; + + // Request the GCM ID. It may be returned now or later, via the callback. + void GetGcmId(const RegistrationCallback& callback); + + // GCMAppHandler overrides + void ShutdownHandler() override; + void OnMessage(const std::string& app_id, + const gcm::GCMClient::IncomingMessage& message) override; + void OnMessagesDeleted(const std::string& app_id) override; + void OnSendError( + const std::string& /* app_id */, + const gcm::GCMClient::SendErrorDetails& /* send_error_details */) + override; + void OnSendAcknowledged(const std::string& /* app_id */, + const std::string& /* message_id */) override; + bool CanHandle(const std::string& app_id) const override; + + private: + // GCM Registration has finished. Notify clients as appropriate. + void RegistrationComplete(const std::string& registration_id, + gcm::GCMClient::Result result); + + gcm::GCMDriver* driver_; + DirectiveHandler* const directive_handler_; + + // TODO(ckehoe): Change this to a CancelableCallback. + base::Callback<void(const std::string&, + gcm::GCMClient::Result)> registration_callback_; + + std::string gcm_id_; + std::vector<RegistrationCallback> pending_id_requests_; + + DISALLOW_COPY_AND_ASSIGN(GCMHandler); +}; + +} // namespace copresence + +#endif // COMPONENTS_COPRESENCE_HANDLERS_GCM_HANDLER_H_ diff --git a/components/copresence/handlers/gcm_handler_unittest.cc b/components/copresence/handlers/gcm_handler_unittest.cc new file mode 100644 index 0000000..ee36291 --- /dev/null +++ b/components/copresence/handlers/gcm_handler_unittest.cc @@ -0,0 +1,71 @@ +// 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 "components/copresence/handlers/gcm_handler.h" + +#include "base/base64.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "components/copresence/proto/push_message.pb.h" +#include "components/copresence/test/fake_directive_handler.h" +#include "components/gcm_driver/fake_gcm_driver.h" +#include "components/gcm_driver/gcm_client.h" +#include "testing/gmock/include/gmock/gmock.h" + +using gcm::GCMClient; + +namespace { + +// TODO(ckehoe): Move this to a central place. +std::string ToUrlSafe(std::string token) { + base::ReplaceChars(token, "+", "-", &token); + base::ReplaceChars(token, "/", "_", &token); + return token; +} + +} // namespace + + +namespace copresence { + +class GCMHandlerTest : public testing::Test { + public: + GCMHandlerTest() + : driver_(new gcm::FakeGCMDriver), + directive_handler_(new FakeDirectiveHandler), + gcm_handler_(driver_.get(), directive_handler_.get()) { + } + + protected: + scoped_ptr<gcm::GCMDriver> driver_; + scoped_ptr<FakeDirectiveHandler> directive_handler_; + GCMHandler gcm_handler_; +}; + +TEST_F(GCMHandlerTest, OnMessage) { + // Create a PushMessage. + PushMessage push_message; + push_message.set_type(PushMessage::REPORT); + Report* report = push_message.mutable_report(); + report->add_directive()->set_subscription_id("subscription 1"); + report->add_directive()->set_subscription_id("subscription 2"); + + // Encode it. + std::string serialized_proto; + std::string encoded_proto; + push_message.SerializeToString(&serialized_proto); + base::Base64Encode(serialized_proto, &encoded_proto); + + // Send it in a GCM message. + GCMClient::IncomingMessage gcm_message; + gcm_message.data[GCMHandler::kGcmMessageKey] = ToUrlSafe(encoded_proto); + gcm_handler_.OnMessage(GCMHandler::kCopresenceAppId, gcm_message); + + // Check that the correct directives were passed along. + EXPECT_THAT(directive_handler_->added_directives(), + testing::ElementsAre("subscription 1", "subscription 2")); +} + +} // namespace copresence + diff --git a/components/copresence/proto/BUILD.gn b/components/copresence/proto/BUILD.gn index 87812dd..d7cd331 100644 --- a/components/copresence/proto/BUILD.gn +++ b/components/copresence/proto/BUILD.gn @@ -11,6 +11,7 @@ proto_library("proto") { "data.proto", "enums.proto", "identity.proto", + "push_message.proto", "rpcs.proto", ] } diff --git a/components/copresence/proto/data.proto b/components/copresence/proto/data.proto index ef8e0ee..b7acb42e 100644 --- a/components/copresence/proto/data.proto +++ b/components/copresence/proto/data.proto @@ -15,6 +15,10 @@ message Status { } message PushServiceRegistration { optional PushService service = 1; + optional GcmRegistration gcm_registration = 2; +} +message GcmRegistration { + optional string device_token = 1; } message DeviceIdentifiers { optional int32 ulr_device_id = 1; diff --git a/components/copresence/proto/push_message.proto b/components/copresence/proto/push_message.proto new file mode 100644 index 0000000..239e885 --- /dev/null +++ b/components/copresence/proto/push_message.proto @@ -0,0 +1,20 @@ +syntax = "proto2"; +package copresence; +option optimize_for = LITE_RUNTIME; +import "data.proto"; +message PushMessage { + enum Type { + TYPE_UNKNOWN = 0; + SYNC_SETTINGS = 3; + OBTAIN_MAC = 5; + NOTIFY_MAC = 6; + TEST = 7; + REPORT = 8; + }; + optional Type type = 1; + optional Report report = 9; +} +message Report { + repeated Directive directive = 1; + repeated SubscribedMessage subscribed_message = 2; +} diff --git a/components/copresence/public/copresence_delegate.h b/components/copresence/public/copresence_delegate.h index b8a01bf..e138d66 100644 --- a/components/copresence/public/copresence_delegate.h +++ b/components/copresence/public/copresence_delegate.h @@ -10,6 +10,10 @@ #include "base/callback_forward.h" +namespace gcm { +class GCMDriver; +} + namespace net { class URLRequestContextGetter; } @@ -49,6 +53,10 @@ class CopresenceDelegate { // Thw WhispernetClient must outlive the CopresenceManager. virtual WhispernetClient* GetWhispernetClient() = 0; + + // Clients may optionally provide a GCMDriver to receive messages from. + // If no driver is available, this can return null. + virtual gcm::GCMDriver* GetGCMDriver() = 0; }; } // namespace copresence diff --git a/components/copresence/rpc/rpc_handler.cc b/components/copresence/rpc/rpc_handler.cc index 7c751ab..78bd958 100644 --- a/components/copresence/rpc/rpc_handler.cc +++ b/components/copresence/rpc/rpc_handler.cc @@ -21,6 +21,7 @@ #include "components/copresence/copresence_switches.h" #include "components/copresence/handlers/directive_handler.h" +#include "components/copresence/handlers/gcm_handler.h" #include "components/copresence/proto/codes.pb.h" #include "components/copresence/proto/data.pb.h" #include "components/copresence/proto/rpcs.pb.h" @@ -38,11 +39,15 @@ using google::protobuf::RepeatedPtrField; const char RpcHandler::kReportRequestRpcName[] = "report"; -// Number of characters of suffix to log for auth tokens -const int kTokenSuffix = 5; - namespace { +const int kTokenLoggingSuffix = 5; +const int kInvalidTokenExpiryTimeMs = 10 * 60 * 1000; // 10 minutes. +const int kMaxInvalidTokens = 10000; +const char kRegisterDeviceRpcName[] = "registerdevice"; +const char kDefaultCopresenceServer[] = + "https://www.googleapis.com/copresence/v2/copresence"; + // UrlSafe is defined as: // '/' represented by a '_' and '+' represented by a '-' // TODO(rkc): Move this to the wrapper. @@ -52,11 +57,6 @@ std::string ToUrlSafe(std::string token) { return token; } -const int kInvalidTokenExpiryTimeMs = 10 * 60 * 1000; // 10 minutes. -const int kMaxInvalidTokens = 10000; -const char kRegisterDeviceRpcName[] = "registerdevice"; -const char kDefaultCopresenceServer[] = - "https://www.googleapis.com/copresence/v2/copresence"; // Logging @@ -94,6 +94,16 @@ bool ReportErrorLogged(const ReportResponse& response) { return result; } +const std::string LoggingStrForToken(const std::string& auth_token) { + if (auth_token.empty()) + return "anonymous"; + + std::string token_suffix = auth_token.substr( + auth_token.length() - kTokenLoggingSuffix, kTokenLoggingSuffix); + return base::StringPrintf("token ...%s", token_suffix.c_str()); +} + + // Request construction // TODO(ckehoe): Move these into a separate file? @@ -149,14 +159,6 @@ void AddTokenToRequest(const AudioToken& token, ReportRequest* request) { signals->set_observed_time_millis(base::Time::Now().ToJsTime()); } -const std::string LoggingStrForToken(const std::string& auth_token) { - std::string token_str = auth_token.empty() ? "anonymous" : - base::StringPrintf("token ...%s", - auth_token.substr(auth_token.length() - kTokenSuffix, - kTokenSuffix).c_str()); - return token_str; -} - } // namespace @@ -164,25 +166,33 @@ const std::string LoggingStrForToken(const std::string& auth_token) { RpcHandler::RpcHandler(CopresenceDelegate* delegate, DirectiveHandler* directive_handler, + GCMHandler* gcm_handler, const PostCallback& server_post_callback) : delegate_(delegate), directive_handler_(directive_handler), + gcm_handler_(gcm_handler), server_post_callback_(server_post_callback), invalid_audio_token_cache_( base::TimeDelta::FromMilliseconds(kInvalidTokenExpiryTimeMs), kMaxInvalidTokens) { DCHECK(delegate_); DCHECK(directive_handler_); + // |gcm_handler_| is optional. if (server_post_callback_.is_null()) { server_post_callback_ = base::Bind(&RpcHandler::SendHttpPost, base::Unretained(this)); } + + if (gcm_handler_) { + gcm_handler_->GetGcmId( + base::Bind(&RpcHandler::RegisterGcmId, base::Unretained(this))); + } } RpcHandler::~RpcHandler() { - // Do not use |directive_handler_| here. - // It will already have been destructed. + // Do not use |directive_handler_| or |gcm_handler_| here. + // They will already have been destructed. for (HttpPost* post : pending_posts_) delete post; } @@ -258,7 +268,8 @@ void RpcHandler::ReportTokens(const std::vector<AudioToken>& tokens) { } } -// Private methods + +// Private functions. RpcHandler::PendingRequest::PendingRequest(scoped_ptr<ReportRequest> report, const std::string& app_id, @@ -275,11 +286,17 @@ void RpcHandler::RegisterForToken(const std::string& auth_token) { DVLOG(2) << "Sending " << LoggingStrForToken(auth_token) << " registration to server."; - // Mark registration as in progress. - device_id_by_auth_token_[auth_token] = ""; - scoped_ptr<RegisterDeviceRequest> request(new RegisterDeviceRequest); - request->mutable_push_service()->set_service(PUSH_SERVICE_NONE); + + // Add a GCM ID for authenticated registration, if we have one. + if (auth_token.empty() || gcm_id_.empty()) { + request->mutable_push_service()->set_service(PUSH_SERVICE_NONE); + } else { + DVLOG(2) << "Registering GCM ID with " << LoggingStrForToken(auth_token); + request->mutable_push_service()->set_service(GCM); + request->mutable_push_service()->mutable_gcm_registration() + ->set_device_token(gcm_id_); + } // Only identify as a Chrome device if we're in anonymous mode. // Authenticated calls come from a "GAIA device". @@ -288,18 +305,28 @@ void RpcHandler::RegisterForToken(const std::string& auth_token) { request->mutable_device_identifiers()->mutable_registrant(); identity->set_type(CHROME); identity->set_chrome_id(base::GenerateGUID()); + + // Since we're generating a new "Chrome ID" here, + // we need to make sure this isn't a duplicate registration. + DCHECK_EQ(0u, device_id_by_auth_token_.count(std::string())) + << "Attempted anonymous re-registration"; } + bool gcm_pending = !auth_token.empty() && gcm_handler_ && gcm_id_.empty(); SendServerRequest( kRegisterDeviceRpcName, - std::string(), // device ID + // This will have the side effect of populating an empty device ID + // for this auth token in the map. This is what we want, + // to mark registration as being in progress. + device_id_by_auth_token_[auth_token], std::string(), // app ID auth_token, request.Pass(), base::Bind(&RpcHandler::RegisterResponseHandler, // On destruction, this request will be cancelled. base::Unretained(this), - auth_token)); + auth_token, + gcm_pending)); } void RpcHandler::ProcessQueuedRequests(const std::string& auth_token) { @@ -343,8 +370,41 @@ void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request, StatusCallback()); } +// Store a GCM ID and send it to the server if needed. The constructor passes +// this callback to the GCMHandler to receive the ID whenever it's ready. +// It may be returned immediately, if the ID is cached, or require a server +// round-trip. This ID must then be passed along to the copresence server. +// There are a few ways this can happen for each auth token: +// +// 1. The GCM ID is available when we first register, and is passed along +// with the RegisterDeviceRequest. +// +// 2. The GCM ID becomes available after the RegisterDeviceRequest has +// completed. Then the loop in this function will invoke RegisterForToken() +// again to pass on the ID. +// +// 3. The GCM ID becomes available after the RegisterDeviceRequest is sent, +// but before it completes. In this case, the gcm_pending flag is passed +// through to the RegisterResponseHandler, which invokes RegisterForToken() +// again to pass on the ID. The loop here must skip pending registrations, +// as the device ID will be empty. +// +// TODO(ckehoe): Add tests for these scenarios. +void RpcHandler::RegisterGcmId(const std::string& gcm_id) { + gcm_id_ = gcm_id; + if (!gcm_id.empty()) { + for (const auto& registration : device_id_by_auth_token_) { + const std::string& auth_token = registration.first; + const std::string& device_id = registration.second; + if (!auth_token.empty() && !device_id.empty()) + RegisterForToken(auth_token); + } + } +} + void RpcHandler::RegisterResponseHandler( const std::string& auth_token, + bool gcm_pending, HttpPost* completed_post, int http_status_code, const std::string& response_data) { @@ -371,6 +431,10 @@ void RpcHandler::RegisterResponseHandler( device_id_by_auth_token_[auth_token] = device_id; DVLOG(2) << LoggingStrForToken(auth_token) << " device registration successful. Id: " << device_id; + + // If we have a GCM ID now, and didn't before, pass it on to the server. + if (gcm_pending && !gcm_id_.empty()) + RegisterForToken(auth_token); } // Send or fail requests on this auth token. @@ -519,7 +583,8 @@ RequestHeader* RpcHandler::CreateRequestHeader( CreateVersion(client_name, std::string())); } header->set_current_time_millis(base::Time::Now().ToJsTime()); - header->set_registered_device_id(device_id); + if (!device_id.empty()) + header->set_registered_device_id(device_id); DeviceFingerprint* fingerprint = new DeviceFingerprint; fingerprint->set_platform_version(delegate_->GetPlatformVersionString()); diff --git a/components/copresence/rpc/rpc_handler.h b/components/copresence/rpc/rpc_handler.h index e2e999f..555a8b1 100644 --- a/components/copresence/rpc/rpc_handler.h +++ b/components/copresence/rpc/rpc_handler.h @@ -22,6 +22,7 @@ namespace copresence { struct AudioToken; class CopresenceDelegate; class DirectiveHandler; +class GCMHandler; class HttpPost; class ReportRequest; class RequestHeader; @@ -65,6 +66,7 @@ class RpcHandler { // |server_post_callback| should be set only by tests. RpcHandler(CopresenceDelegate* delegate, DirectiveHandler* directive_handler, + GCMHandler* gcm_handler, const PostCallback& server_post_callback = PostCallback()); virtual ~RpcHandler(); @@ -107,8 +109,12 @@ class RpcHandler { void SendReportRequest(scoped_ptr<ReportRequest> request, const std::string& auth_token); + // Store a GCM ID and send it to the server if needed. + void RegisterGcmId(const std::string& gcm_id); + // Server call response handlers. void RegisterResponseHandler(const std::string& auth_token, + bool gcm_pending, HttpPost* completed_post, int http_status_code, const std::string& response_data); @@ -152,8 +158,9 @@ class RpcHandler { const PostCleanupCallback& callback); // These belong to the caller. - CopresenceDelegate* delegate_; - DirectiveHandler* directive_handler_; + CopresenceDelegate* const delegate_; + DirectiveHandler* const directive_handler_; + GCMHandler* const gcm_handler_; PostCallback server_post_callback_; @@ -161,6 +168,7 @@ class RpcHandler { TimedMap<std::string, bool> invalid_audio_token_cache_; std::map<std::string, std::string> device_id_by_auth_token_; std::set<HttpPost*> pending_posts_; + std::string gcm_id_; DISALLOW_COPY_AND_ASSIGN(RpcHandler); }; diff --git a/components/copresence/rpc/rpc_handler_unittest.cc b/components/copresence/rpc/rpc_handler_unittest.cc index 7efe2f6..62fd56a 100644 --- a/components/copresence/rpc/rpc_handler_unittest.cc +++ b/components/copresence/rpc/rpc_handler_unittest.cc @@ -17,6 +17,7 @@ #include "components/copresence/proto/data.pb.h" #include "components/copresence/proto/enums.pb.h" #include "components/copresence/proto/rpcs.pb.h" +#include "components/copresence/test/fake_directive_handler.h" #include "components/copresence/test/stub_whispernet_client.h" #include "net/http/http_status_code.h" #include "testing/gmock/include/gmock/gmock.h" @@ -43,45 +44,6 @@ void CreateSubscribedMessage(const std::vector<std::string>& subscription_ids, } } -// TODO(ckehoe): Make DirectiveHandler an interface. -class FakeDirectiveHandler final : public DirectiveHandler { - public: - FakeDirectiveHandler() : DirectiveHandler(nullptr) {} - - const std::vector<std::string>& added_directives() const { - return added_directives_; - } - - const std::vector<std::string>& removed_directives() const { - return removed_directives_; - } - - void Start(WhispernetClient* /* whispernet_client */, - const TokensCallback& /* tokens_cb */) override { - NOTREACHED(); - } - - void AddDirective(const Directive& directive) override { - added_directives_.push_back(directive.subscription_id()); - } - - void RemoveDirectives(const std::string& op_id) override { - removed_directives_.push_back(op_id); - } - - const std::string GetCurrentAudioToken(AudioType type) const override { - return type == AUDIBLE ? "current audible" : "current inaudible"; - } - - bool IsAudioTokenHeard(AudioType type) const override { return true; } - - private: - std::vector<std::string> added_directives_; - std::vector<std::string> removed_directives_; - - DISALLOW_COPY_AND_ASSIGN(FakeDirectiveHandler); -}; - } // namespace class RpcHandlerTest : public testing::Test, public CopresenceDelegate { @@ -90,6 +52,7 @@ class RpcHandlerTest : public testing::Test, public CopresenceDelegate { : whispernet_client_(new StubWhispernetClient), rpc_handler_(this, &directive_handler_, + nullptr, base::Bind(&RpcHandlerTest::CaptureHttpPost, base::Unretained(this))), status_(SUCCESS) {} @@ -123,6 +86,11 @@ class RpcHandlerTest : public testing::Test, public CopresenceDelegate { return whispernet_client_.get(); } + // TODO(ckehoe): Add GCM tests. + gcm::GCMDriver* GetGCMDriver() override { + return nullptr; + } + protected: // Send test input to RpcHandler @@ -140,7 +108,7 @@ class RpcHandlerTest : public testing::Test, public CopresenceDelegate { std::string serialized_response; response.SerializeToString(&serialized_response); rpc_handler_.RegisterResponseHandler( - auth_token, nullptr, net::HTTP_OK, serialized_response); + auth_token, false, nullptr, net::HTTP_OK, serialized_response); } void SendReport(scoped_ptr<ReportRequest> request, diff --git a/components/copresence/test/fake_directive_handler.cc b/components/copresence/test/fake_directive_handler.cc new file mode 100644 index 0000000..810b227 --- /dev/null +++ b/components/copresence/test/fake_directive_handler.cc @@ -0,0 +1,32 @@ +// 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 "components/copresence/test/fake_directive_handler.h" + +#include "components/copresence/proto/data.pb.h" + +namespace copresence { + +FakeDirectiveHandler::FakeDirectiveHandler() : DirectiveHandler(nullptr) {} + +FakeDirectiveHandler::~FakeDirectiveHandler() {} + +void FakeDirectiveHandler::AddDirective(const Directive& directive) { + added_directives_.push_back(directive.subscription_id()); +} + +void FakeDirectiveHandler::RemoveDirectives(const std::string& op_id) { + removed_directives_.push_back(op_id); +} + +const std::string FakeDirectiveHandler::GetCurrentAudioToken(AudioType type) + const { + return type == AUDIBLE ? "current audible" : "current inaudible"; +} + +bool FakeDirectiveHandler::IsAudioTokenHeard(AudioType type) const { + return true; +} + +} // namespace copresence diff --git a/components/copresence/test/fake_directive_handler.h b/components/copresence/test/fake_directive_handler.h new file mode 100644 index 0000000..f7b591c --- /dev/null +++ b/components/copresence/test/fake_directive_handler.h @@ -0,0 +1,49 @@ +// 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 COMPONENTS_COPRESENCE_TEST_FAKE_DIRECTIVE_HANDLER_H_ +#define COMPONENTS_COPRESENCE_TEST_FAKE_DIRECTIVE_HANDLER_H_ + +#include <string> +#include <vector> + +#include "components/copresence/handlers/directive_handler.h" + +namespace copresence { + +// TODO(ckehoe): Make DirectiveHandler an interface. +class FakeDirectiveHandler final : public DirectiveHandler { + public: + FakeDirectiveHandler(); + ~FakeDirectiveHandler() override; + + const std::vector<std::string>& added_directives() const { + return added_directives_; + } + + const std::vector<std::string>& removed_directives() const { + return removed_directives_; + } + + void Start(WhispernetClient* /* whispernet_client */, + const TokensCallback& /* tokens_cb */) override {} + + void AddDirective(const Directive& directive) override; + + void RemoveDirectives(const std::string& op_id) override; + + const std::string GetCurrentAudioToken(AudioType type) const override; + + bool IsAudioTokenHeard(AudioType type) const override; + + private: + std::vector<std::string> added_directives_; + std::vector<std::string> removed_directives_; + + DISALLOW_COPY_AND_ASSIGN(FakeDirectiveHandler); +}; + +} // namespace copresence + +#endif // COMPONENTS_COPRESENCE_TEST_FAKE_DIRECTIVE_HANDLER_H_ |