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);