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