blob: 0db6556cca3a248d741dd41a59393dff19ccf31a [file] [log] [blame]
// Copyright 2017 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.
#ifndef GARNET_DRIVERS_BLUETOOTH_LIB_GAP_LOW_ENERGY_CONNECTION_MANAGER_H_
#define GARNET_DRIVERS_BLUETOOTH_LIB_GAP_LOW_ENERGY_CONNECTION_MANAGER_H_
#include <list>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <lib/async/dispatcher.h>
#include <lib/fit/function.h>
#include "garnet/drivers/bluetooth/lib/data/domain.h"
#include "garnet/drivers/bluetooth/lib/gap/gap.h"
#include "garnet/drivers/bluetooth/lib/gatt/gatt.h"
#include "garnet/drivers/bluetooth/lib/hci/command_channel.h"
#include "garnet/drivers/bluetooth/lib/hci/control_packets.h"
#include "garnet/drivers/bluetooth/lib/hci/low_energy_connector.h"
#include "lib/fxl/macros.h"
#include "lib/fxl/memory/ref_ptr.h"
#include "lib/fxl/memory/weak_ptr.h"
namespace btlib {
namespace hci {
class Transport;
} // namespace hci
namespace gap {
namespace internal {
class LowEnergyConnection;
} // namespace internal
// TODO(armansito): Document the usage pattern.
class LowEnergyConnectionManager;
class PairingDelegate;
class RemoteDevice;
class RemoteDeviceCache;
class LowEnergyConnectionRef final {
public:
// Destroying this object releases its reference to the underlying connection.
~LowEnergyConnectionRef();
// Releases this object's reference to the underlying connection.
void Release();
// Returns true if the underlying connection is still active.
bool active() const { return active_; }
// Sets a callback to be called when the underlying connection is closed.
void set_closed_callback(fit::closure callback) {
closed_cb_ = std::move(callback);
}
const std::string& device_identifier() const { return device_id_; }
hci::ConnectionHandle handle() const { return handle_; }
private:
friend class LowEnergyConnectionManager;
friend class internal::LowEnergyConnection;
LowEnergyConnectionRef(const std::string& device_id,
hci::ConnectionHandle handle,
fxl::WeakPtr<LowEnergyConnectionManager> manager);
// Called by LowEnergyConnectionManager when the underlying connection is
// closed. Notifies |closed_cb_|.
void MarkClosed();
bool active_;
std::string device_id_;
hci::ConnectionHandle handle_;
fxl::WeakPtr<LowEnergyConnectionManager> manager_;
fit::closure closed_cb_;
fxl::ThreadChecker thread_checker_;
FXL_DISALLOW_COPY_AND_ASSIGN(LowEnergyConnectionRef);
};
using LowEnergyConnectionRefPtr = std::unique_ptr<LowEnergyConnectionRef>;
class LowEnergyConnectionManager final {
public:
LowEnergyConnectionManager(fxl::RefPtr<hci::Transport> hci,
hci::LowEnergyConnector* connector,
RemoteDeviceCache* device_cache,
fbl::RefPtr<data::Domain> data_domain,
fbl::RefPtr<gatt::GATT> gatt);
~LowEnergyConnectionManager();
// Allows a caller to claim shared ownership over a connection to the
// requested remote LE device identified by |device_identifier|. Returns
// false, if |device_identifier| is not recognized, otherwise:
//
// * If the requested device is already connected, this method
// asynchronously returns a LowEnergyConnectionRef without sending any
// requests to the controller. This is done for both local and remote
// initiated connections (i.e. the local adapter can either be in the LE
// central or peripheral roles). |callback| always succeeds.
//
// * If the requested device is NOT connected, then this method initiates a
// connection to the requested device using one of the GAP central role
// connection establishment procedures described in Core Spec v5.0, Vol 3,
// Part C, Section 9.3. A LowEnergyConnectionRef is asynchronously
// returned to the caller once the connection has been set up.
//
// The status of the procedure is reported in |callback| in the case of an
// error.
//
// |callback| is posted on the creation thread's dispatcher.
using ConnectionResultCallback =
fit::function<void(hci::Status, LowEnergyConnectionRefPtr)>;
bool Connect(const std::string& device_identifier,
ConnectionResultCallback callback);
RemoteDeviceCache* device_cache() { return device_cache_; }
// Disconnects any existing LE connection to |device_identifier|, invalidating
// all active LowEnergyConnectionRefs. Returns false if |device_identifier| is
// not recognized or the corresponding remote device is not connected.
bool Disconnect(const std::string& device_identifier);
// Initializes a new connection over the given |link| and returns a connection
// reference. Returns nullptr if the connection was rejected.
//
// |link| must be the result of a remote initiated connection.
//
// TODO(armansito): Add an |own_address| parameter for the locally advertised
// address that was connected to.
//
// A link with the given handle should not have been previously registered.
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 authentication challenges.
// Replacing an existing pairing delegate cancels all ongoing pairing
// procedures. If a delegate is not set then all pairing requests will be
// rejected.
void SetPairingDelegate(fxl::WeakPtr<PairingDelegate> delegate);
// TODO(armansito): Add a RemoteDeviceCache::Observer interface and move these
// callbacks there.
// Called when the connection parameters on a link have been updated.
using ConnectionParametersCallback = fit::function<void(const RemoteDevice&)>;
void SetConnectionParametersCallbackForTesting(
ConnectionParametersCallback callback);
// Called when a link with the given handle gets disconnected. This event is
// guaranteed to be called before invalidating connection references.
// |callback| is run on the creation thread.
//
// NOTE: This is intended ONLY for unit tests. Clients should watch for
// disconnection events using LowEnergyConnectionRef::set_closed_callback()
// instead. DO NOT use outside of tests.
using DisconnectCallback = fit::function<void(hci::ConnectionHandle)>;
void SetDisconnectCallbackForTesting(DisconnectCallback callback);
// Sets the timeout interval to be used on future connect requests. The
// default value is kLECreateConnecionTimeoutMs.
void set_request_timeout_for_testing(int64_t value_ms) {
request_timeout_ms_ = value_ms;
}
private:
friend class LowEnergyConnectionRef;
// Mapping from device identifiers to open LE connections.
using ConnectionMap =
std::unordered_map<std::string,
std::unique_ptr<internal::LowEnergyConnection>>;
class PendingRequestData {
public:
PendingRequestData(const common::DeviceAddress& address,
ConnectionResultCallback first_callback);
PendingRequestData() = default;
~PendingRequestData() = default;
PendingRequestData(PendingRequestData&&) = default;
PendingRequestData& operator=(PendingRequestData&&) = default;
void AddCallback(ConnectionResultCallback cb) {
callbacks_.push_back(std::move(cb));
}
// Notifies all elements in |callbacks| with |status| and the result of
// |func|.
using RefFunc = fit::function<LowEnergyConnectionRefPtr()>;
void NotifyCallbacks(hci::Status status, const RefFunc& func);
const common::DeviceAddress& address() const { return address_; }
private:
common::DeviceAddress address_;
std::list<ConnectionResultCallback> callbacks_;
FXL_DISALLOW_COPY_AND_ASSIGN(PendingRequestData);
};
// Called by LowEnergyConnectionRef::Release().
void ReleaseReference(LowEnergyConnectionRef* conn_ref);
// Called when |connector_| completes a pending request. Initiates a new
// connection attempt for the next device in the pending list, if any.
void TryCreateNextConnection();
// Initiates a connection attempt to |peer|.
void RequestCreateConnection(RemoteDevice* peer);
// Initializes the connection to the peer with the given identifier and
// returns the initial reference to it. This method is responsible for setting
// up all data bearers.
LowEnergyConnectionRefPtr InitializeConnection(const std::string& device_id,
hci::ConnectionPtr link);
// Adds a new connection reference to an existing connection to the device
// with the ID |device_identifier| and returns it. Returns nullptr if
// |device_identifier| is not recognized.
LowEnergyConnectionRefPtr AddConnectionRef(
const std::string& device_identifier);
// Cleans up a connection state. This results in a HCI_Disconnect command
// if |close_link| is true, and notifies any referenced
// LowEnergyConnectionRefs of the disconnection. Marks the corresponding
// RemoteDeviceCache entry as disconnected and cleans up all data bearers.
//
// |conn_state| will have been removed from the underlying map at the time of
// a call. Its ownership is passed to the method for disposal.
//
// This is also responsible for unregistering the link from managed subsystems
// (e.g. L2CAP).
void CleanUpConnection(std::unique_ptr<internal::LowEnergyConnection> conn,
bool close_link = true);
// Called by |connector_| when a new locally initiated LE connection has been
// created.
void RegisterLocalInitiatedLink(hci::ConnectionPtr link);
// Updates |device_cache_| with the given |link| and returns the corresponding
// RemoteDevice.
//
// Creates a new RemoteDevice if |link| matches a peer that did not
// previously exist in the cache. Otherwise this updates and returns an
// existing RemoteDevice.
//
// The returned device is marked as non-temporary and its connection
// parameters are updated.
//
// Called by RegisterRemoteInitiatedLink() and RegisterLocalInitiatedLink().
RemoteDevice* UpdateRemoteDeviceWithLink(const hci::Connection& link);
// Called by |connector_| to indicate the result of a connect request.
void OnConnectResult(const std::string& device_identifier,
hci::Status status,
hci::ConnectionPtr link);
// Event handler for the HCI Disconnection Complete event.
// TODO(armansito): This needs to be shared between the BR/EDR and LE
// connection managers, so this handler should be moved elsewhere.
void OnDisconnectionComplete(const hci::EventPacket& event);
// Event handler for the HCI LE Connection Update Complete event.
void OnLEConnectionUpdateComplete(const hci::EventPacket& event);
// Called when the preferred connection parameters have been received for a LE
// peripheral. This can happen in the form of:
//
// 1. <<Slave Connection Interval Range>> advertising data field
// 2. "Peripheral Preferred Connection Parameters" GATT characteristic
// (under "GAP" service)
// 3. HCI LE Remote Connection Parameter Request Event
// 4. L2CAP Connection Parameter Update request
//
// TODO(armansito): Support #1, #2, and #3 above.
//
// This method caches |params| for later connection attempts and sends the
// parameters to the controller if the initializing procedures are complete
// (since we use more agressing initial parameters for pairing and service
// discovery, as recommended by the specification in v5.0, Vol 3, Part C,
// Section 9.3.12.1).
//
// |device_identifier| uniquely identifies the peer. |handle| represents the
// the logical link that |params| should be applied to.
void OnNewLEConnectionParams(
const std::string& device_identifier,
hci::ConnectionHandle handle,
const hci::LEPreferredConnectionParameters& params);
// Tells the controller to use the given connection |params| on the given
// logical link |handle|.
void UpdateConnectionParams(
hci::ConnectionHandle handle,
const hci::LEPreferredConnectionParameters& params);
// Returns an iterator into |connections_| if a connection is found that
// matches the given logical link |handle|. Otherwise, returns an iterator
// that is equal to |connections_.end()|.
//
// The general rules of validity around std::unordered_map::iterator apply to
// the returned value.
ConnectionMap::iterator FindConnection(hci::ConnectionHandle handle);
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_;
// The dispather for all asynchronous tasks.
async_dispatcher_t* dispatcher_;
// The device cache is used to look up and persist remote device data that is
// relevant during connection establishment (such as the address, preferred
// connetion parameters, etc). Expected to outlive this instance.
RemoteDeviceCache* device_cache_; // weak
// The reference to the data domain, used to interact with the L2CAP layer to
// manage LE logical links, fixed channels, and LE-specific L2CAP signaling
// events (e.g. connection parameter update).
fbl::RefPtr<data::Domain> data_domain_;
// The GATT layer reference, used to add and remove ATT data bearers and
// service discovery.
fbl::RefPtr<gatt::GATT> gatt_;
// Local GATT service registry.
std::unique_ptr<gatt::LocalServiceManager> gatt_registry_;
// Event handler ID for the HCI Disconnection Complete event.
hci::CommandChannel::EventHandlerId disconn_cmpl_handler_id_;
// Event handler ID for the HCI LE Connection Update Complete event.
hci::CommandChannel::EventHandlerId conn_update_cmpl_handler_id_;
// Callbacks used by unit tests to observe connection state events.
ConnectionParametersCallback test_conn_params_cb_;
DisconnectCallback test_disconn_cb_;
// Outstanding connection requests based on remote device ID.
std::unordered_map<std::string, PendingRequestData> pending_requests_;
// Mapping from device identifiers to currently open LE connections.
ConnectionMap connections_;
// Performs the Direct Connection Establishment procedure. |connector_| must
// out-live this connection manager.
hci::LowEnergyConnector* connector_; // weak
// Keep this as the last member to make sure that all weak pointers are
// invalidated before other members get destroyed.
fxl::WeakPtrFactory<LowEnergyConnectionManager> weak_ptr_factory_;
FXL_DISALLOW_COPY_AND_ASSIGN(LowEnergyConnectionManager);
};
} // namespace gap
} // namespace btlib
#endif // GARNET_DRIVERS_BLUETOOTH_LIB_GAP_LOW_ENERGY_CONNECTION_MANAGER_H_