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