diff options
author | isherman <isherman@chromium.org> | 2014-09-25 20:32:44 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-26 03:33:00 +0000 |
commit | 83d08a29ec9d53c93f69a6a1cce75f5d736c5f75 (patch) | |
tree | 6bb18201264bc7c352a2307a602304c26606928f | |
parent | e9453ea69cbb3d52e67394e75c747b0b2d970621 (diff) | |
download | chromium_src-83d08a29ec9d53c93f69a6a1cce75f5d736c5f75.zip chromium_src-83d08a29ec9d53c93f69a6a1cce75f5d736c5f75.tar.gz chromium_src-83d08a29ec9d53c93f69a6a1cce75f5d736c5f75.tar.bz2 |
[Easy Unlock] Port the BluetoothConnection class to native code.
BUG=413488
TEST=components_unittests
R=tengs@chromium.org
Review URL: https://codereview.chromium.org/585743004
Cr-Commit-Position: refs/heads/master@{#296872}
-rw-r--r-- | components/components_tests.gyp | 2 | ||||
-rw-r--r-- | components/proximity_auth.gypi | 2 | ||||
-rw-r--r-- | components/proximity_auth/BUILD.gn | 4 | ||||
-rw-r--r-- | components/proximity_auth/bluetooth_connection.cc | 201 | ||||
-rw-r--r-- | components/proximity_auth/bluetooth_connection.h | 93 | ||||
-rw-r--r-- | components/proximity_auth/bluetooth_connection_unittest.cc | 462 | ||||
-rw-r--r-- | components/proximity_auth/connection.h | 15 | ||||
-rw-r--r-- | components/proximity_auth/connection_unittest.cc | 1 | ||||
-rw-r--r-- | components/proximity_auth/remote_device.h | 1 | ||||
-rw-r--r-- | components/proximity_auth/wire_message.cc | 5 | ||||
-rw-r--r-- | components/proximity_auth/wire_message.h | 3 |
11 files changed, 781 insertions, 8 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp index b358711..a38e306 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -655,6 +655,7 @@ 'copresence/rpc/http_post_unittest.cc', 'copresence/rpc/rpc_handler_unittest.cc', 'copresence/timed_map_unittest.cc', + 'proximity_auth/bluetooth_connection_unittest.cc', 'proximity_auth/connection_unittest.cc', 'proximity_auth/proximity_auth_system_unittest.cc', 'proximity_auth/wire_message_unittest.cc', @@ -666,6 +667,7 @@ # Dependencies of proxmity_auth 'components.gyp:proximity_auth', + '../device/bluetooth/bluetooth.gyp:device_bluetooth_mocks', ], }], ['chromeos==1', { diff --git a/components/proximity_auth.gypi b/components/proximity_auth.gypi index 35016f3..f4a805f 100644 --- a/components/proximity_auth.gypi +++ b/components/proximity_auth.gypi @@ -16,6 +16,8 @@ '../net/net.gyp:net', ], 'sources': [ + "proximity_auth/bluetooth_connection.cc", + "proximity_auth/bluetooth_connection.h", "proximity_auth/bluetooth_util.cc", "proximity_auth/bluetooth_util_chromeos.cc", "proximity_auth/bluetooth_util.h", diff --git a/components/proximity_auth/BUILD.gn b/components/proximity_auth/BUILD.gn index 532d2af..98b7171 100644 --- a/components/proximity_auth/BUILD.gn +++ b/components/proximity_auth/BUILD.gn @@ -4,6 +4,8 @@ static_library("proximity_auth") { sources = [ + "bluetooth_connection.cc", + "bluetooth_connection.h", "bluetooth_util.cc", "bluetooth_util_chromeos.cc", "bluetooth_util.h", @@ -27,6 +29,7 @@ static_library("proximity_auth") { source_set("unit_tests") { testonly = true sources = [ + "bluetooth_connection_unittest.cc", "connection_unittest.cc", "proximity_auth_system_unittest.cc", "wire_message_unittest.cc", @@ -35,6 +38,7 @@ source_set("unit_tests") { deps = [ ":proximity_auth", "//base/test:test_support", + "//device/bluetooth:mocks", "//testing/gmock", "//testing/gtest", ] diff --git a/components/proximity_auth/bluetooth_connection.cc b/components/proximity_auth/bluetooth_connection.cc new file mode 100644 index 0000000..fdc7103 --- /dev/null +++ b/components/proximity_auth/bluetooth_connection.cc @@ -0,0 +1,201 @@ +// 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/bluetooth_connection.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/numerics/safe_conversions.h" +#include "components/proximity_auth/bluetooth_util.h" +#include "components/proximity_auth/remote_device.h" +#include "components/proximity_auth/wire_message.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_device.h" +#include "net/base/io_buffer.h" + +namespace proximity_auth { +namespace { +const int kReceiveBufferSizeBytes = 1024; +} + +BluetoothConnection::BluetoothConnection(const RemoteDevice& remote_device, + const device::BluetoothUUID& uuid) + : Connection(remote_device), uuid_(uuid), weak_ptr_factory_(this) { +} + +BluetoothConnection::~BluetoothConnection() { + Disconnect(); +} + +void BluetoothConnection::Connect() { + if (status() != DISCONNECTED) { + VLOG(1) + << "[BC] Ignoring attempt to connect a non-disconnected connection."; + return; + } + + if (!device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) { + VLOG(1) + << "[BC] Connection failed: Bluetooth is unsupported on this platform."; + return; + } + + SetStatus(IN_PROGRESS); + device::BluetoothAdapterFactory::GetAdapter( + base::Bind(&BluetoothConnection::OnAdapterInitialized, + weak_ptr_factory_.GetWeakPtr())); +} + +void BluetoothConnection::Disconnect() { + if (status() == DISCONNECTED) { + VLOG(1) + << "[BC] Ignoring attempt to disconnect a non-connected connection."; + return; + } + + // Set status as disconnected now, rather than after the socket closes, so + // this connection is not reused. + SetStatus(DISCONNECTED); + if (socket_.get()) { + socket_->Disconnect(base::Bind(&base::DoNothing)); + socket_ = NULL; + } + if (adapter_.get()) { + adapter_->RemoveObserver(this); + adapter_ = NULL; + } +} + +void BluetoothConnection::SendMessageImpl(scoped_ptr<WireMessage> message) { + DCHECK_EQ(status(), CONNECTED); + + // Serialize the message. + std::string serialized_message = message->Serialize(); + int message_length = base::checked_cast<int>(serialized_message.size()); + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(message_length); + memcpy(buffer->data(), serialized_message.c_str(), message_length); + + // Send it. + pending_message_ = message.Pass(); + base::WeakPtr<BluetoothConnection> weak_this = weak_ptr_factory_.GetWeakPtr(); + socket_->Send(buffer, + message_length, + base::Bind(&BluetoothConnection::OnSend, weak_this), + base::Bind(&BluetoothConnection::OnSendError, weak_this)); +} + +void BluetoothConnection::DeviceRemoved(device::BluetoothAdapter* adapter, + device::BluetoothDevice* device) { + DCHECK_EQ(adapter, adapter_.get()); + if (device->GetAddress() != remote_device().bluetooth_address) + return; + + DCHECK_NE(status(), DISCONNECTED); + VLOG(1) << "[BC] Device disconnected..."; + Disconnect(); +} + +void BluetoothConnection::ConnectToService( + device::BluetoothDevice* device, + const device::BluetoothUUID& uuid, + const device::BluetoothDevice::ConnectToServiceCallback& callback, + const device::BluetoothDevice::ConnectToServiceErrorCallback& + error_callback) { + bluetooth_util::ConnectToServiceInsecurely( + device, uuid, callback, error_callback); +} + +void BluetoothConnection::StartReceive() { + base::WeakPtr<BluetoothConnection> weak_this = weak_ptr_factory_.GetWeakPtr(); + socket_->Receive(kReceiveBufferSizeBytes, + base::Bind(&BluetoothConnection::OnReceive, weak_this), + base::Bind(&BluetoothConnection::OnReceiveError, weak_this)); +} + +void BluetoothConnection::OnAdapterInitialized( + scoped_refptr<device::BluetoothAdapter> adapter) { + const std::string address = remote_device().bluetooth_address; + device::BluetoothDevice* bluetooth_device = adapter->GetDevice(address); + if (!bluetooth_device) { + VLOG(1) << "[BC] Device with address " << address + << " is not known to the system Bluetooth daemon."; + Disconnect(); + return; + } + + adapter_ = adapter; + adapter_->AddObserver(this); + + base::WeakPtr<BluetoothConnection> weak_this = weak_ptr_factory_.GetWeakPtr(); + ConnectToService( + bluetooth_device, + uuid_, + base::Bind(&BluetoothConnection::OnConnected, weak_this), + base::Bind(&BluetoothConnection::OnConnectionError, weak_this)); +} + +void BluetoothConnection::OnConnected( + scoped_refptr<device::BluetoothSocket> socket) { + if (status() != IN_PROGRESS) { + // This case is reachable if the client of |this| connection called + // |Disconnect()| while the backing Bluetooth connection was pending. + DCHECK_EQ(status(), DISCONNECTED); + VLOG(1) << "[BC] Ignoring successful backend Bluetooth connection to an " + << "already disconnected logical connection."; + return; + } + + VLOG(1) << "[BC] Connection established with " + << remote_device().bluetooth_address; + socket_ = socket; + SetStatus(CONNECTED); + StartReceive(); +} + +void BluetoothConnection::OnConnectionError(const std::string& error_message) { + VLOG(1) << "[BC] Connection failed: " << error_message; + Disconnect(); +} + +void BluetoothConnection::OnSend(int bytes_sent) { + VLOG(1) << "[BC] Successfully sent " << bytes_sent << " bytes."; + OnDidSendMessage(*pending_message_, true); + pending_message_.reset(); +} + +void BluetoothConnection::OnSendError(const std::string& error_message) { + VLOG(1) << "[BC] Error when sending bytes: " << error_message; + OnDidSendMessage(*pending_message_, false); + pending_message_.reset(); + + Disconnect(); +} + +void BluetoothConnection::OnReceive(int bytes_received, + scoped_refptr<net::IOBuffer> buffer) { + VLOG(1) << "[BC] Received " << bytes_received << " bytes."; + OnBytesReceived(std::string(buffer->data(), bytes_received)); + + // Post a task to delay the read until the socket is available, as + // calling StartReceive at this point would error with ERR_IO_PENDING. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&BluetoothConnection::StartReceive, + weak_ptr_factory_.GetWeakPtr())); +} + +void BluetoothConnection::OnReceiveError( + device::BluetoothSocket::ErrorReason error_reason, + const std::string& error_message) { + VLOG(1) << "[BC] Error receiving bytes: " << error_message; + + // Post a task to delay the read until the socket is available, as + // calling StartReceive at this point would error with ERR_IO_PENDING. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&BluetoothConnection::StartReceive, + weak_ptr_factory_.GetWeakPtr())); +} + +} // namespace proximity_auth diff --git a/components/proximity_auth/bluetooth_connection.h b/components/proximity_auth/bluetooth_connection.h new file mode 100644 index 0000000..f126688 --- /dev/null +++ b/components/proximity_auth/bluetooth_connection.h @@ -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. + +#ifndef COMPONENTS_PROXIMITY_AUTH_BLUETOOTH_CONNECTION_H +#define COMPONENTS_PROXIMITY_AUTH_BLUETOOTH_CONNECTION_H + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "components/proximity_auth/connection.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_socket.h" +#include "device/bluetooth/bluetooth_uuid.h" + +namespace net { +class IOBuffer; +} + +namespace proximity_auth { + +struct RemoteDevice; + +// Represents a Bluetooth connection with a remote device. The connection is a +// persistent bidirectional channel for sending and receiving wire messages. +class BluetoothConnection : public Connection, + public device::BluetoothAdapter::Observer { + public: + // Constructs a Bluetooth connection to the service with |uuid| on the + // |remote_device|. The |remote_device| must already be known to the system + // Bluetooth daemon. + BluetoothConnection(const RemoteDevice& remote_device, + const device::BluetoothUUID& uuid); + virtual ~BluetoothConnection(); + + protected: + // Connection: + virtual void Connect() OVERRIDE; + virtual void Disconnect() OVERRIDE; + virtual void SendMessageImpl(scoped_ptr<WireMessage> message) OVERRIDE; + + // BluetoothAdapter::Observer: + virtual void DeviceRemoved(device::BluetoothAdapter* adapter, + device::BluetoothDevice* device) OVERRIDE; + + // Exposed for testing. + virtual void ConnectToService( + device::BluetoothDevice* device, + const device::BluetoothUUID& uuid, + const device::BluetoothDevice::ConnectToServiceCallback& callback, + const device::BluetoothDevice::ConnectToServiceErrorCallback& + error_callback); + + private: + // Registers receive callbacks with the backing |socket_|. + void StartReceive(); + + // Callbacks for asynchronous Bluetooth operations. + void OnAdapterInitialized(scoped_refptr<device::BluetoothAdapter> adapter); + void OnConnected(scoped_refptr<device::BluetoothSocket> socket); + void OnConnectionError(const std::string& error_message); + void OnSend(int bytes_sent); + void OnSendError(const std::string& error_message); + void OnReceive(int bytes_received, scoped_refptr<net::IOBuffer> buffer); + void OnReceiveError(device::BluetoothSocket::ErrorReason error_reason, + const std::string& error_message); + + // The UUID (universally unique identifier) of the Bluetooth service on the + // remote device that |this| connection should connect to. + const device::BluetoothUUID uuid_; + + // The Bluetooth adapter over which this connection is made. Non-null iff + // |this| connection is registered as an observer of the |adapter_|. + scoped_refptr<device::BluetoothAdapter> adapter_; + + // The Bluetooth socket that backs this connection. NULL iff the connection is + // not in a connected state. + scoped_refptr<device::BluetoothSocket> socket_; + + // The message that was sent over the backing |socket_|. NULL iff there is no + // send operation in progress. + scoped_ptr<WireMessage> pending_message_; + + base::WeakPtrFactory<BluetoothConnection> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothConnection); +}; + +} // namespace proximity_auth + +#endif // COMPONENTS_PROXIMITY_AUTH_BLUETOOTH_CONNECTION_H diff --git a/components/proximity_auth/bluetooth_connection_unittest.cc b/components/proximity_auth/bluetooth_connection_unittest.cc new file mode 100644 index 0000000..bfa99e3 --- /dev/null +++ b/components/proximity_auth/bluetooth_connection_unittest.cc @@ -0,0 +1,462 @@ +// 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/bluetooth_connection.h" + +#include "base/message_loop/message_loop.h" +#include "base/numerics/safe_conversions.h" +#include "base/run_loop.h" +#include "components/proximity_auth/remote_device.h" +#include "components/proximity_auth/wire_message.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/bluetooth/test/mock_bluetooth_device.h" +#include "device/bluetooth/test/mock_bluetooth_socket.h" +#include "net/base/io_buffer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::AnyNumber; +using testing::NiceMock; +using testing::Ref; +using testing::Return; +using testing::SaveArg; +using testing::StrictMock; + +namespace proximity_auth { +namespace { + +const char kDeviceName[] = "Device name"; +const char kOtherDeviceName[] = "Other device name"; + +const char kBluetoothAddress[] = "11:22:33:44:55:66"; +const char kOtherBluetoothAddress[] = "AA:BB:CC:DD:EE:FF"; + +const char kSerializedMessage[] = "Yarrr, this be a serialized message. Yarr!"; +const int kSerializedMessageLength = strlen(kSerializedMessage); + +const char kUuid[] = "DEADBEEF-CAFE-FEED-FOOD-D15EA5EBEEF"; + +const RemoteDevice kRemoteDevice = {kDeviceName, kBluetoothAddress}; + +const int kReceiveBufferSize = 6; +const char kReceiveBufferContents[] = "bytes"; + +// Create a buffer for testing received data. +scoped_refptr<net::IOBuffer> CreateReceiveBuffer() { + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kReceiveBufferSize); + memcpy(buffer->data(), kReceiveBufferContents, kReceiveBufferSize); + return buffer; +} + +class MockBluetoothConnection : public BluetoothConnection { + public: + MockBluetoothConnection() + : BluetoothConnection(kRemoteDevice, device::BluetoothUUID(kUuid)) {} + + // Bluetooth dependencies. + typedef device::BluetoothDevice::ConnectToServiceCallback + ConnectToServiceCallback; + typedef device::BluetoothDevice::ConnectToServiceErrorCallback + ConnectToServiceErrorCallback; + MOCK_METHOD4(ConnectToService, + void(device::BluetoothDevice* device, + const device::BluetoothUUID& uuid, + const ConnectToServiceCallback& callback, + const ConnectToServiceErrorCallback& error_callback)); + + // Calls back into the parent Connection class. + MOCK_METHOD1(SetStatusProxy, void(Status status)); + MOCK_METHOD1(OnBytesReceived, void(const std::string& bytes)); + MOCK_METHOD2(OnDidSendMessage, + void(const WireMessage& message, bool success)); + + virtual void SetStatus(Status status) OVERRIDE { + SetStatusProxy(status); + BluetoothConnection::SetStatus(status); + } + + using BluetoothConnection::status; + using BluetoothConnection::Connect; + using BluetoothConnection::DeviceRemoved; + using BluetoothConnection::Disconnect; + + private: + DISALLOW_COPY_AND_ASSIGN(MockBluetoothConnection); +}; + +class TestWireMessage : public WireMessage { + public: + TestWireMessage() : WireMessage("permit id", "payload") {} + virtual ~TestWireMessage() {} + + virtual std::string Serialize() const OVERRIDE { return kSerializedMessage; } + + private: + DISALLOW_COPY_AND_ASSIGN(TestWireMessage); +}; + +} // namespace + +class ProximityAuthBluetoothConnectionTest : public testing::Test { + public: + ProximityAuthBluetoothConnectionTest() + : adapter_(new device::MockBluetoothAdapter), + device_(adapter_.get(), 0, kDeviceName, kBluetoothAddress, true, true), + socket_(new StrictMock<device::MockBluetoothSocket>), + uuid_(kUuid) { + device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_); + + // Suppress uninteresting Gmock call warnings. + EXPECT_CALL(*adapter_, GetDevice(_)).Times(AnyNumber()); + } + + // Transition the connection into an in-progress state. + void BeginConnecting(MockBluetoothConnection* connection) { + EXPECT_EQ(Connection::DISCONNECTED, connection->status()); + + ON_CALL(*adapter_, GetDevice(_)).WillByDefault(Return(&device_)); + EXPECT_CALL(*connection, SetStatusProxy(Connection::IN_PROGRESS)); + EXPECT_CALL(*adapter_, AddObserver(connection)); + EXPECT_CALL(*connection, ConnectToService(&device_, uuid_, _, _)); + connection->Connect(); + + EXPECT_EQ(Connection::IN_PROGRESS, connection->status()); + } + + // Transition the connection into a connected state. + // Saves the success and error callbacks passed into OnReceive(), which can be + // accessed via receive_callback() and receive_success_callback(). + void Connect(MockBluetoothConnection* connection) { + EXPECT_EQ(Connection::DISCONNECTED, connection->status()); + + device::BluetoothDevice::ConnectToServiceCallback callback; + ON_CALL(*adapter_, GetDevice(_)).WillByDefault(Return(&device_)); + EXPECT_CALL(*connection, SetStatusProxy(Connection::IN_PROGRESS)); + EXPECT_CALL(*adapter_, AddObserver(connection)); + EXPECT_CALL(*connection, ConnectToService(_, _, _, _)) + .WillOnce(SaveArg<2>(&callback)); + connection->Connect(); + ASSERT_FALSE(callback.is_null()); + + EXPECT_CALL(*connection, SetStatusProxy(Connection::CONNECTED)); + EXPECT_CALL(*socket_, Receive(_, _, _)) + .WillOnce(DoAll(SaveArg<1>(&receive_callback_), + SaveArg<2>(&receive_error_callback_))); + callback.Run(socket_); + + EXPECT_EQ(Connection::CONNECTED, connection->status()); + } + + device::BluetoothSocket::ReceiveCompletionCallback* receive_callback() { + return &receive_callback_; + } + device::BluetoothSocket::ReceiveErrorCompletionCallback* + receive_error_callback() { + return &receive_error_callback_; + } + + protected: + // Mocks used for verifying interactions with the Bluetooth subsystem. + scoped_refptr<device::MockBluetoothAdapter> adapter_; + NiceMock<device::MockBluetoothDevice> device_; + scoped_refptr<StrictMock<device::MockBluetoothSocket>> socket_; + + device::BluetoothUUID uuid_; + + private: + base::MessageLoop message_loop_; + + device::BluetoothSocket::ReceiveCompletionCallback receive_callback_; + device::BluetoothSocket::ReceiveErrorCompletionCallback + receive_error_callback_; +}; + +TEST_F(ProximityAuthBluetoothConnectionTest, Connect_ConnectionWasInProgress) { + // Create an in-progress connection. + StrictMock<MockBluetoothConnection> connection; + BeginConnecting(&connection); + + // A second call to Connect() should be ignored. + EXPECT_CALL(connection, SetStatusProxy(_)).Times(0); + connection.Connect(); + + // The connection cleans up after itself upon destruction. + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, Connect_ConnectionWasConnected) { + // Create a connected connection. + StrictMock<MockBluetoothConnection> connection; + Connect(&connection); + + // A second call to Connect() should be ignored. + EXPECT_CALL(connection, SetStatusProxy(_)).Times(0); + connection.Connect(); + + // The connection disconnects and unregisters as an observer upon destruction. + EXPECT_CALL(*socket_, Disconnect(_)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, Connect_NoBluetoothAdapter) { + // Some platforms do not support Bluetooth. This test is only meaningful on + // those platforms. + adapter_ = NULL; + if (device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) + return; + + StrictMock<MockBluetoothConnection> connection; + EXPECT_CALL(connection, SetStatusProxy(_)).Times(0); + connection.Connect(); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, Connect_DeviceMissing) { + StrictMock<MockBluetoothConnection> connection; + + ON_CALL(*adapter_, GetDevice(_)) + .WillByDefault(Return(static_cast<device::BluetoothDevice*>(NULL))); + EXPECT_CALL(connection, SetStatusProxy(Connection::IN_PROGRESS)); + EXPECT_CALL(connection, SetStatusProxy(Connection::DISCONNECTED)); + connection.Connect(); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + Connect_DeviceRemovedWhileConnecting) { + // Create an in-progress connection. + StrictMock<MockBluetoothConnection> connection; + BeginConnecting(&connection); + + // Remove the device while the connection is in-progress. This should cause + // the connection to disconnect. + EXPECT_CALL(connection, SetStatusProxy(Connection::DISCONNECTED)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); + connection.DeviceRemoved(adapter_.get(), &device_); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + Connect_OtherDeviceRemovedWhileConnecting) { + // Create an in-progress connection. + StrictMock<MockBluetoothConnection> connection; + BeginConnecting(&connection); + + // Remove a device other than the one that is being connected to. This should + // not have any effect on the connection. + NiceMock<device::MockBluetoothDevice> other_device( + adapter_.get(), 0, kOtherDeviceName, kOtherBluetoothAddress, true, true); + EXPECT_CALL(connection, SetStatusProxy(_)).Times(0); + connection.DeviceRemoved(adapter_.get(), &other_device); + + // The connection removes itself as an observer when it is destroyed. + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, Connect_ConnectionFails) { + StrictMock<MockBluetoothConnection> connection; + + device::BluetoothDevice::ConnectToServiceErrorCallback error_callback; + ON_CALL(*adapter_, GetDevice(_)).WillByDefault(Return(&device_)); + EXPECT_CALL(connection, SetStatusProxy(Connection::IN_PROGRESS)); + EXPECT_CALL(*adapter_, AddObserver(&connection)); + EXPECT_CALL(connection, ConnectToService(&device_, uuid_, _, _)) + .WillOnce(SaveArg<3>(&error_callback)); + connection.Connect(); + ASSERT_FALSE(error_callback.is_null()); + + EXPECT_CALL(connection, SetStatusProxy(Connection::DISCONNECTED)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); + error_callback.Run("super descriptive error message"); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, Connect_ConnectionSucceeds) { + StrictMock<MockBluetoothConnection> connection; + Connect(&connection); + + // The connection disconnects and unregisters as an observer upon destruction. + EXPECT_CALL(*socket_, Disconnect(_)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + Connect_ConnectionSucceeds_ThenDeviceRemoved) { + StrictMock<MockBluetoothConnection> connection; + Connect(&connection); + + EXPECT_CALL(connection, SetStatusProxy(Connection::DISCONNECTED)); + EXPECT_CALL(*socket_, Disconnect(_)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); + connection.DeviceRemoved(adapter_.get(), &device_); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + Connect_ConnectionSucceeds_ReceiveData) { + StrictMock<MockBluetoothConnection> connection; + Connect(&connection); + ASSERT_TRUE(receive_callback() && !receive_callback()->is_null()); + + // Receive some data. Once complete, the connection should re-register to be + // ready receive more data. + scoped_refptr<net::IOBuffer> buffer = CreateReceiveBuffer(); + EXPECT_CALL( + connection, + OnBytesReceived(std::string(kReceiveBufferContents, kReceiveBufferSize))); + EXPECT_CALL(*socket_, Receive(_, _, _)); + receive_callback()->Run(kReceiveBufferSize, buffer); + base::RunLoop run_loop; + run_loop.RunUntilIdle(); + + // The connection disconnects and unregisters as an observer upon destruction. + EXPECT_CALL(*socket_, Disconnect(_)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + Connect_ConnectionSucceeds_ReceiveDataAfterReceiveError) { + StrictMock<MockBluetoothConnection> connection; + Connect(&connection); + ASSERT_TRUE(receive_error_callback() && !receive_error_callback()->is_null()); + + // Simulate an error while receiving data. The connection should re-register + // to be ready receive more data despite the error. + device::BluetoothSocket::ReceiveCompletionCallback receive_callback; + EXPECT_CALL(*socket_, Receive(_, _, _)) + .WillOnce(SaveArg<1>(&receive_callback)); + receive_error_callback()->Run(device::BluetoothSocket::kSystemError, + "The system is down. They're taking over!"); + base::RunLoop run_loop; + run_loop.RunUntilIdle(); + ASSERT_FALSE(receive_callback.is_null()); + + // Receive some data. + scoped_refptr<net::IOBuffer> buffer = CreateReceiveBuffer(); + EXPECT_CALL( + connection, + OnBytesReceived(std::string(kReceiveBufferContents, kReceiveBufferSize))); + EXPECT_CALL(*socket_, Receive(_, _, _)); + receive_callback.Run(kReceiveBufferSize, buffer); + base::RunLoop run_loop2; + run_loop2.RunUntilIdle(); + + // The connection disconnects and unregisters as an observer upon destruction. + EXPECT_CALL(*socket_, Disconnect(_)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + Disconnect_ConnectionWasAlreadyDisconnected) { + StrictMock<MockBluetoothConnection> connection; + EXPECT_CALL(connection, SetStatusProxy(_)).Times(0); + connection.Disconnect(); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + Disconnect_ConnectionWasInProgress) { + // Create an in-progress connection. + StrictMock<MockBluetoothConnection> connection; + BeginConnecting(&connection); + + EXPECT_CALL(connection, SetStatusProxy(Connection::DISCONNECTED)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); + connection.Disconnect(); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + Disconnect_ConnectionWasConnected) { + // Create a connected connection. + StrictMock<MockBluetoothConnection> connection; + Connect(&connection); + + EXPECT_CALL(connection, SetStatusProxy(Connection::DISCONNECTED)); + EXPECT_CALL(*socket_, Disconnect(_)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); + connection.Disconnect(); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + Connect_ThenDisconnectWhileInProgress_ThenBackingConnectionSucceeds) { + StrictMock<MockBluetoothConnection> connection; + device::BluetoothDevice::ConnectToServiceCallback callback; + ON_CALL(*adapter_, GetDevice(_)).WillByDefault(Return(&device_)); + EXPECT_CALL(connection, SetStatusProxy(Connection::IN_PROGRESS)); + EXPECT_CALL(*adapter_, AddObserver(&connection)); + EXPECT_CALL(connection, ConnectToService(&device_, uuid_, _, _)) + .WillOnce(SaveArg<2>(&callback)); + connection.Connect(); + ASSERT_FALSE(callback.is_null()); + + EXPECT_CALL(connection, SetStatusProxy(Connection::DISCONNECTED)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); + connection.Disconnect(); + + EXPECT_CALL(connection, SetStatusProxy(_)).Times(0); + EXPECT_CALL(*socket_, Receive(_, _, _)).Times(0); + callback.Run(socket_); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, + SendMessage_SendsExpectedDataOverTheWire) { + // Create a connected connection. + StrictMock<MockBluetoothConnection> connection; + Connect(&connection); + + scoped_refptr<net::IOBuffer> buffer; + scoped_ptr<TestWireMessage> wire_message(new TestWireMessage); + EXPECT_CALL(*socket_, Send(_, kSerializedMessageLength, _, _)) + .WillOnce(SaveArg<0>(&buffer)); + connection.SendMessage(wire_message.PassAs<WireMessage>()); + ASSERT_TRUE(buffer.get()); + EXPECT_EQ(kSerializedMessage, + std::string(buffer->data(), kSerializedMessageLength)); + + // The connection disconnects and unregisters as an observer upon destruction. + EXPECT_CALL(*socket_, Disconnect(_)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, SendMessage_Success) { + // Create a connected connection. + StrictMock<MockBluetoothConnection> connection; + Connect(&connection); + + scoped_ptr<TestWireMessage> wire_message(new TestWireMessage); + // Ownership will be transfered below, so grab a reference here. + TestWireMessage* expected_wire_message = wire_message.get(); + + device::BluetoothSocket::SendCompletionCallback callback; + EXPECT_CALL(*socket_, Send(_, _, _, _)).WillOnce(SaveArg<2>(&callback)); + connection.SendMessage(wire_message.PassAs<WireMessage>()); + ASSERT_FALSE(callback.is_null()); + + EXPECT_CALL(connection, OnDidSendMessage(Ref(*expected_wire_message), true)); + callback.Run(kSerializedMessageLength); + + // The connection disconnects and unregisters as an observer upon destruction. + EXPECT_CALL(*socket_, Disconnect(_)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); +} + +TEST_F(ProximityAuthBluetoothConnectionTest, SendMessage_Failure) { + // Create a connected connection. + StrictMock<MockBluetoothConnection> connection; + Connect(&connection); + + scoped_ptr<TestWireMessage> wire_message(new TestWireMessage); + // Ownership will be transfered below, so grab a reference here. + TestWireMessage* expected_wire_message = wire_message.get(); + + device::BluetoothSocket::ErrorCompletionCallback error_callback; + EXPECT_CALL(*socket_, Send(_, _, _, _)).WillOnce(SaveArg<3>(&error_callback)); + connection.SendMessage(wire_message.PassAs<WireMessage>()); + + ASSERT_FALSE(error_callback.is_null()); + EXPECT_CALL(connection, OnDidSendMessage(Ref(*expected_wire_message), false)); + EXPECT_CALL(connection, SetStatusProxy(Connection::DISCONNECTED)); + EXPECT_CALL(*socket_, Disconnect(_)); + EXPECT_CALL(*adapter_, RemoveObserver(&connection)); + error_callback.Run("The most helpful of error messages"); +} + +} // namespace proximity_auth diff --git a/components/proximity_auth/connection.h b/components/proximity_auth/connection.h index b0275da..791609d 100644 --- a/components/proximity_auth/connection.h +++ b/components/proximity_auth/connection.h @@ -28,7 +28,7 @@ class Connection { // Constructs a connection to the given |remote_device|. explicit Connection(const RemoteDevice& remote_device); - ~Connection(); + virtual ~Connection(); // Returns true iff the connection's status is CONNECTED. bool IsConnected() const; @@ -45,10 +45,6 @@ class Connection { // 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; @@ -58,17 +54,20 @@ class Connection { 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); + // Virtual for testing. + virtual 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); + // Virtual for testing. + virtual 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); + // Virtual for testing. + virtual 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 diff --git a/components/proximity_auth/connection_unittest.cc b/components/proximity_auth/connection_unittest.cc index 2e3678d..87e5e2a 100644 --- a/components/proximity_auth/connection_unittest.cc +++ b/components/proximity_auth/connection_unittest.cc @@ -5,6 +5,7 @@ #include "components/proximity_auth/connection.h" #include "components/proximity_auth/connection_observer.h" +#include "components/proximity_auth/remote_device.h" #include "components/proximity_auth/wire_message.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/components/proximity_auth/remote_device.h b/components/proximity_auth/remote_device.h index 354b6b8..b36c616 100644 --- a/components/proximity_auth/remote_device.h +++ b/components/proximity_auth/remote_device.h @@ -12,6 +12,7 @@ namespace proximity_auth { struct RemoteDevice { std::string name; + std::string bluetooth_address; }; } // namespace proximity_auth diff --git a/components/proximity_auth/wire_message.cc b/components/proximity_auth/wire_message.cc index 678014d..646d0b0 100644 --- a/components/proximity_auth/wire_message.cc +++ b/components/proximity_auth/wire_message.cc @@ -111,6 +111,11 @@ scoped_ptr<WireMessage> WireMessage::Deserialize( return scoped_ptr<WireMessage>(new WireMessage(permit_id, payload)); } +std::string WireMessage::Serialize() const { + // TODO(isherman): Implement. + return "This method is not yet implemented."; +} + WireMessage::WireMessage(const std::string& permit_id, const std::string& payload) : permit_id_(permit_id), diff --git a/components/proximity_auth/wire_message.h b/components/proximity_auth/wire_message.h index 3610762..4cf44cb 100644 --- a/components/proximity_auth/wire_message.h +++ b/components/proximity_auth/wire_message.h @@ -24,6 +24,9 @@ class WireMessage { const std::string& serialized_message, bool* is_incomplete_message); + // Returns a serialized representation of |this| message. + virtual std::string Serialize() const; + const std::string& permit_id() const { return permit_id_; } const std::string& payload() const { return payload_; } |