1 // Copyright 2023 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 15 #pragma once 16 #include "pw_bluetooth_sapphire/internal/host/common/device_address.h" 17 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_connection.h" 18 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_request.h" 19 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_interrogator.h" 20 #include "pw_bluetooth_sapphire/internal/host/hci/connection.h" 21 #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_connector.h" 22 #include "pw_bluetooth_sapphire/internal/host/l2cap/channel_manager.h" 23 #include "pw_bluetooth_sapphire/internal/host/transport/command_channel.h" 24 25 namespace bt::gap::internal { 26 27 // LowEnergyConnector is a single-use utility for executing either the outbound 28 // connection procedure or the inbound connection procedure (which is a subset 29 // of the outbound procedure). The outbound procedure first scans for and 30 // connects to a peer, whereas the inbound procedure starts with an existing 31 // connection. Next, both procedures interrogate the peer. After construction, 32 // the connection procedure may be started with either StartOutbound() or 33 // StartInbound() and will run to completion unless Cancel() is called. 34 class LowEnergyConnector final { 35 public: 36 using ResultCallback = 37 hci::ResultCallback<std::unique_ptr<LowEnergyConnection>>; 38 39 // Create a connector for connecting to |peer_id|. The connection will be 40 // established with the parameters specified in |options|. 41 LowEnergyConnector(PeerId peer_id, 42 LowEnergyConnectionOptions options, 43 hci::CommandChannel::WeakPtr cmd_channel, 44 PeerCache* peer_cache, 45 WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr, 46 l2cap::ChannelManager* l2cap, 47 gatt::GATT::WeakPtr gatt, 48 pw::async::Dispatcher& dispatcher); 49 50 // Instances should only be destroyed after the result callback is called 51 // (except for stack tear down). Due to the asynchronous nature of cancelling 52 // the connection process, it is NOT safe to destroy a connector before the 53 // result callback has been called. The connector will be unable to wait for 54 // the HCI connection cancellation to complete, which can lead to failure to 55 // connect in later connectors (as the hci::LowEnergyConnector is still 56 // pending). 57 ~LowEnergyConnector(); 58 59 // Initiate an outbound connection. |cb| will be called with the result of the 60 // procedure. Must only be called once. 61 void StartOutbound(pw::chrono::SystemClock::duration request_timeout, 62 hci::LowEnergyConnector* connector, 63 LowEnergyDiscoveryManager::WeakPtr discovery_manager, 64 ResultCallback cb); 65 66 // Start interrogating peer using an already established |connection|. |cb| 67 // will be called with the result of the procedure. Must only be called once. 68 void StartInbound(std::unique_ptr<hci::LowEnergyConnection> connection, 69 ResultCallback cb); 70 71 // Canceling a connector that has not started or has already completed is a 72 // no-op. Otherwise, the pending result callback will be called asynchronously 73 // once cancelation has succeeded. 74 void Cancel(); 75 76 // Attach connector inspect node as a child node of |parent| with the name 77 // |name|. 78 void AttachInspect(inspect::Node& parent, std::string name); 79 80 private: 81 enum class State { 82 kDefault, 83 kStartingScanning, // Outbound only 84 kScanning, // Outbound only 85 kConnecting, // Outbound only 86 kInterrogating, // Outbound & inbound 87 kAwaitingConnectionFailedToBeEstablishedDisconnect, // Outbound & inbound 88 kPauseBeforeConnectionRetry, // Outbound only 89 kComplete, // Outbound & inbound 90 kFailed, // Outbound & inbound 91 }; 92 93 static const char* StateToString(State); 94 95 // Initiate scanning for peer before connecting to ensure it is advertising. 96 void StartScanningForPeer(); 97 void OnScanStart(LowEnergyDiscoverySessionPtr session); 98 99 // Initiate HCI connection procedure. 100 void RequestCreateConnection(); 101 void OnConnectResult(hci::Result<> status, 102 std::unique_ptr<hci::LowEnergyConnection> link); 103 104 // Creates LowEnergyConnection and initializes fixed channels & timers. 105 // Returns true on success, false on failure. 106 bool InitializeConnection(std::unique_ptr<hci::LowEnergyConnection> link); 107 108 void StartInterrogation(); 109 void OnInterrogationComplete(hci::Result<> status); 110 111 // Handle a disconnect during kInterrogating or 112 // kAwaitingConnectionFailedToBeEstablishedDisconnect. 113 void OnPeerDisconnect(pw::bluetooth::emboss::StatusCode status); 114 115 // Returns true if the connection is retried. 116 // 117 // The link layer only considers a connection established after a packet is 118 // received from the peer before (6 * connInterval), even though it notifies 119 // the host immediately after sending a CONNECT_IND pdu. See Core Spec v5.2, 120 // Vol 6, Part B, Sec 4.5 for details. 121 // 122 // In the field, we have noticed a substantial amount of 0x3e (Connection 123 // Failed to be Established) HCI link errors occurring on links AFTER being 124 // notified of successful HCI-level connection. To work around this issue, we 125 // perform link-layer interrogation on the peer before returning 126 // gap::LowEnergyConnections to higher layer clients. If we receive the 0x3e 127 // error during interrogation, we will retry the connection process a number 128 // of times. 129 bool MaybeRetryConnection(); 130 131 void NotifySuccess(); 132 void NotifyFailure(hci::Result<> status = ToResult(HostError::kFailed)); 133 134 // Set is_outbound_ and its Inspect property. 135 void set_is_outbound(bool is_outbound); 136 137 pw::async::Dispatcher& dispatcher_; 138 139 StringInspectable<State> state_{ 140 State::kDefault, 141 /*convert=*/[](auto s) { return StateToString(s); }}; 142 143 PeerId peer_id_; 144 DeviceAddress peer_address_; 145 PeerCache* peer_cache_; 146 147 // Layer pointers to be passed to LowEnergyConnection. 148 l2cap::ChannelManager* l2cap_; 149 gatt::GATT::WeakPtr gatt_; 150 151 // True if this connector is connecting an outbound connection, false if it is 152 // connecting an inbound connection. 153 std::optional<bool> is_outbound_; 154 155 // Time after which an outbound HCI connection request is considered to have 156 // timed out. This is configurable to allow unit tests to set a shorter value. 157 pw::chrono::SystemClock::duration hci_request_timeout_; 158 159 LowEnergyConnectionOptions options_; 160 161 // Callback used to return the result of the connection procedure to the 162 // owning class. 163 ResultCallback result_cb_; 164 165 // Used to connect outbound connections during the kConnecting state. 166 hci::LowEnergyConnector* hci_connector_ = nullptr; 167 168 // The LowEnergyConnection to be passed to LowEnergyConnectionManager. Created 169 // during the kConnecting state for outbound connections, or during 170 // construction for inbound connections. 171 std::unique_ptr<internal::LowEnergyConnection> connection_; 172 173 // For outbound connections, this is a 0-indexed counter of which connection 174 // attempt the connector is on. 175 IntInspectable<int> connection_attempt_{0}; 176 177 SmartTask request_create_connection_task_{dispatcher_}; 178 179 // Task called after the scan attempt times out. 180 std::optional<SmartTask> scan_timeout_task_; 181 182 std::unique_ptr<LowEnergyDiscoverySession> discovery_session_; 183 184 // Sends HCI commands that request version and feature support information 185 // from peer controllers. Initialized only during interrogation. 186 std::optional<LowEnergyInterrogator> interrogator_; 187 188 LowEnergyDiscoveryManager::WeakPtr discovery_manager_; 189 190 hci::CommandChannel::WeakPtr cmd_; 191 192 // Only used to construct a LowEnergyConnection. 193 WeakSelf<LowEnergyConnectionManager>::WeakPtr le_connection_manager_; 194 195 struct InspectProperties { 196 inspect::StringProperty peer_id; 197 inspect::BoolProperty is_outbound; 198 }; 199 InspectProperties inspect_properties_; 200 inspect::Node inspect_node_; 201 202 WeakSelf<LowEnergyConnector> weak_self_{this}; 203 }; 204 205 } // namespace bt::gap::internal 206