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