• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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