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