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