summaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorisherman <isherman@chromium.org>2015-04-23 17:43:53 -0700
committerCommit bot <commit-bot@chromium.org>2015-04-24 00:44:02 +0000
commitfd7c534b719de832eb7ce1e3aa9043ba063f68c4 (patch)
tree91671104ce92e7386d1f9ac305630014b9f33f29 /components
parent9fde3e00865650009c7e85fcb7157719372b97a8 (diff)
downloadchromium_src-fd7c534b719de832eb7ce1e3aa9043ba063f68c4.zip
chromium_src-fd7c534b719de832eb7ce1e3aa9043ba063f68c4.tar.gz
chromium_src-fd7c534b719de832eb7ce1e3aa9043ba063f68c4.tar.bz2
[Easy Unlock] Port the BluetoothThrottler class to native code.
BUG=424419 TEST=components_unittests R=tengs@chromium.org Review URL: https://codereview.chromium.org/663693002 Cr-Commit-Position: refs/heads/master@{#326706}
Diffstat (limited to 'components')
-rw-r--r--components/components_tests.gyp2
-rw-r--r--components/proximity_auth.gypi5
-rw-r--r--components/proximity_auth/BUILD.gn7
-rw-r--r--components/proximity_auth/bluetooth_connection_finder.cc4
-rw-r--r--components/proximity_auth/bluetooth_connection_finder.h2
-rw-r--r--components/proximity_auth/bluetooth_throttler.h38
-rw-r--r--components/proximity_auth/bluetooth_throttler_impl.cc66
-rw-r--r--components/proximity_auth/bluetooth_throttler_impl.h69
-rw-r--r--components/proximity_auth/bluetooth_throttler_impl_unittest.cc88
-rw-r--r--components/proximity_auth/client.cc4
-rw-r--r--components/proximity_auth/client.h2
-rw-r--r--components/proximity_auth/connection.cc5
-rw-r--r--components/proximity_auth/connection_finder.h4
-rw-r--r--components/proximity_auth/connection_observer.h4
-rw-r--r--components/proximity_auth/connection_unittest.cc9
-rw-r--r--components/proximity_auth/throttled_bluetooth_connection_finder.cc55
-rw-r--r--components/proximity_auth/throttled_bluetooth_connection_finder.h60
-rw-r--r--components/proximity_auth/throttled_bluetooth_connection_finder_unittest.cc133
18 files changed, 537 insertions, 20 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index f48690f..05383f1 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -405,6 +405,7 @@
'proximity_auth_unittest_sources': [
'proximity_auth/bluetooth_connection_finder_unittest.cc',
'proximity_auth/bluetooth_connection_unittest.cc',
+ 'proximity_auth/bluetooth_throttler_impl_unittest.cc',
'proximity_auth/client_unittest.cc',
'proximity_auth/connection_unittest.cc',
'proximity_auth/cryptauth/base64url_unittest.cc',
@@ -415,6 +416,7 @@
'proximity_auth/cryptauth/fake_secure_message_delegate_unittest.cc',
'proximity_auth/proximity_auth_system_unittest.cc',
'proximity_auth/remote_status_update_unittest.cc',
+ 'proximity_auth/throttled_bluetooth_connection_finder_unittest.cc',
'proximity_auth/wire_message_unittest.cc',
],
'query_parser_unittest_sources': [
diff --git a/components/proximity_auth.gypi b/components/proximity_auth.gypi
index ba8abc4..8a5526c 100644
--- a/components/proximity_auth.gypi
+++ b/components/proximity_auth.gypi
@@ -20,6 +20,9 @@
"proximity_auth/bluetooth_connection.h",
"proximity_auth/bluetooth_connection_finder.cc",
"proximity_auth/bluetooth_connection_finder.h",
+ "proximity_auth/bluetooth_throttler.h",
+ "proximity_auth/bluetooth_throttler_impl.cc",
+ "proximity_auth/bluetooth_throttler_impl.h",
"proximity_auth/bluetooth_util.cc",
"proximity_auth/bluetooth_util.h",
"proximity_auth/bluetooth_util_chromeos.cc",
@@ -40,6 +43,8 @@
"proximity_auth/secure_context.h",
"proximity_auth/switches.cc",
"proximity_auth/switches.h",
+ "proximity_auth/throttled_bluetooth_connection_finder.cc",
+ "proximity_auth/throttled_bluetooth_connection_finder.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 380459e..7d31f67 100644
--- a/components/proximity_auth/BUILD.gn
+++ b/components/proximity_auth/BUILD.gn
@@ -10,6 +10,9 @@ source_set("proximity_auth") {
"bluetooth_connection.h",
"bluetooth_connection_finder.cc",
"bluetooth_connection_finder.h",
+ "bluetooth_throttler.h",
+ "bluetooth_throttler_impl.cc",
+ "bluetooth_throttler_impl.h",
"bluetooth_util.cc",
"bluetooth_util.h",
"bluetooth_util_chromeos.cc",
@@ -28,6 +31,8 @@ source_set("proximity_auth") {
"secure_context.h",
"switches.cc",
"switches.h",
+ "throttled_bluetooth_connection_finder.cc",
+ "throttled_bluetooth_connection_finder.h",
"wire_message.cc",
"wire_message.h",
]
@@ -44,10 +49,12 @@ source_set("unit_tests") {
sources = [
"bluetooth_connection_finder_unittest.cc",
"bluetooth_connection_unittest.cc",
+ "bluetooth_throttler_impl_unittest.cc",
"client_unittest.cc",
"connection_unittest.cc",
"proximity_auth_system_unittest.cc",
"remote_status_update_unittest.cc",
+ "throttled_bluetooth_connection_finder_unittest.cc",
"wire_message_unittest.cc",
]
diff --git a/components/proximity_auth/bluetooth_connection_finder.cc b/components/proximity_auth/bluetooth_connection_finder.cc
index 3fe6567..1f26d2f 100644
--- a/components/proximity_auth/bluetooth_connection_finder.cc
+++ b/components/proximity_auth/bluetooth_connection_finder.cc
@@ -119,10 +119,10 @@ void BluetoothConnectionFinder::AdapterPoweredChanged(BluetoothAdapter* adapter,
}
void BluetoothConnectionFinder::OnConnectionStatusChanged(
- const Connection& connection,
+ Connection* connection,
Connection::Status old_status,
Connection::Status new_status) {
- DCHECK_EQ(&connection, connection_.get());
+ DCHECK_EQ(connection, connection_.get());
if (connection_->IsConnected()) {
base::TimeDelta elapsed = base::TimeTicks::Now() - start_time_;
diff --git a/components/proximity_auth/bluetooth_connection_finder.h b/components/proximity_auth/bluetooth_connection_finder.h
index 40b28dd..cca88f63 100644
--- a/components/proximity_auth/bluetooth_connection_finder.h
+++ b/components/proximity_auth/bluetooth_connection_finder.h
@@ -64,7 +64,7 @@ class BluetoothConnectionFinder : public ConnectionFinder,
void OnAdapterInitialized(scoped_refptr<device::BluetoothAdapter> adapter);
// ConnectionObserver:
- void OnConnectionStatusChanged(const Connection& connection,
+ void OnConnectionStatusChanged(Connection* connection,
Connection::Status old_status,
Connection::Status new_status) override;
diff --git a/components/proximity_auth/bluetooth_throttler.h b/components/proximity_auth/bluetooth_throttler.h
new file mode 100644
index 0000000..1e428fe
--- /dev/null
+++ b/components/proximity_auth/bluetooth_throttler.h
@@ -0,0 +1,38 @@
+// Copyright 2015 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_THROTTLER_H
+#define COMPONENTS_PROXIMITY_AUTH_BLUETOOTH_THROTTLER_H
+
+namespace base {
+class TimeDelta;
+}
+
+namespace proximity_auth {
+
+class Connection;
+
+// An interface for throttling repeated connection attempts to the same device.
+// This throttling is necessary to prevent a kernel race condition when
+// connecting before the previous connection fully closes, putting the
+// connection in a corrupted, and unrecoverable state. http://crbug.com/345232
+class BluetoothThrottler {
+ public:
+ virtual ~BluetoothThrottler() {}
+
+ // Returns the current delay that must be respected prior to reattempting to
+ // establish a connection with the remote device. The returned value is 0 if
+ // no delay is needed.
+ virtual base::TimeDelta GetDelay() const = 0;
+
+ // Should be called when a connection to the remote device is established.
+ // Note that the |connection| is passed as a weak reference. The throttler
+ // will ensure, by registering as an observer, that it never attempts to use
+ // the connection after it has been destroyed.
+ virtual void OnConnection(Connection* connection) = 0;
+};
+
+} // namespace proximity_auth
+
+#endif // COMPONENTS_PROXIMITY_AUTH_BLUETOOTH_THROTTLER_H
diff --git a/components/proximity_auth/bluetooth_throttler_impl.cc b/components/proximity_auth/bluetooth_throttler_impl.cc
new file mode 100644
index 0000000..9963fb0
--- /dev/null
+++ b/components/proximity_auth/bluetooth_throttler_impl.cc
@@ -0,0 +1,66 @@
+// Copyright 2015 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_throttler_impl.h"
+
+#include "base/stl_util.h"
+#include "base/time/tick_clock.h"
+#include "components/proximity_auth/connection.h"
+
+namespace proximity_auth {
+namespace {
+
+// Time to wait after disconnect before reconnecting.
+const int kCooldownTimeSecs = 7;
+
+} // namespace
+
+BluetoothThrottlerImpl::BluetoothThrottlerImpl(
+ scoped_ptr<base::TickClock> clock)
+ : clock_(clock.Pass()) {
+}
+
+BluetoothThrottlerImpl::~BluetoothThrottlerImpl() {
+ for (Connection* connection : connections_) {
+ connection->RemoveObserver(this);
+ }
+}
+
+base::TimeDelta BluetoothThrottlerImpl::GetDelay() const {
+ if (last_disconnect_time_.is_null())
+ return base::TimeDelta();
+
+ base::TimeTicks now = clock_->NowTicks();
+ base::TimeTicks throttled_start_time =
+ last_disconnect_time_ + GetCooldownTimeDelta();
+ if (now >= throttled_start_time)
+ return base::TimeDelta();
+
+ return throttled_start_time - now;
+}
+
+void BluetoothThrottlerImpl::OnConnection(Connection* connection) {
+ DCHECK(!ContainsKey(connections_, connection));
+ connections_.insert(connection);
+ connection->AddObserver(this);
+}
+
+base::TimeDelta BluetoothThrottlerImpl::GetCooldownTimeDelta() const {
+ return base::TimeDelta::FromSeconds(kCooldownTimeSecs);
+}
+
+void BluetoothThrottlerImpl::OnConnectionStatusChanged(
+ Connection* connection,
+ Connection::Status old_status,
+ Connection::Status new_status) {
+ DCHECK(ContainsKey(connections_, connection));
+ if (old_status == Connection::CONNECTED &&
+ new_status == Connection::DISCONNECTED) {
+ last_disconnect_time_ = clock_->NowTicks();
+ connection->RemoveObserver(this);
+ connections_.erase(connection);
+ }
+}
+
+} // namespace proximity_auth
diff --git a/components/proximity_auth/bluetooth_throttler_impl.h b/components/proximity_auth/bluetooth_throttler_impl.h
new file mode 100644
index 0000000..3ba71541
--- /dev/null
+++ b/components/proximity_auth/bluetooth_throttler_impl.h
@@ -0,0 +1,69 @@
+// Copyright 2015 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_THROTTLER_IMPL_H
+#define COMPONENTS_PROXIMITY_AUTH_BLUETOOTH_THROTTLER_IMPL_H
+
+#include <set>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "components/proximity_auth/bluetooth_throttler.h"
+#include "components/proximity_auth/connection_observer.h"
+
+namespace base {
+class TickClock;
+}
+
+namespace proximity_auth {
+
+class Connection;
+
+// This class throttles repeated connection attempts to the same device. This
+// throttling is necessary to prevent a kernel race condition when connecting
+// before the previous connection fully closes, putting the connection in a
+// corrupted, and unrecoverable state. http://crbug.com/345232
+class BluetoothThrottlerImpl : public BluetoothThrottler,
+ public ConnectionObserver {
+ public:
+ // Creates a throttler for connections to a remote device, using the |clock|
+ // as a time source.
+ explicit BluetoothThrottlerImpl(scoped_ptr<base::TickClock> clock);
+ ~BluetoothThrottlerImpl() override;
+
+ // BluetoothThrottler:
+ base::TimeDelta GetDelay() const override;
+ void OnConnection(Connection* connection) override;
+
+ protected:
+ // Returns the duration to wait, after disconnecting, before reattempting a
+ // connection to the remote device. Exposed for testing.
+ base::TimeDelta GetCooldownTimeDelta() const;
+
+ private:
+ // ConnectionObserver:
+ void OnConnectionStatusChanged(Connection* connection,
+ Connection::Status old_status,
+ Connection::Status new_status) override;
+
+ // Tracks the last seen disconnect time for the |remote_device_|.
+ base::TimeTicks last_disconnect_time_;
+
+ // The time source.
+ scoped_ptr<base::TickClock> clock_;
+
+ // The currently connected connections.
+ // Each connection is stored as a weak reference, which is safe because |this|
+ // instance is registered as an observer, and will unregister when the
+ // connection disconnects, which is guaranteed to occur before the connection
+ // is destroyed.
+ std::set<Connection*> connections_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothThrottlerImpl);
+};
+
+} // namespace proximity_auth
+
+#endif // COMPONENTS_PROXIMITY_AUTH_BLUETOOTH_THROTTLER_IMPL_H
diff --git a/components/proximity_auth/bluetooth_throttler_impl_unittest.cc b/components/proximity_auth/bluetooth_throttler_impl_unittest.cc
new file mode 100644
index 0000000..1bca940
--- /dev/null
+++ b/components/proximity_auth/bluetooth_throttler_impl_unittest.cc
@@ -0,0 +1,88 @@
+// Copyright 2015 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_throttler_impl.h"
+
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/time.h"
+#include "components/proximity_auth/wire_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace proximity_auth {
+namespace {
+
+class StubConnection : public Connection {
+ public:
+ StubConnection() : Connection(RemoteDevice()) {}
+ ~StubConnection() override {}
+
+ void Connect() override {}
+ void Disconnect() override {}
+ void SendMessageImpl(scoped_ptr<WireMessage> message) override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StubConnection);
+};
+
+class TestBluetoothThrottler : public BluetoothThrottlerImpl {
+ public:
+ explicit TestBluetoothThrottler(scoped_ptr<base::TickClock> clock)
+ : BluetoothThrottlerImpl(clock.Pass()) {}
+ ~TestBluetoothThrottler() override {}
+
+ // Increase visibility for testing.
+ using BluetoothThrottlerImpl::GetCooldownTimeDelta;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestBluetoothThrottler);
+};
+
+} // namespace
+
+class ProximityAuthBluetoothThrottlerImplTest : public testing::Test {
+ public:
+ ProximityAuthBluetoothThrottlerImplTest()
+ : clock_(new base::SimpleTestTickClock),
+ throttler_(make_scoped_ptr(clock_)) {
+ // The throttler treats null times as special, so start with a non-null
+ // time.
+ clock_->Advance(base::TimeDelta::FromSeconds(1));
+ }
+
+ protected:
+ // The clock is owned by the |throttler_|.
+ base::SimpleTestTickClock* clock_;
+ TestBluetoothThrottler throttler_;
+};
+
+TEST_F(ProximityAuthBluetoothThrottlerImplTest,
+ GetDelay_FirstConnectionIsNotThrottled) {
+ EXPECT_EQ(base::TimeDelta(), throttler_.GetDelay());
+}
+
+TEST_F(ProximityAuthBluetoothThrottlerImplTest,
+ GetDelay_ConnectionAfterDisconnectIsThrottled) {
+ // Simulate a connection followed by a disconnection.
+ StubConnection connection;
+ throttler_.OnConnection(&connection);
+ static_cast<ConnectionObserver*>(&throttler_)
+ ->OnConnectionStatusChanged(&connection, Connection::CONNECTED,
+ Connection::DISCONNECTED);
+ EXPECT_GT(throttler_.GetDelay(), base::TimeDelta());
+}
+
+TEST_F(ProximityAuthBluetoothThrottlerImplTest,
+ GetDelay_DelayedConnectionAfterDisconnectIsNotThrottled) {
+ // Simulate a connection followed by a disconnection, then allow the cooldown
+ // period to elapse.
+ StubConnection connection;
+ throttler_.OnConnection(&connection);
+ static_cast<ConnectionObserver*>(&throttler_)
+ ->OnConnectionStatusChanged(&connection, Connection::CONNECTED,
+ Connection::DISCONNECTED);
+ clock_->Advance(throttler_.GetCooldownTimeDelta());
+ EXPECT_EQ(base::TimeDelta(), throttler_.GetDelay());
+}
+
+} // namespace proximity_auth
diff --git a/components/proximity_auth/client.cc b/components/proximity_auth/client.cc
index 016c505..a0b6a06 100644
--- a/components/proximity_auth/client.cc
+++ b/components/proximity_auth/client.cc
@@ -177,10 +177,10 @@ void Client::HandleUnlockResponseMessage(const base::DictionaryValue& message) {
FOR_EACH_OBSERVER(ClientObserver, observers_, OnUnlockResponse(true));
}
-void Client::OnConnectionStatusChanged(const Connection& connection,
+void Client::OnConnectionStatusChanged(Connection* connection,
Connection::Status old_status,
Connection::Status new_status) {
- DCHECK_EQ(&connection, connection_.get());
+ DCHECK_EQ(connection, connection_.get());
if (new_status != Connection::CONNECTED) {
VLOG(1) << "[Client] Secure channel disconnected...";
connection_->RemoveObserver(this);
diff --git a/components/proximity_auth/client.h b/components/proximity_auth/client.h
index 5cd5107..3b66e8e 100644
--- a/components/proximity_auth/client.h
+++ b/components/proximity_auth/client.h
@@ -91,7 +91,7 @@ class Client : public ConnectionObserver {
void HandleUnlockResponseMessage(const base::DictionaryValue& message);
// ConnectionObserver:
- void OnConnectionStatusChanged(const Connection& connection,
+ void OnConnectionStatusChanged(Connection* connection,
Connection::Status old_status,
Connection::Status new_status) override;
void OnMessageReceived(const Connection& connection,
diff --git a/components/proximity_auth/connection.cc b/components/proximity_auth/connection.cc
index 65f6dd6..437be60 100644
--- a/components/proximity_auth/connection.cc
+++ b/components/proximity_auth/connection.cc
@@ -54,9 +54,8 @@ void Connection::SetStatus(Status status) {
Status old_status = status_;
status_ = status;
- FOR_EACH_OBSERVER(ConnectionObserver,
- observers_,
- OnConnectionStatusChanged(*this, old_status, status_));
+ FOR_EACH_OBSERVER(ConnectionObserver, observers_,
+ OnConnectionStatusChanged(this, old_status, status_));
}
void Connection::OnDidSendMessage(const WireMessage& message, bool success) {
diff --git a/components/proximity_auth/connection_finder.h b/components/proximity_auth/connection_finder.h
index 51292da..246a9a0 100644
--- a/components/proximity_auth/connection_finder.h
+++ b/components/proximity_auth/connection_finder.h
@@ -16,7 +16,6 @@ class Connection;
// Interface for finding a connection to a remote device.
class ConnectionFinder {
public:
- ConnectionFinder() {}
virtual ~ConnectionFinder() {}
// Attempts to find a connection to a remote device. The finder will try to
@@ -27,9 +26,6 @@ class ConnectionFinder {
typedef base::Callback<void(scoped_ptr<Connection> connection)>
ConnectionCallback;
virtual void Find(const ConnectionCallback& connection_callback) = 0;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ConnectionFinder);
};
} // namespace proximity_auth
diff --git a/components/proximity_auth/connection_observer.h b/components/proximity_auth/connection_observer.h
index 89bf974..27cfb35 100644
--- a/components/proximity_auth/connection_observer.h
+++ b/components/proximity_auth/connection_observer.h
@@ -15,8 +15,8 @@ class WireMessage;
class ConnectionObserver {
public:
// Called when the |connection|'s status changes from |old_status| to
- // |new_status|.
- virtual void OnConnectionStatusChanged(const Connection& connection,
+ // |new_status|. The |connectoin| is guaranteed to be non-null.
+ virtual void OnConnectionStatusChanged(Connection* connection,
Connection::Status old_status,
Connection::Status new_status) {}
diff --git a/components/proximity_auth/connection_unittest.cc b/components/proximity_auth/connection_unittest.cc
index 8fb1593..3fb5f50 100644
--- a/components/proximity_auth/connection_unittest.cc
+++ b/components/proximity_auth/connection_unittest.cc
@@ -59,7 +59,7 @@ class MockConnectionObserver : public ConnectionObserver {
virtual ~MockConnectionObserver() {}
MOCK_METHOD3(OnConnectionStatusChanged,
- void(const Connection& connection,
+ void(Connection* connection,
Connection::Status old_status,
Connection::Status new_status));
MOCK_METHOD2(OnMessageReceived,
@@ -143,10 +143,9 @@ TEST(ProximityAuthConnectionTest, SetStatus_NotifiesObserversOfStatusChange) {
StrictMock<MockConnectionObserver> observer;
connection.AddObserver(&observer);
- EXPECT_CALL(
- observer,
- OnConnectionStatusChanged(
- Ref(connection), Connection::DISCONNECTED, Connection::CONNECTED));
+ EXPECT_CALL(observer,
+ OnConnectionStatusChanged(&connection, Connection::DISCONNECTED,
+ Connection::CONNECTED));
connection.SetStatus(Connection::CONNECTED);
}
diff --git a/components/proximity_auth/throttled_bluetooth_connection_finder.cc b/components/proximity_auth/throttled_bluetooth_connection_finder.cc
new file mode 100644
index 0000000..252a47f
--- /dev/null
+++ b/components/proximity_auth/throttled_bluetooth_connection_finder.cc
@@ -0,0 +1,55 @@
+// Copyright 2015 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/throttled_bluetooth_connection_finder.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "components/proximity_auth/bluetooth_connection_finder.h"
+#include "components/proximity_auth/bluetooth_throttler.h"
+
+namespace proximity_auth {
+
+ThrottledBluetoothConnectionFinder::ThrottledBluetoothConnectionFinder(
+ scoped_ptr<BluetoothConnectionFinder> connection_finder,
+ scoped_refptr<base::TaskRunner> task_runner,
+ BluetoothThrottler* throttler)
+ : connection_finder_(connection_finder.Pass()),
+ task_runner_(task_runner),
+ throttler_(throttler),
+ weak_ptr_factory_(this) {
+}
+
+ThrottledBluetoothConnectionFinder::~ThrottledBluetoothConnectionFinder() {
+}
+
+void ThrottledBluetoothConnectionFinder::Find(
+ const ConnectionCallback& connection_callback) {
+ const base::TimeDelta delay = throttler_->GetDelay();
+
+ // Wait, if needed.
+ if (delay != base::TimeDelta()) {
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ThrottledBluetoothConnectionFinder::Find,
+ weak_ptr_factory_.GetWeakPtr(), connection_callback),
+ delay);
+ return;
+ }
+
+ connection_finder_->Find(
+ base::Bind(&ThrottledBluetoothConnectionFinder::OnConnection,
+ weak_ptr_factory_.GetWeakPtr(), connection_callback));
+}
+
+void ThrottledBluetoothConnectionFinder::OnConnection(
+ const ConnectionCallback& connection_callback,
+ scoped_ptr<Connection> connection) {
+ throttler_->OnConnection(connection.get());
+ connection_callback.Run(connection.Pass());
+}
+
+} // namespace proximity_auth
diff --git a/components/proximity_auth/throttled_bluetooth_connection_finder.h b/components/proximity_auth/throttled_bluetooth_connection_finder.h
new file mode 100644
index 0000000..c107bb3
--- /dev/null
+++ b/components/proximity_auth/throttled_bluetooth_connection_finder.h
@@ -0,0 +1,60 @@
+// Copyright 2015 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_THROTTLED_BLUETOOTH_CONNECTION_FINDER_H
+#define COMPONENTS_PROXIMITY_AUTH_THROTTLED_BLUETOOTH_CONNECTION_FINDER_H
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/proximity_auth/connection_finder.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace proximity_auth {
+
+class BluetoothConnectionFinder;
+class BluetoothThrottler;
+class Connection;
+
+// A Bluetooth connection finder that delays Find() requests according to the
+// throttler's cooldown period.
+class ThrottledBluetoothConnectionFinder : public ConnectionFinder {
+ public:
+ // Note: The |throttler| is not owned, and must outlive |this| instance.
+ ThrottledBluetoothConnectionFinder(
+ scoped_ptr<BluetoothConnectionFinder> connection_finder,
+ scoped_refptr<base::TaskRunner> task_runner,
+ BluetoothThrottler* throttler);
+ ~ThrottledBluetoothConnectionFinder() override;
+
+ // ConnectionFinder:
+ void Find(const ConnectionCallback& connection_callback) override;
+
+ private:
+ // Callback to be called when a connection is found.
+ void OnConnection(const ConnectionCallback& connection_callback,
+ scoped_ptr<Connection> connection);
+
+ // The underlying connection finder.
+ scoped_ptr<BluetoothConnectionFinder> connection_finder_;
+
+ // The task runner used for posting delayed messages.
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+ // The throttler managing this connection finder. The throttler is not owned,
+ // and must outlive |this| instance.
+ BluetoothThrottler* throttler_;
+
+ base::WeakPtrFactory<ThrottledBluetoothConnectionFinder> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThrottledBluetoothConnectionFinder);
+};
+
+} // namespace proximity_auth
+
+#endif // COMPONENTS_PROXIMITY_AUTH_THROTTLED_BLUETOOTH_CONNECTION_FINDER_H
diff --git a/components/proximity_auth/throttled_bluetooth_connection_finder_unittest.cc b/components/proximity_auth/throttled_bluetooth_connection_finder_unittest.cc
new file mode 100644
index 0000000..cf2ca7b
--- /dev/null
+++ b/components/proximity_auth/throttled_bluetooth_connection_finder_unittest.cc
@@ -0,0 +1,133 @@
+// Copyright 2015 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/throttled_bluetooth_connection_finder.h"
+
+#include "base/bind.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/time/time.h"
+#include "components/proximity_auth/bluetooth_connection_finder.h"
+#include "components/proximity_auth/bluetooth_throttler.h"
+#include "components/proximity_auth/connection.h"
+#include "components/proximity_auth/remote_device.h"
+#include "components/proximity_auth/wire_message.h"
+#include "device/bluetooth/bluetooth_uuid.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::NiceMock;
+using testing::Return;
+using testing::_;
+
+namespace proximity_auth {
+namespace {
+
+const int kPollingIntervalSeconds = 7;
+const char kUuid[] = "DEADBEEF-CAFE-FEED-FOOD-D15EA5EBEEF";
+
+// A callback that stores a found |connection| into |out|.
+void SaveConnection(scoped_ptr<Connection>* out,
+ scoped_ptr<Connection> connection) {
+ *out = connection.Pass();
+}
+
+class StubConnection : public Connection {
+ public:
+ StubConnection() : Connection(RemoteDevice()) {}
+ ~StubConnection() override {}
+
+ void Connect() override {}
+ void Disconnect() override {}
+ void SendMessageImpl(scoped_ptr<WireMessage> message) override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StubConnection);
+};
+
+class MockBluetoothThrottler : public BluetoothThrottler {
+ public:
+ MockBluetoothThrottler() {}
+ ~MockBluetoothThrottler() override {}
+
+ MOCK_CONST_METHOD0(GetDelay, base::TimeDelta());
+ MOCK_METHOD1(OnConnection, void(Connection* connection));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockBluetoothThrottler);
+};
+
+class FakeBluetoothConnectionFinder : public BluetoothConnectionFinder {
+ public:
+ FakeBluetoothConnectionFinder()
+ : BluetoothConnectionFinder(
+ RemoteDevice(),
+ device::BluetoothUUID(kUuid),
+ base::TimeDelta::FromSeconds(kPollingIntervalSeconds)) {}
+ ~FakeBluetoothConnectionFinder() override {}
+
+ void Find(const ConnectionCallback& connection_callback) override {
+ connection_callback.Run(make_scoped_ptr(new StubConnection));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeBluetoothConnectionFinder);
+};
+
+} // namespace
+
+class ProximityAuthThrottledBluetoothConnectionFinderTest
+ : public testing::Test {
+ public:
+ ProximityAuthThrottledBluetoothConnectionFinderTest()
+ : task_runner_(new base::TestSimpleTaskRunner) {}
+
+ protected:
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ NiceMock<MockBluetoothThrottler> throttler_;
+};
+
+TEST_F(ProximityAuthThrottledBluetoothConnectionFinderTest,
+ Find_ExecutesImmediatelyWhenUnthrottled) {
+ ON_CALL(throttler_, GetDelay()).WillByDefault(Return(base::TimeDelta()));
+
+ ThrottledBluetoothConnectionFinder connection_finder(
+ make_scoped_ptr(new FakeBluetoothConnectionFinder), task_runner_,
+ &throttler_);
+ scoped_ptr<Connection> connection;
+ connection_finder.Find(base::Bind(&SaveConnection, &connection));
+ EXPECT_TRUE(connection);
+}
+
+TEST_F(ProximityAuthThrottledBluetoothConnectionFinderTest,
+ Find_ExecutesAfterADelayWhenThrottled) {
+ ON_CALL(throttler_, GetDelay())
+ .WillByDefault(Return(base::TimeDelta::FromSeconds(1)));
+
+ ThrottledBluetoothConnectionFinder connection_finder(
+ make_scoped_ptr(new FakeBluetoothConnectionFinder), task_runner_,
+ &throttler_);
+ scoped_ptr<Connection> connection;
+ connection_finder.Find(base::Bind(&SaveConnection, &connection));
+ EXPECT_FALSE(connection);
+
+ // The connection should be found once the throttling period has elapsed.
+ ON_CALL(throttler_, GetDelay()).WillByDefault(Return(base::TimeDelta()));
+ task_runner_->RunUntilIdle();
+ EXPECT_TRUE(connection);
+}
+
+TEST_F(ProximityAuthThrottledBluetoothConnectionFinderTest,
+ OnConnection_ForwardsNotificationToThrottler) {
+ ON_CALL(throttler_, GetDelay()).WillByDefault(Return(base::TimeDelta()));
+
+ ThrottledBluetoothConnectionFinder connection_finder(
+ make_scoped_ptr(new FakeBluetoothConnectionFinder), task_runner_,
+ &throttler_);
+ scoped_ptr<Connection> connection;
+ EXPECT_CALL(throttler_, OnConnection(_));
+ connection_finder.Find(base::Bind(&SaveConnection, &connection));
+ EXPECT_TRUE(connection);
+}
+
+} // namespace proximity_auth