xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/hci/legacy_low_energy_advertiser.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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 #include "pw_bluetooth_sapphire/internal/host/hci/legacy_low_energy_advertiser.h"
16 
17 #include "pw_bluetooth_sapphire/internal/host/common/advertising_data.h"
18 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
19 #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
20 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
21 #include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h"
22 #include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
23 
24 namespace bt::hci {
25 namespace pwemb = pw::bluetooth::emboss;
26 
~LegacyLowEnergyAdvertiser()27 LegacyLowEnergyAdvertiser::~LegacyLowEnergyAdvertiser() {
28   // This object is probably being destroyed because the stack is shutting down,
29   // in which case the HCI layer may have already been destroyed.
30   if (!hci().is_alive() || !hci()->command_channel()) {
31     return;
32   }
33 
34   StopAdvertising();
35 }
36 
BuildEnablePacket(const DeviceAddress &,pwemb::GenericEnableParam enable,bool)37 CommandPacket LegacyLowEnergyAdvertiser::BuildEnablePacket(
38     const DeviceAddress&,
39     pwemb::GenericEnableParam enable,
40     bool /*extended_pdu*/) {
41   auto packet =
42       hci::CommandPacket::New<pwemb::LESetAdvertisingEnableCommandWriter>(
43           hci_spec::kLESetAdvertisingEnable);
44   auto packet_view = packet.view_t();
45   packet_view.advertising_enable().Write(enable);
46   return packet;
47 }
48 
BuildSetAdvertisingData(const DeviceAddress &,const AdvertisingData & data,AdvFlags flags,bool)49 std::vector<CommandPacket> LegacyLowEnergyAdvertiser::BuildSetAdvertisingData(
50     const DeviceAddress&,
51     const AdvertisingData& data,
52     AdvFlags flags,
53     bool /*extended_pdu*/) {
54   if (data.CalculateBlockSize() == 0) {
55     std::vector<CommandPacket> packets;
56     return packets;
57   }
58 
59   auto packet = CommandPacket::New<pwemb::LESetAdvertisingDataCommandWriter>(
60       hci_spec::kLESetAdvertisingData);
61   auto params = packet.view_t();
62   const uint8_t data_length =
63       static_cast<uint8_t>(data.CalculateBlockSize(/*include_flags=*/true));
64   params.advertising_data_length().Write(data_length);
65 
66   MutableBufferView adv_view(params.advertising_data().BackingStorage().data(),
67                              data_length);
68   data.WriteBlock(&adv_view, flags);
69 
70   std::vector<CommandPacket> packets;
71   packets.reserve(1);
72   packets.emplace_back(std::move(packet));
73   return packets;
74 }
75 
BuildSetScanResponse(const DeviceAddress &,const AdvertisingData & scan_rsp,bool)76 std::vector<CommandPacket> LegacyLowEnergyAdvertiser::BuildSetScanResponse(
77     const DeviceAddress&,
78     const AdvertisingData& scan_rsp,
79     bool /*extended_pdu*/) {
80   if (scan_rsp.CalculateBlockSize() == 0) {
81     std::vector<CommandPacket> packets;
82     return packets;
83   }
84 
85   auto packet = CommandPacket::New<pwemb::LESetScanResponseDataCommandWriter>(
86       hci_spec::kLESetScanResponseData);
87   auto params = packet.view_t();
88   const uint8_t data_length =
89       static_cast<uint8_t>(scan_rsp.CalculateBlockSize());
90   params.scan_response_data_length().Write(data_length);
91 
92   MutableBufferView scan_data_view(
93       params.scan_response_data().BackingStorage().data(), data_length);
94   scan_rsp.WriteBlock(&scan_data_view, /*flags=*/std::nullopt);
95 
96   std::vector<CommandPacket> packets;
97   packets.reserve(1);
98   packets.emplace_back(std::move(packet));
99   return packets;
100 }
101 
102 std::optional<CommandPacket>
BuildSetAdvertisingParams(const DeviceAddress &,const AdvertisingEventProperties & properties,pwemb::LEOwnAddressType own_address_type,const AdvertisingIntervalRange & interval,bool)103 LegacyLowEnergyAdvertiser::BuildSetAdvertisingParams(
104     const DeviceAddress&,
105     const AdvertisingEventProperties& properties,
106     pwemb::LEOwnAddressType own_address_type,
107     const AdvertisingIntervalRange& interval,
108     bool /*extended_pdu*/) {
109   auto packet =
110       CommandPacket::New<pwemb::LESetAdvertisingParametersCommandWriter>(
111           hci_spec::kLESetAdvertisingParameters);
112   auto params = packet.view_t();
113   params.advertising_interval_min().Write(interval.min());
114   params.advertising_interval_max().Write(interval.max());
115   params.adv_type().Write(
116       AdvertisingEventPropertiesToLEAdvertisingType(properties));
117   params.own_address_type().Write(own_address_type);
118   params.advertising_channel_map().BackingStorage().WriteUInt(
119       hci_spec::kLEAdvertisingChannelAll);
120   params.advertising_filter_policy().Write(
121       pwemb::LEAdvertisingFilterPolicy::ALLOW_ALL);
122 
123   // We don't support directed advertising yet, so leave peer_address and
124   // peer_address_type as 0x00
125   // (|packet| parameters are initialized to zero above).
126 
127   return packet;
128 }
129 
BuildUnsetAdvertisingData(const DeviceAddress &,bool)130 CommandPacket LegacyLowEnergyAdvertiser::BuildUnsetAdvertisingData(
131     const DeviceAddress&, bool /*extended_pdu*/) {
132   return CommandPacket::New<pwemb::LESetAdvertisingDataCommandWriter>(
133       hci_spec::kLESetAdvertisingData);
134 }
135 
BuildUnsetScanResponse(const DeviceAddress &,bool)136 CommandPacket LegacyLowEnergyAdvertiser::BuildUnsetScanResponse(
137     const DeviceAddress&, bool /*extended_pdu*/) {
138   auto packet = CommandPacket::New<pwemb::LESetScanResponseDataCommandWriter>(
139       hci_spec::kLESetScanResponseData);
140   return packet;
141 }
142 
BuildRemoveAdvertisingSet(const DeviceAddress &,bool)143 CommandPacket LegacyLowEnergyAdvertiser::BuildRemoveAdvertisingSet(
144     const DeviceAddress&, bool /*extended_pdu*/) {
145   auto packet =
146       hci::CommandPacket::New<pwemb::LESetAdvertisingEnableCommandWriter>(
147           hci_spec::kLESetAdvertisingEnable);
148   auto packet_view = packet.view_t();
149   packet_view.advertising_enable().Write(pwemb::GenericEnableParam::DISABLE);
150   return packet;
151 }
152 
BuildReadAdvertisingTxPower()153 static CommandPacket BuildReadAdvertisingTxPower() {
154   return CommandPacket::New<pwemb::LEReadAdvertisingChannelTxPowerCommandView>(
155       hci_spec::kLEReadAdvertisingChannelTxPower);
156 }
157 
StartAdvertising(const DeviceAddress & address,const AdvertisingData & data,const AdvertisingData & scan_rsp,const AdvertisingOptions & options,ConnectionCallback connect_callback,ResultFunction<> result_callback)158 void LegacyLowEnergyAdvertiser::StartAdvertising(
159     const DeviceAddress& address,
160     const AdvertisingData& data,
161     const AdvertisingData& scan_rsp,
162     const AdvertisingOptions& options,
163     ConnectionCallback connect_callback,
164     ResultFunction<> result_callback) {
165   if (options.extended_pdu) {
166     bt_log(INFO,
167            "hci-le",
168            "legacy advertising cannot use extended advertising PDUs");
169     result_callback(ToResult(HostError::kNotSupported));
170     return;
171   }
172 
173   fit::result<HostError> result =
174       CanStartAdvertising(address, data, scan_rsp, options, connect_callback);
175   if (result.is_error()) {
176     result_callback(ToResult(result.error_value()));
177     return;
178   }
179 
180   if (IsAdvertising() && !IsAdvertising(address, options.extended_pdu)) {
181     bt_log(INFO,
182            "hci-le",
183            "already advertising (only one advertisement supported at a time)");
184     result_callback(ToResult(HostError::kNotSupported));
185     return;
186   }
187 
188   if (IsAdvertising()) {
189     bt_log(DEBUG, "hci-le", "updating existing advertisement");
190   }
191 
192   // Midst of a TX power level read - send a cancel over the previous status
193   // callback.
194   if (staged_params_.has_value()) {
195     auto result_cb = std::move(staged_params_.value().result_callback);
196     result_cb(ToResult(HostError::kCanceled));
197   }
198 
199   // If the TX Power level is requested, then stage the parameters for the read
200   // operation. If there already is an outstanding TX Power Level read request,
201   // return early. Advertising on the outstanding call will now use the updated
202   // |staged_params_|.
203   if (options.include_tx_power_level) {
204     AdvertisingData data_copy;
205     data.Copy(&data_copy);
206 
207     AdvertisingData scan_rsp_copy;
208     scan_rsp.Copy(&scan_rsp_copy);
209 
210     staged_params_ = StagedParams{address,
211                                   std::move(data_copy),
212                                   std::move(scan_rsp_copy),
213                                   options,
214                                   std::move(connect_callback),
215                                   std::move(result_callback)};
216 
217     if (starting_ && hci_cmd_runner().IsReady()) {
218       return;
219     }
220   }
221 
222   if (!hci_cmd_runner().IsReady()) {
223     bt_log(DEBUG,
224            "hci-le",
225            "canceling advertising start/stop sequence due to new advertising "
226            "request");
227     // Abort any remaining commands from the current stop sequence. If we got
228     // here then the controller MUST receive our request to disable advertising,
229     // so the commands that we send next will overwrite the current advertising
230     // settings and re-enable it.
231     hci_cmd_runner().Cancel();
232   }
233 
234   starting_ = true;
235   local_address_ = DeviceAddress();
236 
237   // If the TX Power Level is requested, read it from the controller, update the
238   // data buf, and proceed with starting advertising.
239   //
240   // If advertising was canceled during the TX power level read (either
241   // |starting_| was reset or the |result_callback| was moved), return early.
242   if (options.include_tx_power_level) {
243     auto power_cb = [this](auto, const hci::EventPacket& event) mutable {
244       PW_CHECK(staged_params_.has_value());
245       if (!starting_ || !staged_params_.value().result_callback) {
246         bt_log(
247             INFO, "hci-le", "Advertising canceled during TX Power Level read.");
248         return;
249       }
250 
251       if (HCI_IS_ERROR(event, WARN, "hci-le", "read TX power level failed")) {
252         staged_params_.value().result_callback(event.ToResult());
253         staged_params_ = {};
254         local_address_ = DeviceAddress();
255         starting_ = false;
256         return;
257       }
258 
259       auto staged_params = std::move(staged_params_.value());
260       staged_params_ = {};
261 
262       // Update the advertising and scan response data with the TX power level.
263       auto view = event.view<
264           pw::bluetooth::emboss::
265               LEReadAdvertisingChannelTxPowerCommandCompleteEventView>();
266       staged_params.data.SetTxPower(view.tx_power_level().Read());
267       if (staged_params.scan_rsp.CalculateBlockSize()) {
268         staged_params.scan_rsp.SetTxPower(view.tx_power_level().Read());
269       }
270 
271       StartAdvertisingInternal(
272           staged_params.address,
273           staged_params.data,
274           staged_params.scan_rsp,
275           staged_params.options,
276           std::move(staged_params.connect_callback),
277           [this,
278            address_copy = staged_params.address,
279            result_cb = std::move(staged_params.result_callback)](
280               const Result<>& start_result) {
281             starting_ = false;
282             local_address_ = address_copy;
283             result_cb(start_result);
284           });
285     };
286 
287     hci()->command_channel()->SendCommand(BuildReadAdvertisingTxPower(),
288                                           std::move(power_cb));
289     return;
290   }
291 
292   StartAdvertisingInternal(
293       address,
294       data,
295       scan_rsp,
296       options,
297       std::move(connect_callback),
298       [this, address_copy = address, result_cb = std::move(result_callback)](
299           const Result<>& start_result) {
300         starting_ = false;
301         local_address_ = address_copy;
302         result_cb(start_result);
303       });
304 }
305 
StopAdvertising()306 void LegacyLowEnergyAdvertiser::StopAdvertising() {
307   LowEnergyAdvertiser::StopAdvertising();
308   starting_ = false;
309   local_address_ = DeviceAddress();
310 }
311 
StopAdvertising(const DeviceAddress & address,bool extended_pdu)312 void LegacyLowEnergyAdvertiser::StopAdvertising(const DeviceAddress& address,
313                                                 bool extended_pdu) {
314   if (extended_pdu) {
315     bt_log(INFO,
316            "hci-le",
317            "legacy advertising cannot use extended advertising PDUs");
318     return;
319   }
320 
321   if (!hci_cmd_runner().IsReady()) {
322     hci_cmd_runner().Cancel();
323   }
324 
325   LowEnergyAdvertiser::StopAdvertisingInternal(address, extended_pdu);
326   starting_ = false;
327   local_address_ = DeviceAddress();
328 }
329 
OnIncomingConnection(hci_spec::ConnectionHandle handle,pwemb::ConnectionRole role,const DeviceAddress & peer_address,const hci_spec::LEConnectionParameters & conn_params)330 void LegacyLowEnergyAdvertiser::OnIncomingConnection(
331     hci_spec::ConnectionHandle handle,
332     pwemb::ConnectionRole role,
333     const DeviceAddress& peer_address,
334     const hci_spec::LEConnectionParameters& conn_params) {
335   static DeviceAddress identity_address =
336       DeviceAddress(DeviceAddress::Type::kLEPublic, {0});
337 
338   // We use the identity address as the local address if we aren't advertising.
339   // If we aren't advertising, this is obviously wrong. However, the link will
340   // be disconnected in that case before it can propagate to higher layers.
341   DeviceAddress local_address = identity_address;
342   if (IsAdvertising()) {
343     local_address = local_address_;
344   }
345 
346   CompleteIncomingConnection(handle,
347                              role,
348                              local_address,
349                              peer_address,
350                              conn_params,
351                              /*extended_pdu=*/false);
352 }
353 
354 }  // namespace bt::hci
355