WIP [bt][gap] Introduce PairingDelegate interface
Change-Id: I466508b73b05560623f6ba2496235869a197291d
TODO: unit tests & TODOs
diff --git a/drivers/bluetooth/host/fidl/host_server.cc b/drivers/bluetooth/host/fidl/host_server.cc
index fe33acf..048d691 100644
--- a/drivers/bluetooth/host/fidl/host_server.cc
+++ b/drivers/bluetooth/host/fidl/host_server.cc
@@ -12,6 +12,7 @@
#include "garnet/drivers/bluetooth/lib/gap/gap.h"
#include "garnet/drivers/bluetooth/lib/gap/low_energy_discovery_manager.h"
#include "lib/fxl/logging.h"
+#include "lib/fxl/strings/string_printf.h"
#include "helpers.h"
#include "low_energy_central_server.h"
@@ -31,12 +32,17 @@
gatt_host_(gatt_host),
weak_ptr_factory_(this) {
FXL_DCHECK(gatt_host_);
+
+ auto self = weak_ptr_factory_.GetWeakPtr();
adapter->remote_device_cache()->set_device_updated_callback(
- [self = weak_ptr_factory_.GetWeakPtr()](const auto& device) {
+ [self](const auto& device) {
if (self) {
self->OnRemoteDeviceUpdated(device);
}
});
+
+ // TODO(armansito): Do this in response to Host::SetPairingDelegate().
+ adapter->SetPairingDelegate(self);
}
void HostServer::GetInfo(GetInfoCallback callback) {
@@ -224,6 +230,37 @@
gatt_host_->CloseServers();
}
+btlib::sm::IOCapability HostServer::io_capability() const {
+ // TODO(armansito): implement
+ return btlib::sm::IOCapability::kDisplayOnly;
+}
+
+void HostServer::StopPairing(std::string id, btlib::sm::Status status) {
+ // TODO(armansito): implement
+}
+
+void HostServer::ConfirmPairing(std::string id, ConfirmCallback confirm) {
+ FXL_LOG(INFO) << "bthost: Pairing request for device: " << id;
+
+ // TODO(armansito): Call out to PairingDelegate FIDL interface
+ confirm(true);
+}
+
+void HostServer::DisplayPasskey(std::string id, uint32_t passkey,
+ ConfirmCallback confirm) {
+ FXL_LOG(INFO) << "bthost: Pairing request for device: " << id;
+
+ // TODO(armansito): Call out to PairingDelegate FIDL interface
+ FXL_LOG(INFO) << fxl::StringPrintf("bthost: Enter passkey: %06u", passkey);
+ confirm(true);
+}
+
+void HostServer::RequestPasskey(std::string id,
+ PasskeyResponseCallback respond) {
+ // TODO(armansito): Call out to PairingDelegate FIDL interface
+ respond(-1);
+}
+
void HostServer::OnConnectionError(Server* server) {
FXL_DCHECK(server);
servers_.erase(server);
diff --git a/drivers/bluetooth/host/fidl/host_server.h b/drivers/bluetooth/host/fidl/host_server.h
index bade927..176df83 100644
--- a/drivers/bluetooth/host/fidl/host_server.h
+++ b/drivers/bluetooth/host/fidl/host_server.h
@@ -21,6 +21,7 @@
#include "garnet/drivers/bluetooth/lib/gap/bredr_connection_manager.h"
#include "garnet/drivers/bluetooth/lib/gap/bredr_discovery_manager.h"
#include "garnet/drivers/bluetooth/lib/gap/low_energy_discovery_manager.h"
+#include "garnet/drivers/bluetooth/lib/gap/pairing_delegate.h"
namespace bthost {
@@ -28,7 +29,8 @@
// Implements the Host FIDL interface. Owns all FIDL connections that have been
// opened through it.
-class HostServer : public AdapterServerBase<fuchsia::bluetooth::host::Host> {
+class HostServer : public AdapterServerBase<fuchsia::bluetooth::host::Host>,
+ public btlib::gap::PairingDelegate {
public:
HostServer(zx::channel channel, fxl::WeakPtr<btlib::gap::Adapter> adapter,
fbl::RefPtr<GattHost> gatt_host);
@@ -46,10 +48,6 @@
SetConnectableCallback callback) override;
void SetDiscoverable(bool discoverable,
SetDiscoverableCallback callback) override;
-
- // Called by |adapter()->remote_device_cache()| when a remote device is updated.
- void OnRemoteDeviceUpdated(const ::btlib::gap::RemoteDevice& remote_device);
-
void RequestLowEnergyCentral(
::fidl::InterfaceRequest<fuchsia::bluetooth::le::Central> central)
override;
@@ -61,6 +59,18 @@
override;
void Close() override;
+ // ::btlib::gap::PairingDelegate overrides:
+ btlib::sm::IOCapability io_capability() const override;
+ void StopPairing(std::string id, btlib::sm::Status status) override;
+ void ConfirmPairing(std::string id, ConfirmCallback confirm) override;
+ void DisplayPasskey(std::string id, uint32_t passkey,
+ ConfirmCallback confirm) override;
+ void RequestPasskey(std::string id, PasskeyResponseCallback respond) override;
+
+ // Called by |adapter()->remote_device_cache()| when a remote device is
+ // updated.
+ void OnRemoteDeviceUpdated(const ::btlib::gap::RemoteDevice& remote_device);
+
// Called when |server| receives a channel connection error.
void OnConnectionError(Server* server);
diff --git a/drivers/bluetooth/lib/gap/BUILD.gn b/drivers/bluetooth/lib/gap/BUILD.gn
index 7cad65c..289f7c6 100644
--- a/drivers/bluetooth/lib/gap/BUILD.gn
+++ b/drivers/bluetooth/lib/gap/BUILD.gn
@@ -27,6 +27,7 @@
"low_energy_discovery_manager.h",
"low_energy_state.cc",
"low_energy_state.h",
+ "pairing_delegate.h",
"random_address_generator.cc",
"random_address_generator.h",
"remote_device.cc",
diff --git a/drivers/bluetooth/lib/gap/adapter.cc b/drivers/bluetooth/lib/gap/adapter.cc
index c5e4766..13041f4 100644
--- a/drivers/bluetooth/lib/gap/adapter.cc
+++ b/drivers/bluetooth/lib/gap/adapter.cc
@@ -169,6 +169,11 @@
CleanUp();
}
+void Adapter::SetPairingDelegate(fxl::WeakPtr<PairingDelegate> delegate) {
+ // TODO(armansito): Do the same for BR/EDR.
+ le_connection_manager()->SetPairingDelegate(delegate);
+}
+
bool Adapter::IsDiscovering() const {
return (le_discovery_manager_ && le_discovery_manager_->discovering()) ||
(bredr_discovery_manager_ && bredr_discovery_manager_->discovering());
diff --git a/drivers/bluetooth/lib/gap/adapter.h b/drivers/bluetooth/lib/gap/adapter.h
index 24f8760..cb1976d 100644
--- a/drivers/bluetooth/lib/gap/adapter.h
+++ b/drivers/bluetooth/lib/gap/adapter.h
@@ -32,7 +32,7 @@
class BrEdrConnectionManager;
class BrEdrDiscoveryManager;
-
+class PairingDelegate;
class LowEnergyAdvertisingManager;
class LowEnergyConnectionManager;
class LowEnergyDiscoveryManager;
@@ -134,6 +134,11 @@
return &device_cache_;
}
+ // Assigns a pairing delegate to this adapter. This PairingDelegate and its
+ // I/O capabilities will be used for all future pairing procedures. Setting a
+ // new PairingDelegate cancels all ongoing pairing procedures.
+ void SetPairingDelegate(fxl::WeakPtr<PairingDelegate> delegate);
+
// Returns true if any discovery process (LE or BR/EDR) is running on this
// adapter.
bool IsDiscovering() const;
diff --git a/drivers/bluetooth/lib/gap/low_energy_connection_manager.cc b/drivers/bluetooth/lib/gap/low_energy_connection_manager.cc
index 8159f47..ba7682d 100644
--- a/drivers/bluetooth/lib/gap/low_energy_connection_manager.cc
+++ b/drivers/bluetooth/lib/gap/low_energy_connection_manager.cc
@@ -14,8 +14,10 @@
#include "garnet/drivers/bluetooth/lib/sm/util.h"
#include "lib/fxl/logging.h"
+#include "lib/fxl/random/rand.h"
#include "lib/fxl/strings/string_printf.h"
+#include "pairing_delegate.h"
#include "remote_device.h"
#include "remote_device_cache.h"
@@ -97,8 +99,11 @@
// for now.
// TODO(armansito): Refactor so that RegisterLE parameters are passed in
// the constructor. Each sm::PairingState should always have a LE bearer.
- auto pairing =
- std::make_unique<sm::PairingState>(sm::IOCapability::kDisplayOnly);
+ auto io_cap = sm::IOCapability::kNoInputNoOutput;
+ if (self->conn_mgr_->pairing_delegate()) {
+ io_cap = self->conn_mgr_->pairing_delegate()->io_capability();
+ }
+ auto pairing = std::make_unique<sm::PairingState>(io_cap);
pairing->RegisterLE(self->link_->WeakPtr(), std::move(smp));
pairing->set_legacy_tk_delegate([self](auto method, auto responder) {
if (self) {
@@ -129,6 +134,14 @@
dispatcher_);
}
+ // Cancels any on going pairing procedures and sets up SMP to use the provided
+ // new I/O capabilities for future pairing procedures.
+ void ResetPairingState(sm::IOCapability ioc) {
+ pairing_->Abort();
+
+ // TODO: assign |pairing_| with new I/O capabilities
+ }
+
size_t ref_count() const { return refs_.size(); }
const std::string& id() const { return id_; }
@@ -150,9 +163,48 @@
FXL_VLOG(1) << "gap: TK request - method: "
<< sm::util::PairingMethodToString(method);
- // TODO(armansito): Call out to the pairing delegate and obtain the key from
- // it.
- responder(true /* success */, 0);
+ auto delegate = conn_mgr_->pairing_delegate();
+ if (!delegate) {
+ FXL_LOG(ERROR) << "gap: Rejecting pairing without a PairingDelegate!";
+ responder(false, 0);
+ return;
+ }
+
+ if (method == sm::PairingMethod::kPasskeyEntryInput) {
+ // The TK will be provided by the user.
+ delegate->RequestPasskey(
+ id(), [responder = std::move(responder)](int64_t passkey) {
+ if (passkey < 0) {
+ responder(false, 0);
+ } else {
+ responder(true, static_cast<uint32_t>(passkey));
+ }
+ });
+ return;
+ }
+
+ if (method == sm::PairingMethod::kPasskeyEntryDisplay) {
+ // Randomly generate a 6 digit passkey.
+ // TODO(armansito): Use a uniform prng.
+ uint32_t passkey = fxl::RandUint64() % 1000000;
+ delegate->DisplayPasskey(
+ id(), passkey,
+ [passkey, responder = std::move(responder)](bool confirm) {
+ responder(confirm, passkey);
+ });
+ return;
+ }
+
+ // TODO(armansito): Support providing a TK out of band.
+ // OnTKRequest() should only be called for legacy pairing.
+ FXL_DCHECK(method == sm::PairingMethod::kJustWorks);
+
+ delegate->ConfirmPairing(id(),
+ [responder = std::move(responder)](bool confirm) {
+ // The TK for Just Works pairing is 0 (Vol 3,
+ // Part H, 2.3.5.2).
+ responder(confirm, 0);
+ });
}
void CloseRefs() {
@@ -410,6 +462,16 @@
return InitializeConnection(peer->identifier(), std::move(link));
}
+void LowEnergyConnectionManager::SetPairingDelegate(
+ fxl::WeakPtr<PairingDelegate> delegate) {
+ for (auto& iter : connections_) {
+ iter.second->ResetPairingState(delegate
+ ? delegate->io_capability()
+ : sm::IOCapability::kNoInputNoOutput);
+ }
+ pairing_delegate_ = delegate;
+}
+
void LowEnergyConnectionManager::SetConnectionParametersCallbackForTesting(
ConnectionParametersCallback callback) {
test_conn_params_cb_ = std::move(callback);
diff --git a/drivers/bluetooth/lib/gap/low_energy_connection_manager.h b/drivers/bluetooth/lib/gap/low_energy_connection_manager.h
index 37b0f5b..b9f6d02 100644
--- a/drivers/bluetooth/lib/gap/low_energy_connection_manager.h
+++ b/drivers/bluetooth/lib/gap/low_energy_connection_manager.h
@@ -39,6 +39,7 @@
// TODO(armansito): Document the usage pattern.
class LowEnergyConnectionManager;
+class PairingDelegate;
class RemoteDevice;
class RemoteDeviceCache;
@@ -136,6 +137,13 @@
LowEnergyConnectionRefPtr RegisterRemoteInitiatedLink(
hci::ConnectionPtr link);
+ // Returns the PairingDelegate currently assigned to this connection manager.
+ PairingDelegate* pairing_delegate() const { return pairing_delegate_.get(); }
+
+ // Assigns a new PairingDelegate to handle LE pairing events. Setting a new
+ // pairing delegate cancels all ongoing pairing procedures.
+ void SetPairingDelegate(fxl::WeakPtr<PairingDelegate> delegate);
+
// TODO(armansito): Add a RemoteDeviceCache::Observer interface and move these
// callbacks there.
@@ -301,6 +309,10 @@
fxl::RefPtr<hci::Transport> hci_;
+ // The pairing delegate used for authentication challenges. If nullptr, all
+ // pairing requests will be rejected.
+ fxl::WeakPtr<PairingDelegate> pairing_delegate_;
+
// Time after which a connection attempt is considered to have timed out. This
// is configurable to allow unit tests to set a shorter value.
int64_t request_timeout_ms_;
diff --git a/drivers/bluetooth/lib/gap/pairing_delegate.h b/drivers/bluetooth/lib/gap/pairing_delegate.h
new file mode 100644
index 0000000..b117c9d
--- /dev/null
+++ b/drivers/bluetooth/lib/gap/pairing_delegate.h
@@ -0,0 +1,54 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <lib/fit/function.h>
+
+#include "garnet/drivers/bluetooth/lib/sm/smp.h"
+#include "garnet/drivers/bluetooth/lib/sm/status.h"
+
+namespace btlib {
+namespace gap {
+
+// An object that implements PairingDelegate is responsible for fulfilling user
+// authentication challenges during pairing.
+class PairingDelegate {
+ public:
+ using ConfirmCallback = fit::function<void(bool confirm)>;
+
+ virtual ~PairingDelegate() = default;
+
+ // Returns the I/O capability of this delegate.
+ virtual sm::IOCapability io_capability() const = 0;
+
+ // Terminate any ongoing pairing challenge for the peer device with the given
+ // |identifier|.
+ virtual void StopPairing(std::string id, sm::Status status) = 0;
+
+ // Ask the user to confirm the pairing request from the device with the given
+ // |id| and confirm or reject by calling |confirm|.
+ virtual void ConfirmPairing(std::string id, ConfirmCallback confirm) = 0;
+
+ // Ask the user to confirm the 6-digit |passkey| and report status by invoking
+ // |confirm|.
+ virtual void DisplayPasskey(std::string id, uint32_t passkey, ConfirmCallback confirm) = 0;
+
+ // Ask the user to enter a 6-digit passkey or reject pairing. Report the
+ // result by invoking |respond|.
+ //
+ // A valid |passkey| must be a non-negative integer. Pass a negative value to
+ // reject pairing.
+ using PasskeyResponseCallback = fit::function<void(int64_t passkey)>;
+ virtual void RequestPasskey(std::string id, PasskeyResponseCallback respond) = 0;
+
+ protected:
+ PairingDelegate() = default;
+
+ private:
+ FXL_DISALLOW_COPY_AND_ASSIGN(PairingDelegate);
+};
+
+} // namespace gap
+} // namespace btlib
diff --git a/drivers/bluetooth/lib/sm/pairing_state.cc b/drivers/bluetooth/lib/sm/pairing_state.cc
index 8df1a6f..baac38c 100644
--- a/drivers/bluetooth/lib/sm/pairing_state.cc
+++ b/drivers/bluetooth/lib/sm/pairing_state.cc
@@ -190,6 +190,16 @@
le_smp_->InitiateFeatureExchange();
}
+void PairingState::Abort() {
+ if (!le_smp_ || !le_smp_->pairing_started())
+ return;
+
+ FXL_VLOG(1) << "sm: Abort pairing";
+ if (legacy_state_) {
+ AbortLegacyPairing(ErrorCode::kUnspecifiedReason);
+ }
+}
+
void PairingState::BeginLegacyPairingPhase2(const ByteBuffer& preq,
const ByteBuffer& pres) {
FXL_DCHECK(legacy_state_);
diff --git a/drivers/bluetooth/lib/sm/pairing_state.h b/drivers/bluetooth/lib/sm/pairing_state.h
index 64ec88e..f65006f 100644
--- a/drivers/bluetooth/lib/sm/pairing_state.h
+++ b/drivers/bluetooth/lib/sm/pairing_state.h
@@ -85,6 +85,9 @@
fit::function<void(Status status, const SecurityProperties& sec_props)>;
void UpdateSecurity(SecurityLevel level, PairingCallback callback);
+ // Abort any on-going pairing procedure. TODO: note about callbacks.
+ void Abort();
+
private:
static constexpr size_t kPairingRequestSize =
sizeof(Header) + sizeof(PairingRequestParams);