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