[Easy Unlock] Port the BluetoothConnection class to native code.

BUG=413488
TEST=components_unittests
[email protected]

Review URL: https://codereview.chromium.org/585743004

Cr-Commit-Position: refs/heads/master@{#296872}
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index b3587112..a38e30623 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 35016f30..f4a805ff 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 @@
 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 @@
   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..fdc71032
--- /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 @@
 
   // 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 @@
 
   // 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 @@
  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 2e3678d1..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 @@
 
 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 @@
   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 36107620..4cf44cb2 100644
--- a/components/proximity_auth/wire_message.h
+++ b/components/proximity_auth/wire_message.h
@@ -24,6 +24,9 @@
       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_; }