diff --git a/components/gcm_driver/gcm_client_impl.cc b/components/gcm_driver/gcm_client_impl.cc index a60df6915aa6..2d05aea939a5 100644 --- a/components/gcm_driver/gcm_client_impl.cc +++ b/components/gcm_driver/gcm_client_impl.cc @@ -341,6 +341,7 @@ void GCMClientImpl::InitializeMCSClient( network_session_, net_log_.net_log(), &recorder_); + connection_factory_->SetConnectionListener(this); mcs_client_ = internals_builder_->BuildMCSClient( chrome_build_info_.version, clock_.get(), @@ -711,8 +712,9 @@ GCMClient::GCMStatistics GCMClientImpl::GetStatistics() const { stats.is_recording = recorder_.is_recording(); stats.gcm_client_state = GetStateString(); stats.connection_client_created = mcs_client_.get() != NULL; + if (connection_factory_.get()) + stats.connection_state = connection_factory_->GetConnectionStateString(); if (mcs_client_.get()) { - stats.connection_state = mcs_client_->GetStateString(); stats.send_queue_size = mcs_client_->GetSendQueueSize(); stats.resend_queue_size = mcs_client_->GetResendQueueSize(); } @@ -731,6 +733,17 @@ void GCMClientImpl::OnActivityRecorded() { delegate_->OnActivityRecorded(); } +void GCMClientImpl::OnConnected(const GURL& current_server, + const net::IPEndPoint& ip_endpoint) { + // TODO(zea): inform GCMClient::Delegate and app handlers as well. + delegate_->OnActivityRecorded(); +} + +void GCMClientImpl::OnDisconnected() { + // TODO(zea): inform GCMClient::Delegate and app handlers as well. + delegate_->OnActivityRecorded(); +} + void GCMClientImpl::OnMessageReceivedFromMCS(const gcm::MCSMessage& message) { switch (message.tag()) { case kLoginResponseTag: diff --git a/components/gcm_driver/gcm_client_impl.h b/components/gcm_driver/gcm_client_impl.h index e74a8aa2b135..464b70709963 100644 --- a/components/gcm_driver/gcm_client_impl.h +++ b/components/gcm_driver/gcm_client_impl.h @@ -74,12 +74,13 @@ class GCMInternalsBuilder { // Checkins. It also allows for registering user delegates that host // applications that send and receive messages. class GCMClientImpl - : public GCMClient, public GCMStatsRecorder::Delegate { + : public GCMClient, public GCMStatsRecorder::Delegate, + public ConnectionFactory::ConnectionListener { public: explicit GCMClientImpl(scoped_ptr internals_builder); virtual ~GCMClientImpl(); - // Overridden from GCMClient: + // GCMClient implementation. virtual void Initialize( const ChromeBuildInfo& chrome_build_info, const base::FilePath& store_path, @@ -101,8 +102,15 @@ class GCMClientImpl virtual void SetRecording(bool recording) OVERRIDE; virtual void ClearActivityLogs() OVERRIDE; virtual GCMStatistics GetStatistics() const OVERRIDE; + + // GCMStatsRecorder::Delegate implemenation. virtual void OnActivityRecorded() OVERRIDE; + // ConnectionFactory::ConnectionListener implementation. + virtual void OnConnected(const GURL& current_server, + const net::IPEndPoint& ip_endpoint) OVERRIDE; + virtual void OnDisconnected() OVERRIDE; + private: // State representation of the GCMClient. // Any change made to this enum should have corresponding change in the diff --git a/google_apis/gcm/engine/connection_factory.cc b/google_apis/gcm/engine/connection_factory.cc index 016e1e2b89c7..a4bffd170767 100644 --- a/google_apis/gcm/engine/connection_factory.cc +++ b/google_apis/gcm/engine/connection_factory.cc @@ -6,6 +6,9 @@ namespace gcm { +ConnectionFactory::ConnectionListener::ConnectionListener() {} +ConnectionFactory::ConnectionListener::~ConnectionListener() {} + ConnectionFactory::ConnectionFactory() {} ConnectionFactory::~ConnectionFactory() {} diff --git a/google_apis/gcm/engine/connection_factory.h b/google_apis/gcm/engine/connection_factory.h index 393d6a9ca247..1db02a6ca137 100644 --- a/google_apis/gcm/engine/connection_factory.h +++ b/google_apis/gcm/engine/connection_factory.h @@ -5,10 +5,18 @@ #ifndef GOOGLE_APIS_GCM_ENGINE_CONNECTION_FACTORY_H_ #define GOOGLE_APIS_GCM_ENGINE_CONNECTION_FACTORY_H_ +#include + #include "base/time/time.h" #include "google_apis/gcm/base/gcm_export.h" #include "google_apis/gcm/engine/connection_handler.h" +class GURL; + +namespace net { +class IPEndPoint; +} + namespace mcs_proto { class LoginRequest; } @@ -36,6 +44,22 @@ class GCM_EXPORT ConnectionFactory { CONNECTION_RESET_COUNT, }; + // Listener interface to be notified of endpoint connection events. + class GCM_EXPORT ConnectionListener { + public: + ConnectionListener(); + virtual ~ConnectionListener(); + + // Notifies the listener that GCM has performed a handshake with and is now + // actively connected to |current_server|. |ip_endpoint| is the resolved + // ip address/port through which the connection is being made. + virtual void OnConnected(const GURL& current_server, + const net::IPEndPoint& ip_endpoint) = 0; + + // Notifies the listener that the connection has been interrupted. + virtual void OnDisconnected() = 0; + }; + ConnectionFactory(); virtual ~ConnectionFactory(); @@ -66,6 +90,9 @@ class GCM_EXPORT ConnectionFactory { // connection. virtual bool IsEndpointReachable() const = 0; + // Returns a debug string describing the connection state. + virtual std::string GetConnectionStateString() const = 0; + // If in backoff, the time at which the next retry will be made. Otherwise, // a null time, indicating either no attempt to connect has been made or no // backoff is in progress. @@ -76,6 +103,11 @@ class GCM_EXPORT ConnectionFactory { // If the last connection was made within kConnectionResetWindowSecs, the old // backoff is restored, else a new backoff kicks off. virtual void SignalConnectionReset(ConnectionResetReason reason) = 0; + + // Sets the current connection listener. Only one listener is supported at a + // time, and the listener must either outlive the connection factory or + // call SetConnectionListener(NULL) upon destruction. + virtual void SetConnectionListener(ConnectionListener* listener) = 0; }; } // namespace gcm diff --git a/google_apis/gcm/engine/connection_factory_impl.cc b/google_apis/gcm/engine/connection_factory_impl.cc index bab0a95d90e6..9f71b9015ff3 100644 --- a/google_apis/gcm/engine/connection_factory_impl.cc +++ b/google_apis/gcm/engine/connection_factory_impl.cc @@ -59,6 +59,7 @@ ConnectionFactoryImpl::ConnectionFactoryImpl( waiting_for_backoff_(false), logging_in_(false), recorder_(recorder), + listener_(NULL), weak_ptr_factory_(this) { DCHECK_GE(mcs_endpoints_.size(), 1U); } @@ -140,6 +141,18 @@ bool ConnectionFactoryImpl::IsEndpointReachable() const { return connection_handler_ && connection_handler_->CanSendMessage(); } +std::string ConnectionFactoryImpl::GetConnectionStateString() const { + if (IsEndpointReachable()) + return "CONNECTED"; + if (logging_in_) + return "LOGGING IN"; + if (connecting_) + return "CONNECTING"; + if (waiting_for_backoff_) + return "WAITING FOR BACKOFF"; + return "NOT CONNECTED"; +} + void ConnectionFactoryImpl::SignalConnectionReset( ConnectionResetReason reason) { // A failure can trigger multiple resets, so no need to do anything if a @@ -149,6 +162,9 @@ void ConnectionFactoryImpl::SignalConnectionReset( return; } + if (listener_) + listener_->OnDisconnected(); + UMA_HISTOGRAM_ENUMERATION("GCM.ConnectionResetReason", reason, CONNECTION_RESET_COUNT); @@ -202,6 +218,11 @@ void ConnectionFactoryImpl::SignalConnectionReset( Connect(); } +void ConnectionFactoryImpl::SetConnectionListener( + ConnectionListener* listener) { + listener_ = listener; +} + base::TimeTicks ConnectionFactoryImpl::NextRetryAttempt() const { if (!backoff_entry_) return base::TimeTicks(); @@ -234,6 +255,18 @@ GURL ConnectionFactoryImpl::GetCurrentEndpoint() const { return mcs_endpoints_[next_endpoint_]; } +net::IPEndPoint ConnectionFactoryImpl::GetPeerIP() { + if (!socket_handle_.socket()) + return net::IPEndPoint(); + + net::IPEndPoint ip_endpoint; + int result = socket_handle_.socket()->GetPeerAddress(&ip_endpoint); + if (result != net::OK) + return net::IPEndPoint(); + + return ip_endpoint; +} + void ConnectionFactoryImpl::ConnectImpl() { DCHECK(!IsEndpointReachable()); DCHECK(!socket_handle_.socket()); @@ -348,6 +381,9 @@ void ConnectionFactoryImpl::ConnectionHandlerCallback(int result) { previous_backoff_.swap(backoff_entry_); backoff_entry_->Reset(); logging_in_ = false; + + if (listener_) + listener_->OnConnected(GetCurrentEndpoint(), GetPeerIP()); } // This has largely been copied from diff --git a/google_apis/gcm/engine/connection_factory_impl.h b/google_apis/gcm/engine/connection_factory_impl.h index b52ef2dc8688..31cecf688414 100644 --- a/google_apis/gcm/engine/connection_factory_impl.h +++ b/google_apis/gcm/engine/connection_factory_impl.h @@ -48,8 +48,10 @@ class GCM_EXPORT ConnectionFactoryImpl : virtual ConnectionHandler* GetConnectionHandler() const OVERRIDE; virtual void Connect() OVERRIDE; virtual bool IsEndpointReachable() const OVERRIDE; + virtual std::string GetConnectionStateString() const OVERRIDE; virtual base::TimeTicks NextRetryAttempt() const OVERRIDE; virtual void SignalConnectionReset(ConnectionResetReason reason) OVERRIDE; + virtual void SetConnectionListener(ConnectionListener* listener) OVERRIDE; // NetworkChangeNotifier observer implementations. virtual void OnConnectionTypeChanged( @@ -61,6 +63,10 @@ class GCM_EXPORT ConnectionFactoryImpl : // attempt will be made. GURL GetCurrentEndpoint() const; + // Returns the IPEndpoint to which the factory is currently connected. If no + // connection is active, returns an empty IPEndpoint. + net::IPEndPoint GetPeerIP(); + protected: // Implementation of Connect(..). If not in backoff, uses |login_request_| // in attempting a connection/handshake. On connection/handshake failure, goes @@ -161,6 +167,9 @@ class GCM_EXPORT ConnectionFactoryImpl : // Recorder that records GCM activities for debugging purpose. Not owned. GCMStatsRecorder* recorder_; + // Listener for connection change events. + ConnectionListener* listener_; + base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(ConnectionFactoryImpl); diff --git a/google_apis/gcm/engine/connection_factory_impl_unittest.cc b/google_apis/gcm/engine/connection_factory_impl_unittest.cc index 1d21943e29d4..ef1618329882 100644 --- a/google_apis/gcm/engine/connection_factory_impl_unittest.cc +++ b/google_apis/gcm/engine/connection_factory_impl_unittest.cc @@ -254,27 +254,40 @@ void TestConnectionFactoryImpl::SetDelayLogin(bool delay_login) { fake_handler_->set_fail_login(delay_login_); } -class ConnectionFactoryImplTest : public testing::Test { +} // namespace + +class ConnectionFactoryImplTest + : public testing::Test, + public ConnectionFactory::ConnectionListener { public: ConnectionFactoryImplTest(); virtual ~ConnectionFactoryImplTest(); TestConnectionFactoryImpl* factory() { return &factory_; } + GURL& connected_server() { return connected_server_; } void WaitForConnections(); + // ConnectionFactory::ConnectionListener + virtual void OnConnected(const GURL& current_server, + const net::IPEndPoint& ip_endpoint) OVERRIDE; + virtual void OnDisconnected() OVERRIDE; + private: void ConnectionsComplete(); TestConnectionFactoryImpl factory_; base::MessageLoop message_loop_; scoped_ptr run_loop_; + + GURL connected_server_; }; ConnectionFactoryImplTest::ConnectionFactoryImplTest() : factory_(base::Bind(&ConnectionFactoryImplTest::ConnectionsComplete, base::Unretained(this))), run_loop_(new base::RunLoop()) { + factory()->SetConnectionListener(this); factory()->Initialize( ConnectionFactory::BuildLoginRequestCallback(), ConnectionHandler::ProtoReceivedCallback(), @@ -293,11 +306,22 @@ void ConnectionFactoryImplTest::ConnectionsComplete() { run_loop_->Quit(); } +void ConnectionFactoryImplTest::OnConnected( + const GURL& current_server, + const net::IPEndPoint& ip_endpoint) { + connected_server_ = current_server; +} + +void ConnectionFactoryImplTest::OnDisconnected() { + connected_server_ = GURL(); +} + // Verify building a connection handler works. TEST_F(ConnectionFactoryImplTest, Initialize) { ConnectionHandler* handler = factory()->GetConnectionHandler(); ASSERT_TRUE(handler); EXPECT_FALSE(factory()->IsEndpointReachable()); + EXPECT_FALSE(connected_server().is_valid()); } // An initial successful connection should not result in backoff. @@ -307,6 +331,7 @@ TEST_F(ConnectionFactoryImplTest, ConnectSuccess) { EXPECT_TRUE(factory()->NextRetryAttempt().is_null()); EXPECT_EQ(factory()->GetCurrentEndpoint(), BuildEndpoints()[0]); EXPECT_TRUE(factory()->IsEndpointReachable()); + EXPECT_TRUE(connected_server().is_valid()); } // A connection failure should result in backoff, and attempting the fallback @@ -317,6 +342,7 @@ TEST_F(ConnectionFactoryImplTest, ConnectFail) { EXPECT_FALSE(factory()->NextRetryAttempt().is_null()); EXPECT_EQ(factory()->GetCurrentEndpoint(), BuildEndpoints()[1]); EXPECT_FALSE(factory()->IsEndpointReachable()); + EXPECT_FALSE(connected_server().is_valid()); } // A connection success after a failure should reset backoff. @@ -326,6 +352,7 @@ TEST_F(ConnectionFactoryImplTest, FailThenSucceed) { factory()->Connect(); WaitForConnections(); EXPECT_FALSE(factory()->IsEndpointReachable()); + EXPECT_FALSE(connected_server().is_valid()); base::TimeTicks retry_time = factory()->NextRetryAttempt(); EXPECT_FALSE(retry_time.is_null()); EXPECT_GE((retry_time - connect_time).InMilliseconds(), CalculateBackoff(1)); @@ -333,6 +360,7 @@ TEST_F(ConnectionFactoryImplTest, FailThenSucceed) { WaitForConnections(); EXPECT_TRUE(factory()->NextRetryAttempt().is_null()); EXPECT_TRUE(factory()->IsEndpointReachable()); + EXPECT_TRUE(connected_server().is_valid()); } // Multiple connection failures should retry with an exponentially increasing @@ -346,6 +374,7 @@ TEST_F(ConnectionFactoryImplTest, MultipleFailuresThenSucceed) { factory()->Connect(); WaitForConnections(); EXPECT_FALSE(factory()->IsEndpointReachable()); + EXPECT_FALSE(connected_server().is_valid()); base::TimeTicks retry_time = factory()->NextRetryAttempt(); EXPECT_FALSE(retry_time.is_null()); EXPECT_GE((retry_time - connect_time).InMilliseconds(), @@ -355,6 +384,7 @@ TEST_F(ConnectionFactoryImplTest, MultipleFailuresThenSucceed) { WaitForConnections(); EXPECT_TRUE(factory()->NextRetryAttempt().is_null()); EXPECT_TRUE(factory()->IsEndpointReachable()); + EXPECT_TRUE(connected_server().is_valid()); } // IP events should trigger canary connections. @@ -408,12 +438,15 @@ TEST_F(ConnectionFactoryImplTest, CanarySucceedsThenDisconnects) { net::NetworkChangeNotifier::CONNECTION_WIFI); WaitForConnections(); EXPECT_TRUE(factory()->IsEndpointReachable()); + EXPECT_TRUE(connected_server().is_valid()); factory()->SetConnectResult(net::OK); factory()->SignalConnectionReset(ConnectionFactory::SOCKET_FAILURE); EXPECT_FALSE(factory()->IsEndpointReachable()); + EXPECT_FALSE(connected_server().is_valid()); WaitForConnections(); EXPECT_TRUE(factory()->IsEndpointReachable()); + EXPECT_TRUE(connected_server().is_valid()); } // Verify that if a canary connects, but hasn't finished the handshake, a @@ -486,6 +519,7 @@ TEST_F(ConnectionFactoryImplTest, SignalResetRestoresBackoff) { factory()->SignalConnectionReset(ConnectionFactory::SOCKET_FAILURE); EXPECT_FALSE(factory()->IsEndpointReachable()); + EXPECT_FALSE(connected_server().is_valid()); EXPECT_NE(retry_time, factory()->NextRetryAttempt()); retry_time = factory()->NextRetryAttempt(); EXPECT_FALSE(retry_time.is_null()); @@ -499,6 +533,7 @@ TEST_F(ConnectionFactoryImplTest, SignalResetRestoresBackoff) { WaitForConnections(); EXPECT_TRUE(factory()->NextRetryAttempt().is_null()); EXPECT_TRUE(factory()->IsEndpointReachable()); + EXPECT_TRUE(connected_server().is_valid()); factory()->SignalConnectionReset(ConnectionFactory::SOCKET_FAILURE); EXPECT_NE(retry_time, factory()->NextRetryAttempt()); @@ -507,7 +542,7 @@ TEST_F(ConnectionFactoryImplTest, SignalResetRestoresBackoff) { EXPECT_GE((retry_time - connect_time).InMilliseconds(), CalculateBackoff(3)); EXPECT_FALSE(factory()->IsEndpointReachable()); + EXPECT_FALSE(connected_server().is_valid()); } -} // namespace } // namespace gcm diff --git a/google_apis/gcm/engine/fake_connection_factory.cc b/google_apis/gcm/engine/fake_connection_factory.cc index 88bbc51be530..51447b9e5a8a 100644 --- a/google_apis/gcm/engine/fake_connection_factory.cc +++ b/google_apis/gcm/engine/fake_connection_factory.cc @@ -41,6 +41,10 @@ bool FakeConnectionFactory::IsEndpointReachable() const { return connection_handler_.get() && connection_handler_->CanSendMessage(); } +std::string FakeConnectionFactory::GetConnectionStateString() const { + return ""; +} + base::TimeTicks FakeConnectionFactory::NextRetryAttempt() const { return base::TimeTicks(); } @@ -53,4 +57,8 @@ void FakeConnectionFactory::SignalConnectionReset( reconnect_pending_ = true; } +void FakeConnectionFactory::SetConnectionListener( + ConnectionListener* listener) { +} + } // namespace gcm diff --git a/google_apis/gcm/engine/fake_connection_factory.h b/google_apis/gcm/engine/fake_connection_factory.h index 393e591e926d..b4f0e884d5d9 100644 --- a/google_apis/gcm/engine/fake_connection_factory.h +++ b/google_apis/gcm/engine/fake_connection_factory.h @@ -27,8 +27,10 @@ class FakeConnectionFactory : public ConnectionFactory { virtual ConnectionHandler* GetConnectionHandler() const OVERRIDE; virtual void Connect() OVERRIDE; virtual bool IsEndpointReachable() const OVERRIDE; + virtual std::string GetConnectionStateString() const OVERRIDE; virtual base::TimeTicks NextRetryAttempt() const OVERRIDE; virtual void SignalConnectionReset(ConnectionResetReason reason) OVERRIDE; + virtual void SetConnectionListener(ConnectionListener* listener) OVERRIDE; // Whether a connection reset has been triggered and is yet to run. bool reconnect_pending() const { return reconnect_pending_; }