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_assert/assert.h> 17 18 #include <memory> 19 20 #include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h" 21 #include "pw_bluetooth_sapphire/internal/host/hci/local_address_delegate.h" 22 #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_connection.h" 23 #include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h" 24 #include "pw_bluetooth_sapphire/internal/host/transport/error.h" 25 26 namespace bt { 27 class AdvertisingData; 28 29 namespace hci { 30 class Transport; 31 32 class AdvertisingIntervalRange final { 33 public: 34 // Constructs an advertising interval range, capping the values based on the 35 // allowed range (Vol 2, Part E, 7.8.5). AdvertisingIntervalRange(uint16_t min,uint16_t max)36 constexpr AdvertisingIntervalRange(uint16_t min, uint16_t max) 37 : min_(std::max(min, hci_spec::kLEAdvertisingIntervalMin)), 38 max_(std::min(max, hci_spec::kLEAdvertisingIntervalMax)) { 39 PW_ASSERT(min < max); 40 } 41 min()42 uint16_t min() const { return min_; } max()43 uint16_t max() const { return max_; } 44 45 private: 46 uint16_t min_; 47 uint16_t max_; 48 }; 49 50 class LowEnergyAdvertiser : public LocalAddressClient { 51 public: 52 explicit LowEnergyAdvertiser(hci::Transport::WeakPtr hci, 53 uint16_t max_advertising_data_length); 54 ~LowEnergyAdvertiser() override = default; 55 56 using ConnectionCallback = 57 fit::function<void(std::unique_ptr<hci::LowEnergyConnection> link)>; 58 59 // TODO(armansito): The |address| parameter of this function doesn't always 60 // correspond to the advertised device address as the local address for an 61 // advertisement cannot always be configured by the advertiser. This is the 62 // case especially in the following conditions: 63 // 64 // 1. The type of |address| is "LE Public". The advertised address always 65 // corresponds to the 66 // controller's BD_ADDR. This is the case in both legacy and extended 67 // advertising. 68 // 69 // 2. The type of |address| is "LE Random" and the advertiser implements 70 // legacy advertising. 71 // Since the controller local address is shared between scan, initiation, 72 // and advertising procedures, the advertiser cannot configure this 73 // address without interfering with the state of other ongoing 74 // procedures. 75 // 76 // We should either revisit this interface or update the documentation to 77 // reflect the fact that the |address| is sometimes a hint and may or may not 78 // end up being advertised. Currently the GAP layer decides which address to 79 // pass to this call but the layering should be revisited when we add support 80 // for extended advertising. 81 // 82 // ----- 83 // 84 // Attempt to start advertising |data| with |options.flags| and scan response 85 // |scan_rsp| using advertising address |address|. If |options.anonymous| is 86 // set, |address| is ignored. 87 // 88 // If |address| is currently advertised, the advertisement is updated. 89 // 90 // If |connect_callback| is provided, the advertisement will be connectable, 91 // and the provided |status_callback| will be called with a connection 92 // reference when this advertisement is connected to and the advertisement has 93 // been stopped. 94 // 95 // |options.interval| must be a value in "controller timeslices". See 96 // hci-spec/hci_constants.h for the valid range. 97 // 98 // Provides results in |status_callback|. If advertising is setup, the final 99 // interval of advertising is provided in |interval| and |status| is kSuccess. 100 // Otherwise, |status| indicates the type of error and |interval| has no 101 // meaning. 102 // 103 // |status_callback| may be called before this function returns, but will be 104 // called before any calls to |connect_callback|. 105 // 106 // The maximum advertising and scan response data sizes are determined by the 107 // Bluetooth controller (4.x supports up to 31 bytes while 5.x is extended up 108 // to 251). If |data| and |scan_rsp| exceed this internal limit, a 109 // HostError::kAdvertisingDataTooLong or HostError::kScanResponseTooLong error 110 // will be generated. 111 struct AdvertisingOptions { AdvertisingOptionsAdvertisingOptions112 AdvertisingOptions(AdvertisingIntervalRange init_interval, 113 AdvFlags init_flags, 114 bool init_extended_pdu, 115 bool init_anonymous, 116 bool init_include_tx_power_level) 117 : interval(init_interval), 118 flags(init_flags), 119 extended_pdu(init_extended_pdu), 120 include_tx_power_level(init_include_tx_power_level), 121 anonymous(init_anonymous) {} 122 123 AdvertisingIntervalRange interval; 124 AdvFlags flags; 125 bool extended_pdu; 126 bool include_tx_power_level; 127 128 // TODO(b/42157563): anonymous advertising is currently not // supported 129 bool anonymous; 130 }; 131 132 // Core Spec Version 5.4, Volume 4, Part E, Section 7.8.53: These fields are 133 // the same as those defined in advertising event properties. 134 // 135 // TODO(fxbug.dev/333129711): LEAdvertisingEventProperties is 136 // currently defined in Emboss as a bits field. Unfortunately, this means that 137 // we cannot use it as storage within our own code. Instead, we have to 138 // redefine a struct with the same fields in it if we want to use it as 139 // storage. 140 struct AdvertisingEventProperties { 141 bool connectable = false; 142 bool scannable = false; 143 bool directed = false; 144 bool high_duty_cycle_directed_connectable = false; 145 bool use_legacy_pdus = false; 146 bool anonymous_advertising = false; 147 bool include_tx_power = false; 148 IsDirectedAdvertisingEventProperties149 bool IsDirected() const { 150 return directed || high_duty_cycle_directed_connectable; 151 } 152 }; 153 154 // Determine the properties of an advertisement based on the parameters the 155 // client has passed in. For example, if the client has included a scan 156 // response, the advertisement should be scannable. 157 static AdvertisingEventProperties GetAdvertisingEventProperties( 158 const AdvertisingData& data, 159 const AdvertisingData& scan_rsp, 160 const AdvertisingOptions& options, 161 const ConnectionCallback& connect_callback); 162 163 // Convert individual advertisement properties (e.g. connecatble, scannable, 164 // directed, etc) to a legacy LEAdvertisingType 165 static pw::bluetooth::emboss::LEAdvertisingType 166 AdvertisingEventPropertiesToLEAdvertisingType( 167 const AdvertisingEventProperties& p); 168 169 virtual void StartAdvertising(const DeviceAddress& address, 170 const AdvertisingData& data, 171 const AdvertisingData& scan_rsp, 172 const AdvertisingOptions& options, 173 ConnectionCallback connect_callback, 174 ResultFunction<> result_callback) = 0; 175 176 // Stops advertisement on all currently advertising addresses. Idempotent and 177 // asynchronous. 178 virtual void StopAdvertising(); 179 180 // Stops any advertisement currently active on |address|. Idempotent and 181 // asynchronous. 182 virtual void StopAdvertising(const DeviceAddress& address, 183 bool extended_pdu) = 0; 184 185 // Callback for an incoming LE connection. This function should be called in 186 // reaction to any connection that was not initiated locally. This object will 187 // determine if it was a result of an active advertisement and route the 188 // connection accordingly. 189 virtual void OnIncomingConnection( 190 hci_spec::ConnectionHandle handle, 191 pw::bluetooth::emboss::ConnectionRole role, 192 const DeviceAddress& peer_address, 193 const hci_spec::LEConnectionParameters& conn_params) = 0; 194 195 // Returns true if currently advertising at all IsAdvertising()196 bool IsAdvertising() const { return !connection_callbacks_.empty(); } 197 198 // Returns true if currently advertising for the given address IsAdvertising(const DeviceAddress & address,bool extended_pdu)199 bool IsAdvertising(const DeviceAddress& address, bool extended_pdu) const { 200 return connection_callbacks_.count({address, extended_pdu}) != 0; 201 } 202 203 // Returns the number of advertisements currently registered NumAdvertisements()204 size_t NumAdvertisements() const { return connection_callbacks_.size(); } 205 206 // Returns the maximum number of advertisements that can be supported 207 virtual size_t MaxAdvertisements() const = 0; 208 209 protected: 210 // Build the HCI command packet to enable advertising for the flavor of low 211 // energy advertising being implemented. 212 virtual CommandPacket BuildEnablePacket( 213 const DeviceAddress& address, 214 pw::bluetooth::emboss::GenericEnableParam enable, 215 bool extended_pdu) = 0; 216 217 // Build the HCI command packet to set the advertising parameters for the 218 // flavor of low energy advertising being implemented. 219 virtual std::optional<CommandPacket> BuildSetAdvertisingParams( 220 const DeviceAddress& address, 221 const AdvertisingEventProperties& properties, 222 pw::bluetooth::emboss::LEOwnAddressType own_address_type, 223 const AdvertisingIntervalRange& interval, 224 bool extended_pdu) = 0; 225 226 // Build the HCI command packet to set the advertising data for the flavor of 227 // low energy advertising being implemented. 228 virtual std::vector<CommandPacket> BuildSetAdvertisingData( 229 const DeviceAddress& address, 230 const AdvertisingData& data, 231 AdvFlags flags, 232 bool extended_pdu) = 0; 233 234 // Build the HCI command packet to delete the advertising parameters from the 235 // controller for the flavor of low energy advertising being implemented. This 236 // method is used when stopping an advertisement. 237 virtual CommandPacket BuildUnsetAdvertisingData(const DeviceAddress& address, 238 bool extended_pdu) = 0; 239 240 // Build the HCI command packet to set the data sent in a scan response (if 241 // requested) for the flavor of low energy advertising being implemented. 242 virtual std::vector<CommandPacket> BuildSetScanResponse( 243 const DeviceAddress& address, 244 const AdvertisingData& scan_rsp, 245 bool extended_pdu) = 0; 246 247 // Build the HCI command packet to delete the advertising parameters from the 248 // controller for the flavor of low energy advertising being implemented. 249 virtual CommandPacket BuildUnsetScanResponse(const DeviceAddress& address, 250 bool extended_pdu) = 0; 251 252 // Build the HCI command packet to remove the advertising set entirely from 253 // the controller's memory for the flavor of low energy advertising being 254 // implemented. 255 virtual CommandPacket BuildRemoveAdvertisingSet(const DeviceAddress& address, 256 bool extended_pdu) = 0; 257 258 // Called when the command packet created with BuildSetAdvertisingParams 259 // returns with a result OnSetAdvertisingParamsComplete(const EventPacket &)260 virtual void OnSetAdvertisingParamsComplete(const EventPacket&) {} 261 262 // Called when a sequence of HCI commands that form a single operation (e.g. 263 // start advertising, stop advertising) completes in its entirety. Subclasses 264 // can override this method to be notified when the HCI command runner is 265 // available once again. OnCurrentOperationComplete()266 virtual void OnCurrentOperationComplete() {} 267 268 // Get the current limit in bytes of the advertisement data supported. 269 size_t GetSizeLimit(const AdvertisingEventProperties& properties, 270 const AdvertisingOptions& options) const; 271 272 // Check whether we can actually start advertising given the combination of 273 // input parameters (e.g. check that the requested advertising data and scan 274 // response will actually fit within the size limitations of the advertising 275 // PDUs) 276 fit::result<HostError> CanStartAdvertising( 277 const DeviceAddress& address, 278 const AdvertisingData& data, 279 const AdvertisingData& scan_rsp, 280 const AdvertisingOptions& options, 281 const ConnectionCallback& connect_callback) const; 282 283 // Unconditionally start advertising (all checks must be performed in the 284 // methods that call this one). 285 void StartAdvertisingInternal(const DeviceAddress& address, 286 const AdvertisingData& data, 287 const AdvertisingData& scan_rsp, 288 const AdvertisingOptions& options, 289 ConnectionCallback connect_callback, 290 hci::ResultFunction<> callback); 291 292 // Unconditionally stop advertising (all checks muts be performed in the 293 // methods that call this one). 294 void StopAdvertisingInternal(const DeviceAddress& address, bool extended_pdu); 295 296 // Handle shared housekeeping tasks when an incoming connection is completed 297 // (e.g. clean up internal state, call callbacks, etc) 298 void CompleteIncomingConnection( 299 hci_spec::ConnectionHandle handle, 300 pw::bluetooth::emboss::ConnectionRole role, 301 const DeviceAddress& local_address, 302 const DeviceAddress& peer_address, 303 const hci_spec::LEConnectionParameters& conn_params, 304 bool extended_pdu); 305 hci_cmd_runner()306 SequentialCommandRunner& hci_cmd_runner() const { return *hci_cmd_runner_; } hci()307 hci::Transport::WeakPtr hci() const { return hci_; } 308 309 private: 310 struct StagedParameters { 311 AdvertisingData data; 312 AdvertisingData scan_rsp; 313 resetStagedParameters314 void reset() { 315 AdvertisingData blank; 316 blank.Copy(&data); 317 blank.Copy(&scan_rsp); 318 } 319 }; 320 321 // Continuation function for starting advertising, called automatically via 322 // callbacks in StartAdvertisingInternal. Developers should not call this 323 // function directly. 324 bool StartAdvertisingInternalStep2(const DeviceAddress& address, 325 const AdvertisingOptions& options, 326 ConnectionCallback connect_callback, 327 hci::ResultFunction<> result_callback); 328 329 // Enqueue onto the HCI command runner the HCI commands necessary to stop 330 // advertising and completely remove a given address from the controller's 331 // memory. If even one of the HCI commands cannot be generated for some 332 // reason, no HCI commands are enqueued. 333 bool EnqueueStopAdvertisingCommands(const DeviceAddress& address, 334 bool extended_pdu); 335 336 hci::Transport::WeakPtr hci_; 337 std::unique_ptr<SequentialCommandRunner> hci_cmd_runner_; 338 StagedParameters staged_parameters_; 339 340 struct TupleKeyHasher { operatorTupleKeyHasher341 size_t operator()(const std::tuple<DeviceAddress, bool>& t) const { 342 std::hash<DeviceAddress> device_address_hasher; 343 std::hash<bool> bool_hasher; 344 const auto& [address, extended_pdu] = t; 345 return device_address_hasher(address) ^ bool_hasher(extended_pdu); 346 } 347 }; 348 std::unordered_map<std::tuple<DeviceAddress, bool>, 349 ConnectionCallback, 350 TupleKeyHasher> 351 connection_callbacks_; 352 353 uint16_t max_advertising_data_length_ = hci_spec::kMaxLEAdvertisingDataLength; 354 355 BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyAdvertiser); 356 }; 357 358 } // namespace hci 359 } // namespace bt 360