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