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