From 06c6a9a72eaf2cc5fbcc429127bd2e3f4dfce75d Mon Sep 17 00:00:00 2001 From: isherman Date: Tue, 9 Sep 2014 22:48:21 -0700 Subject: [PATCH] [EasyUnlock] Port Connection class to native code. BUG=410639 TEST=components_unittests R=tengs@chromium.org, cbentzel@chromium.org Review URL: https://codereview.chromium.org/538843002 Cr-Commit-Position: refs/heads/master@{#294114} --- components/components_tests.gyp | 1 + components/proximity_auth.gypi | 5 + components/proximity_auth/BUILD.gn | 7 + components/proximity_auth/DEPS | 1 + components/proximity_auth/connection.cc | 101 ++++++++++ components/proximity_auth/connection.h | 108 ++++++++++ components/proximity_auth/connection_observer.h | 37 ++++ components/proximity_auth/connection_unittest.cc | 242 +++++++++++++++++++++++ components/proximity_auth/wire_message.cc | 29 +++ components/proximity_auth/wire_message.h | 39 ++++ 10 files changed, 570 insertions(+) create mode 100644 components/proximity_auth/connection.cc create mode 100644 components/proximity_auth/connection.h create mode 100644 components/proximity_auth/connection_observer.h create mode 100644 components/proximity_auth/connection_unittest.cc create mode 100644 components/proximity_auth/wire_message.cc create mode 100644 components/proximity_auth/wire_message.h diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 9b25cc15af77..31bdb9a6e883 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -174,6 +174,7 @@ 'precache/core/precache_database_unittest.cc', 'precache/core/precache_fetcher_unittest.cc', 'precache/core/precache_url_table_unittest.cc', + 'proximity_auth/connection_unittest.cc', 'proximity_auth/proximity_auth_system_unittest.cc', 'query_parser/query_parser_unittest.cc', 'query_parser/snippet_unittest.cc', diff --git a/components/proximity_auth.gypi b/components/proximity_auth.gypi index c7b21b2b8104..61efc51e7d87 100644 --- a/components/proximity_auth.gypi +++ b/components/proximity_auth.gypi @@ -14,9 +14,14 @@ '../base/base.gyp:base', ], 'sources': [ + "proximity_auth/connection.cc", + "proximity_auth/connection.h", + "proximity_auth/connection_observer.h", "proximity_auth/proximity_auth_system.cc", "proximity_auth/proximity_auth_system.h", "proximity_auth/remote_device.h", + "proximity_auth/wire_message.cc", + "proximity_auth/wire_message.h", ], }, ], diff --git a/components/proximity_auth/BUILD.gn b/components/proximity_auth/BUILD.gn index 09de32c022f9..1f2540282459 100644 --- a/components/proximity_auth/BUILD.gn +++ b/components/proximity_auth/BUILD.gn @@ -4,9 +4,14 @@ static_library("proximity_auth") { sources = [ + "connection.cc", + "connection.h", + "connection_observer.h", "proximity_auth_system.cc", "proximity_auth_system.h", "remote_device.h", + "wire_message.cc", + "wire_message.h", ] deps = [ @@ -17,12 +22,14 @@ static_library("proximity_auth") { source_set("unit_tests") { testonly = true sources = [ + "connection_unittest.cc", "proximity_auth_system_unittest.cc", ] deps = [ ":proximity_auth", "//base/test:test_support", + "//testing/gmock", "//testing/gtest", ] } diff --git a/components/proximity_auth/DEPS b/components/proximity_auth/DEPS index 48e88750d4a4..8fa9d48d882c 100644 --- a/components/proximity_auth/DEPS +++ b/components/proximity_auth/DEPS @@ -1,2 +1,3 @@ include_rules = [ + "+net", ] diff --git a/components/proximity_auth/connection.cc b/components/proximity_auth/connection.cc new file mode 100644 index 000000000000..5a35e4dd21f0 --- /dev/null +++ b/components/proximity_auth/connection.cc @@ -0,0 +1,101 @@ +// 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/proximity_auth/connection.h" + +#include "base/logging.h" +#include "components/proximity_auth/connection_observer.h" +#include "components/proximity_auth/wire_message.h" + +namespace proximity_auth { + +Connection::Connection(const RemoteDevice& remote_device) + : remote_device_(remote_device), + status_(DISCONNECTED), + is_sending_message_(false) { +} + +Connection::~Connection() { +} + +bool Connection::IsConnected() const { + return status_ == CONNECTED; +} + +void Connection::SendMessage(scoped_ptr message) { + if (!IsConnected()) { + VLOG(1) << "Cannot send message when disconnected."; + return; + } + + if (is_sending_message_) { + VLOG(1) << "Another message is currently in progress."; + return; + } + + is_sending_message_ = true; + SendMessageImpl(message.Pass()); +} + +void Connection::AddObserver(ConnectionObserver* observer) { + observers_.AddObserver(observer); +} + +void Connection::RemoveObserver(ConnectionObserver* observer) { + observers_.RemoveObserver(observer); +} + +void Connection::SetStatus(Status status) { + if (status_ == status) + return; + + received_bytes_.clear(); + + Status old_status = status_; + status_ = status; + FOR_EACH_OBSERVER(ConnectionObserver, + observers_, + OnConnectionStatusChanged(*this, old_status, status_)); +} + +void Connection::OnDidSendMessage(const WireMessage& message, bool success) { + if (!is_sending_message_) { + VLOG(1) << "Send completed, but no message in progress."; + return; + } + + is_sending_message_ = false; + FOR_EACH_OBSERVER( + ConnectionObserver, observers_, OnSendCompleted(*this, message, success)); +} + +void Connection::OnBytesReceived(const std::string& bytes) { + if (!IsConnected()) { + VLOG(1) << "Received bytes, but not connected."; + return; + } + + received_bytes_ += bytes; + if (HasReceivedCompleteMessage()) { + scoped_ptr message = DeserializeWireMessage(); + if (message) { + FOR_EACH_OBSERVER( + ConnectionObserver, observers_, OnMessageReceived(*this, *message)); + } + + // Whether the message was parsed successfully or not, clear the + // |received_bytes_| buffer. + received_bytes_.clear(); + } +} + +bool Connection::HasReceivedCompleteMessage() { + return WireMessage::IsCompleteMessage(received_bytes_); +} + +scoped_ptr Connection::DeserializeWireMessage() { + return WireMessage::Deserialize(received_bytes_); +} + +} // namespace proximity_auth diff --git a/components/proximity_auth/connection.h b/components/proximity_auth/connection.h new file mode 100644 index 000000000000..b904f84ee5bc --- /dev/null +++ b/components/proximity_auth/connection.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. + +#ifndef COMPONENTS_PROXIMITY_AUTH_CONNECTION_H +#define COMPONENTS_PROXIMITY_AUTH_CONNECTION_H + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "components/proximity_auth/remote_device.h" + +namespace proximity_auth { + +class ConnectionObserver; +class WireMessage; + +// Base class representing a connection with a remote device, which is a +// persistent bidirectional channel for sending and receiving wire messages. +class Connection { + public: + enum Status { + DISCONNECTED, + IN_PROGRESS, + CONNECTED, + }; + + // Constructs a connection to the given |remote_device|. + explicit Connection(const RemoteDevice& remote_device); + ~Connection(); + + // Returns true iff the connection's status is CONNECTED. + bool IsConnected() const; + + // Sends a message to the remote device. + // |OnSendCompleted()| will be called for all observers upon completion with + // either success or failure. + void SendMessage(scoped_ptr message); + + void AddObserver(ConnectionObserver* observer); + void RemoveObserver(ConnectionObserver* observer); + + const RemoteDevice& remote_device() const { return remote_device_; } + + // Abstract methods that subclasses should implement: + + // Pauses or unpauses the handling of incoming messages. Pausing allows the + // user of the connection to add or remove observers without missing messages. + virtual void SetPaused(bool paused) = 0; + + // Attempts to connect to the remote device if not already connected. + virtual void Connect() = 0; + + // Disconnects from the remote device. + virtual void Disconnect() = 0; + + protected: + // Sets the connection's status to |status|. If this is different from the + // previous status, notifies observers of the change in status. + void SetStatus(Status status); + + Status status() const { return status_; } + + // Called after attempting to send bytes over the connection, whether the + // message was successfully sent or not. + void OnDidSendMessage(const WireMessage& message, bool success); + + // Called when bytes are read from the connection. There should not be a send + // in progress when this function is called. + void OnBytesReceived(const std::string& bytes); + + // Sends bytes over the connection. The implementing class should call + // OnSendCompleted() once the send succeeds or fails. At most one send will be + // in progress. + virtual void SendMessageImpl(scoped_ptr message) = 0; + + // Returns |true| iff the |received_bytes_| are long enough to contain a + // complete wire message. Exposed for testing. + virtual bool HasReceivedCompleteMessage(); + + // Deserializes the |recieved_bytes_| and returns the resulting WireMessage, + // or NULL if the message is malformed. Exposed for testing. + virtual scoped_ptr DeserializeWireMessage(); + + private: + // The remote device corresponding to this connection. + const RemoteDevice remote_device_; + + // The current status of the connection. + Status status_; + + // The registered observers of the connection. + ObserverList observers_; + + // A temporary buffer storing bytes received before a received message can be + // fully constructed. + std::string received_bytes_; + + // Whether a message is currently in the process of being sent. + bool is_sending_message_; + + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +} // namespace proximity_auth + +#endif // COMPONENTS_PROXIMITY_AUTH_CONNECTION_H diff --git a/components/proximity_auth/connection_observer.h b/components/proximity_auth/connection_observer.h new file mode 100644 index 000000000000..53578901a292 --- /dev/null +++ b/components/proximity_auth/connection_observer.h @@ -0,0 +1,37 @@ +// 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_PROXIMITY_AUTH_CONNECTION_OBSERVER_H +#define COMPONENTS_PROXIMITY_AUTH_CONNECTION_OBSERVER_H + +#include "components/proximity_auth/connection.h" + +namespace proximity_auth { + +class WireMessage; + +// An interface for observing events that happen on a Connection. +class ConnectionObserver { + public: + // Called when the |connection|'s status changes from |old_status| to + // |new_status|. + virtual void OnConnectionStatusChanged(const Connection& connection, + Connection::Status old_status, + Connection::Status new_status) = 0; + + // Called when a |message| is received from a remote device over the + // |connection|. + virtual void OnMessageReceived(const Connection& connection, + const WireMessage& message) = 0; + + // Called after a |message| is sent to the remote device over the + // |connection|. |success| is |true| iff the message is sent successfully. + virtual void OnSendCompleted(const Connection& connection, + const WireMessage& message, + bool success) = 0; +}; + +} // namespace proximity_auth + +#endif // COMPONENTS_PROXIMITY_AUTH_CONNECTION_OBSERVER_H diff --git a/components/proximity_auth/connection_unittest.cc b/components/proximity_auth/connection_unittest.cc new file mode 100644 index 000000000000..2f9fbdb78b56 --- /dev/null +++ b/components/proximity_auth/connection_unittest.cc @@ -0,0 +1,242 @@ +// 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/proximity_auth/connection.h" + +#include "components/proximity_auth/connection_observer.h" +#include "components/proximity_auth/wire_message.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; +using testing::StrictMock; + +namespace proximity_auth { +namespace { + +class MockConnection : public Connection { + public: + MockConnection() : Connection(RemoteDevice()) {} + ~MockConnection() {} + + MOCK_METHOD1(SetPaused, void(bool paused)); + MOCK_METHOD0(Connect, void()); + MOCK_METHOD0(Disconnect, void()); + MOCK_METHOD0(CancelConnectionAttempt, void()); + MOCK_METHOD1(SendMessageImplProxy, void(WireMessage* message)); + MOCK_METHOD0(HasReceivedCompleteMessage, bool()); + MOCK_METHOD0(DeserializeWireMessageProxy, WireMessage*()); + + // Gmock only supports copyable types, so create simple wrapper methods for + // ease of mocking. + virtual void SendMessageImpl(scoped_ptr message) OVERRIDE { + SendMessageImplProxy(message.get()); + } + + virtual scoped_ptr DeserializeWireMessage() OVERRIDE { + return make_scoped_ptr(DeserializeWireMessageProxy()); + } + + using Connection::status; + using Connection::SetStatus; + using Connection::OnDidSendMessage; + using Connection::OnBytesReceived; + + private: + DISALLOW_COPY_AND_ASSIGN(MockConnection); +}; + +class MockConnectionObserver : public ConnectionObserver { + public: + MockConnectionObserver() {} + virtual ~MockConnectionObserver() {} + + MOCK_METHOD3(OnConnectionStatusChanged, + void(const Connection& connection, + Connection::Status old_status, + Connection::Status new_status)); + MOCK_METHOD2(OnMessageReceived, + void(const Connection& connection, const WireMessage& message)); + MOCK_METHOD3(OnSendCompleted, + void(const Connection& connection, + const WireMessage& message, + bool success)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockConnectionObserver); +}; + +// Unlike WireMessage, offers a public constructor. +class TestWireMessage : public WireMessage { + public: + TestWireMessage() {} + virtual ~TestWireMessage() {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestWireMessage); +}; + +} // namespace + +TEST(ProximityAuthConnectionTest, IsConnected) { + StrictMock connection; + EXPECT_FALSE(connection.IsConnected()); + + connection.SetStatus(Connection::CONNECTED); + EXPECT_TRUE(connection.IsConnected()); + + connection.SetStatus(Connection::DISCONNECTED); + EXPECT_FALSE(connection.IsConnected()); + + connection.SetStatus(Connection::IN_PROGRESS); + EXPECT_FALSE(connection.IsConnected()); +} + +TEST(ProximityAuthConnectionTest, SendMessage_FailsWhenNotConnected) { + StrictMock connection; + connection.SetStatus(Connection::IN_PROGRESS); + + EXPECT_CALL(connection, SendMessageImplProxy(_)).Times(0); + connection.SendMessage(scoped_ptr()); +} + +TEST(ProximityAuthConnectionTest, + SendMessage_FailsWhenAnotherMessageSendIsInProgress) { + NiceMock connection; + connection.SetStatus(Connection::CONNECTED); + connection.SendMessage(scoped_ptr()); + + EXPECT_CALL(connection, SendMessageImplProxy(_)).Times(0); + connection.SendMessage(scoped_ptr()); +} + +TEST(ProximityAuthConnectionTest, SendMessage_SucceedsWhenConnected) { + StrictMock connection; + connection.SetStatus(Connection::CONNECTED); + + EXPECT_CALL(connection, SendMessageImplProxy(_)); + connection.SendMessage(scoped_ptr()); +} + +TEST(ProximityAuthConnectionTest, + SendMessage_SucceedsAfterPreviousMessageSendCompletes) { + NiceMock connection; + connection.SetStatus(Connection::CONNECTED); + connection.SendMessage(scoped_ptr()); + connection.OnDidSendMessage(TestWireMessage(), true /* success */); + + EXPECT_CALL(connection, SendMessageImplProxy(_)); + connection.SendMessage(scoped_ptr()); +} + +TEST(ProximityAuthConnectionTest, SetStatus_NotifiesObserversOfStatusChange) { + StrictMock connection; + EXPECT_EQ(Connection::DISCONNECTED, connection.status()); + + StrictMock observer; + connection.AddObserver(&observer); + + EXPECT_CALL( + observer, + OnConnectionStatusChanged( + Ref(connection), Connection::DISCONNECTED, Connection::CONNECTED)); + connection.SetStatus(Connection::CONNECTED); +} + +TEST(ProximityAuthConnectionTest, + SetStatus_DoesntNotifyObserversIfStatusUnchanged) { + StrictMock connection; + EXPECT_EQ(Connection::DISCONNECTED, connection.status()); + + StrictMock observer; + connection.AddObserver(&observer); + + EXPECT_CALL(observer, OnConnectionStatusChanged(_, _, _)).Times(0); + connection.SetStatus(Connection::DISCONNECTED); +} + +TEST(ProximityAuthConnectionTest, + OnDidSendMessage_NotifiesObserversIfMessageSendInProgress) { + NiceMock connection; + connection.SetStatus(Connection::CONNECTED); + connection.SendMessage(scoped_ptr()); + + StrictMock observer; + connection.AddObserver(&observer); + + EXPECT_CALL(observer, OnSendCompleted(Ref(connection), _, true)); + connection.OnDidSendMessage(TestWireMessage(), true /* success */); +} + +TEST(ProximityAuthConnectionTest, + OnDidSendMessage_DoesntNotifyObserversIfNoMessageSendInProgress) { + NiceMock connection; + connection.SetStatus(Connection::CONNECTED); + + StrictMock observer; + connection.AddObserver(&observer); + + EXPECT_CALL(observer, OnSendCompleted(_, _, _)).Times(0); + connection.OnDidSendMessage(TestWireMessage(), true /* success */); +} + +TEST(ProximityAuthConnectionTest, + OnBytesReceived_NotifiesObserversOnValidMessage) { + NiceMock connection; + connection.SetStatus(Connection::CONNECTED); + + StrictMock observer; + connection.AddObserver(&observer); + + ON_CALL(connection, HasReceivedCompleteMessage()).WillByDefault(Return(true)); + ON_CALL(connection, DeserializeWireMessageProxy()) + .WillByDefault(Return(new TestWireMessage)); + EXPECT_CALL(observer, OnMessageReceived(Ref(connection), _)); + connection.OnBytesReceived(std::string()); +} + +TEST(ProximityAuthConnectionTest, + OnBytesReceived_DoesntNotifyObserversIfNotConnected) { + StrictMock connection; + connection.SetStatus(Connection::IN_PROGRESS); + + StrictMock observer; + connection.AddObserver(&observer); + + EXPECT_CALL(observer, OnMessageReceived(_, _)).Times(0); + connection.OnBytesReceived(std::string()); +} + +TEST(ProximityAuthConnectionTest, + OnBytesReceived_DoesntNotifyObserversIfMessageIsIncomplete) { + NiceMock connection; + connection.SetStatus(Connection::CONNECTED); + + StrictMock observer; + connection.AddObserver(&observer); + + ON_CALL(connection, HasReceivedCompleteMessage()) + .WillByDefault(Return(false)); + EXPECT_CALL(observer, OnMessageReceived(_, _)).Times(0); + connection.OnBytesReceived(std::string()); +} + +TEST(ProximityAuthConnectionTest, + OnBytesReceived_DoesntNotifyObserversIfMessageIsInvalid) { + NiceMock connection; + connection.SetStatus(Connection::CONNECTED); + + StrictMock observer; + connection.AddObserver(&observer); + + ON_CALL(connection, HasReceivedCompleteMessage()).WillByDefault(Return(true)); + ON_CALL(connection, DeserializeWireMessageProxy()) + .WillByDefault(Return(static_cast(NULL))); + EXPECT_CALL(observer, OnMessageReceived(_, _)).Times(0); + connection.OnBytesReceived(std::string()); +} + +} // namespace proximity_auth diff --git a/components/proximity_auth/wire_message.cc b/components/proximity_auth/wire_message.cc new file mode 100644 index 000000000000..b6b57705c200 --- /dev/null +++ b/components/proximity_auth/wire_message.cc @@ -0,0 +1,29 @@ +// 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/proximity_auth/wire_message.h" + +namespace proximity_auth { + +WireMessage::~WireMessage() { +} + +// static +bool WireMessage::IsCompleteMessage(const std::string& serialized_message) { + // TODO(isherman): Implement. + return false; +} + +// static +scoped_ptr WireMessage::Deserialize( + const std::string& serialized_message) { + // TODO(isherman): Implement. + return scoped_ptr(); +} + +WireMessage::WireMessage() { + // TODO(isherman): Implement. +} + +} // namespace proximity_auth diff --git a/components/proximity_auth/wire_message.h b/components/proximity_auth/wire_message.h new file mode 100644 index 000000000000..db5af37ec2b6 --- /dev/null +++ b/components/proximity_auth/wire_message.h @@ -0,0 +1,39 @@ +// 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_PROXIMITY_AUTH_WIRE_MESSAGE_H +#define COMPONENTS_PROXIMITY_AUTH_WIRE_MESSAGE_H + +#include + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace proximity_auth { + +class WireMessage { + public: + virtual ~WireMessage(); + + // Returns |true| iff the size of |message_bytes| is at least equal to the + // message length encoded in the message header. Returns false if the message + // header is not available. + static bool IsCompleteMessage(const std::string& message_bytes); + + // Returns the deserialized message from |serialized_message|, or NULL if the + // message is malformed. + static scoped_ptr Deserialize( + const std::string& serialized_message); + + protected: + // Visible for tests. + WireMessage(); + + private: + DISALLOW_COPY_AND_ASSIGN(WireMessage); +}; + +} // namespace proximity_auth + +#endif // COMPONENTS_PROXIMITY_AUTH_WIRE_MESSAGE_H -- 2.11.4.GIT