• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 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/central.h"
16 
17 #include "pw_bluetooth_sapphire/internal/connection_options.h"
18 #include "pw_bluetooth_sapphire/internal/uuid.h"
19 
20 namespace pw::bluetooth_sapphire {
21 namespace {
22 
23 pw::sync::Mutex g_peripheral_lock;
24 
DiscoveryFilterFrom(const Central::ScanFilter & in)25 bt::hci::DiscoveryFilter DiscoveryFilterFrom(const Central::ScanFilter& in) {
26   bt::hci::DiscoveryFilter out;
27   if (in.service_uuid.has_value()) {
28     bt::UUID uuid = internal::UuidFrom(in.service_uuid.value());
29     out.set_service_uuids(std::vector<bt::UUID>{std::move(uuid)});
30   }
31   if (in.service_data_uuid.has_value()) {
32     bt::UUID uuid = internal::UuidFrom(in.service_data_uuid.value());
33     out.set_service_data_uuids(std::vector<bt::UUID>{std::move(uuid)});
34   }
35   if (in.manufacturer_id.has_value()) {
36     out.set_manufacturer_code(in.manufacturer_id.value());
37   }
38   if (in.connectable.has_value()) {
39     out.set_connectable(in.connectable.value());
40   }
41   if (in.name.has_value()) {
42     out.set_name_substring(std::string(in.name.value()));
43   }
44   if (in.max_path_loss.has_value()) {
45     out.set_pathloss(in.max_path_loss.value());
46   }
47   if (in.solicitation_uuid.has_value()) {
48     bt::UUID uuid = internal::UuidFrom(in.solicitation_uuid.value());
49     out.set_solicitation_uuids(std::vector<bt::UUID>{std::move(uuid)});
50   }
51   return out;
52 }
53 
ScanResultFrom(const bt::gap::Peer & peer,pw::multibuf::MultiBufAllocator & allocator)54 std::optional<Central::ScanResult> ScanResultFrom(
55     const bt::gap::Peer& peer, pw::multibuf::MultiBufAllocator& allocator) {
56   Central::ScanResult out;
57   out.peer_id = peer.identifier().value();
58   // TODO: https://pwbug.dev/377301546 - Report the "connectable" value of this
59   // advertisement, not the Peer's dual-mode connectability.
60   out.connectable = peer.connectable();
61   out.rssi = peer.rssi();
62 
63   if (!peer.le()->parsed_advertising_data_timestamp().has_value()) {
64     bt_log(DEBUG, "api", "failed to get advertising data time");
65     return std::nullopt;
66   }
67   out.last_updated = peer.le()->parsed_advertising_data_timestamp().value();
68 
69   bt::BufferView data_view = peer.le()->advertising_data();
70   std::optional<pw::multibuf::MultiBuf> data =
71       allocator.Allocate(data_view.size());
72   if (!data) {
73     bt_log(DEBUG, "api", "failed to allocate buffer for advertising data");
74     return std::nullopt;
75   }
76   StatusWithSize copy_status = data->CopyFrom(data_view.subspan());
77   if (!copy_status.ok()) {
78     bt_log(DEBUG,
79            "api",
80            "failed to copy scan result data: %s",
81            copy_status.status().str());
82     return std::nullopt;
83   }
84   out.data = std::move(data.value());
85 
86   if (peer.name().has_value()) {
87     out.name.emplace();
88     Status append_status =
89         pw::string::Append(out.name.value(), peer.name().value());
90     // RESOURCE_EXHAUSTED means that the name was truncated, which is OK.
91     if (!append_status.ok() && !append_status.IsResourceExhausted()) {
92       bt_log(DEBUG,
93              "api",
94              "failed to set scan result name: %s",
95              append_status.str());
96       return std::nullopt;
97     }
98   }
99 
100   return out;
101 }
102 
103 }  // namespace
104 
Central(bt::gap::Adapter::WeakPtr adapter,pw::async::Dispatcher & dispatcher,pw::multibuf::MultiBufAllocator & allocator)105 Central::Central(bt::gap::Adapter::WeakPtr adapter,
106                  pw::async::Dispatcher& dispatcher,
107                  pw::multibuf::MultiBufAllocator& allocator)
108     : adapter_(std::move(adapter)),
109       dispatcher_(dispatcher),
110       heap_dispatcher_(dispatcher),
111       allocator_(allocator),
112       weak_factory_(this),
113       self_(weak_factory_.GetWeakPtr()) {}
114 
~Central()115 Central::~Central() {
116   std::lock_guard guard(lock());
117   scans_.clear();
118 }
119 
Connect(pw::bluetooth::PeerId peer_id,bluetooth::low_energy::Connection2::ConnectionOptions options)120 async2::OnceReceiver<Central::ConnectResult> Central::Connect(
121     pw::bluetooth::PeerId peer_id,
122     bluetooth::low_energy::Connection2::ConnectionOptions options) {
123   bt::PeerId internal_peer_id(peer_id);
124   bt::gap::LowEnergyConnectionOptions connection_options =
125       internal::ConnectionOptionsFrom(options);
126 
127   auto [result_sender, result_receiver] =
128       async2::MakeOnceSenderAndReceiver<ConnectResult>();
129 
130   bt::gap::Adapter::LowEnergy::ConnectionResultCallback result_cb =
131       [self = self_,
132        peer = internal_peer_id,
133        sender = std::move(result_sender)](
134           bt::gap::Adapter::LowEnergy::ConnectionResult result) mutable {
135         if (!self.is_alive()) {
136           return;
137         }
138         self->OnConnectionResult(peer, std::move(result), std::move(sender));
139       };
140 
141   async::TaskFunction task_fn = [self = self_,
142                                  internal_peer_id,
143                                  connection_options,
144                                  cb = std::move(result_cb)](
145                                     async::Context&, Status status) mutable {
146     if (!status.ok() || !self.is_alive()) {
147       return;
148     }
149     self->adapter_->le()->Connect(
150         internal_peer_id, std::move(cb), connection_options);
151   };
152   Status post_status = heap_dispatcher_.Post(std::move(task_fn));
153   PW_CHECK_OK(post_status);
154 
155   return std::move(result_receiver);
156 }
157 
Scan(const ScanOptions & options)158 async2::OnceReceiver<Central::ScanStartResult> Central::Scan(
159     const ScanOptions& options) {
160   // TODO: https://pwbug.dev/377301546 - Support the different types of active
161   // scans.
162   bool active = (options.scan_type != ScanType::kPassive);
163 
164   if (options.filters.empty()) {
165     return async2::OnceReceiver<ScanStartResult>(
166         pw::unexpected(StartScanError::kInvalidParameters));
167   }
168 
169   auto [result_sender, result_receiver] =
170       async2::MakeOnceSenderAndReceiver<Central::ScanStartResult>();
171 
172   auto callback =
173       [self = self_, sender = std::move(result_sender)](
174           std::unique_ptr<bt::gap::LowEnergyDiscoverySession> session) mutable {
175         // callback will always be run on the Bluetooth thread
176 
177         if (!self.is_alive()) {
178           sender.emplace(pw::unexpected(StartScanError::kInternal));
179           return;
180         }
181 
182         if (!session) {
183           bt_log(WARN, "api", "failed to start LE discovery session");
184           sender.emplace(pw::unexpected(StartScanError::kInternal));
185           return;
186         }
187 
188         ScanHandleImpl* scan_handle_raw_ptr =
189             new ScanHandleImpl(session->scan_id(), &self.get());
190         ScanHandle::Ptr scan_handle_ptr(scan_handle_raw_ptr);
191         {
192           std::lock_guard guard(lock());
193           auto [iter, emplaced] = self->scans_.try_emplace(session->scan_id(),
194                                                            std::move(session),
195                                                            scan_handle_raw_ptr,
196                                                            session->scan_id(),
197                                                            &self.get());
198           PW_CHECK(emplaced);
199         }
200 
201         sender.emplace(std::move(scan_handle_ptr));
202       };
203 
204   // Convert options to filters now because options contains non-owning views
205   // that won't be valid in callbacks.
206   std::vector<bt::hci::DiscoveryFilter> discovery_filters;
207   discovery_filters.reserve(options.filters.size());
208   for (const ScanFilter& filter : options.filters) {
209     discovery_filters.emplace_back(DiscoveryFilterFrom(filter));
210   }
211 
212   async::TaskFunction task_fn = [self = self_,
213                                  filters = std::move(discovery_filters),
214                                  active,
215                                  cb = std::move(callback)](
216                                     async::Context&, Status status) mutable {
217     if (status.ok()) {
218       // TODO: https://pwbug.dev/377301546 - Support configuring interval,
219       // window, and PHY.
220       self->adapter_->le()->StartDiscovery(active, filters, std::move(cb));
221     }
222   };
223   Status post_status = heap_dispatcher_.Post(std::move(task_fn));
224   PW_CHECK_OK(post_status);
225 
226   return std::move(result_receiver);
227 }
228 
lock()229 pw::sync::Mutex& Central::lock() { return g_peripheral_lock; }
230 
~ScanHandleImpl()231 Central::ScanHandleImpl::~ScanHandleImpl() {
232   std::lock_guard guard(lock());
233   if (central_) {
234     central_->StopScanLocked(scan_id_);
235   }
236 }
237 
QueueScanResultLocked(ScanResult && result)238 void Central::ScanHandleImpl::QueueScanResultLocked(ScanResult&& result) {
239   if (results_.size() == kMaxScanResultsQueueSize) {
240     results_.pop();
241   }
242   results_.push(std::move(result));
243   std::move(waker_).Wake();
244 }
245 
246 async2::Poll<pw::Result<Central::ScanResult>>
PendResult(async2::Context & cx)247 Central::ScanHandleImpl::PendResult(async2::Context& cx) {
248   std::lock_guard guard(lock());
249   if (!results_.empty()) {
250     ScanResult result = std::move(results_.front());
251     results_.pop();
252     return async2::Ready(std::move(result));
253   }
254 
255   if (!central_) {
256     return async2::Ready(pw::Status::Cancelled());
257   }
258 
259   PW_ASYNC_STORE_WAKER(cx, waker_, "scan result");
260   return async2::Pending();
261 }
262 
ScanState(std::unique_ptr<bt::gap::LowEnergyDiscoverySession> session,ScanHandleImpl * scan_handle,uint16_t scan_id,Central * central)263 Central::ScanState::ScanState(
264     std::unique_ptr<bt::gap::LowEnergyDiscoverySession> session,
265     ScanHandleImpl* scan_handle,
266     uint16_t scan_id,
267     Central* central)
268     : scan_id_(scan_id),
269       scan_handle_(scan_handle),
270       central_(central),
271       session_(std::move(session)) {
272   session_->SetResultCallback(
273       [this](const bt::gap::Peer& peer) { OnScanResult(peer); });
274   session_->set_error_callback([this]() { OnError(); });
275 }
276 
~ScanState()277 Central::ScanState::~ScanState() {
278   // lock() is expected to be held
279   if (scan_handle_) {
280     scan_handle_->OnScanErrorLocked();
281     scan_handle_ = nullptr;
282   }
283 }
284 
OnScanResult(const bt::gap::Peer & peer)285 void Central::ScanState::OnScanResult(const bt::gap::Peer& peer) {
286   // TODO: https://pwbug.dev/377301546 - Getting only a Peer as a scan result is
287   // awkward. Update LowEnergyDiscoverySession to give us the actual
288   // LowEnergyScanResult.
289   std::lock_guard guard(lock());
290   if (!scan_handle_) {
291     return;
292   }
293 
294   std::optional<Central::ScanResult> scan_result =
295       ScanResultFrom(peer, central_->allocator_);
296   if (!scan_result.has_value()) {
297     return;
298   }
299 
300   scan_handle_->QueueScanResultLocked(std::move(scan_result.value()));
301   scan_handle_->WakeLocked();
302 }
303 
OnError()304 void Central::ScanState::OnError() {
305   std::lock_guard guard(lock());
306   if (scan_handle_) {
307     scan_handle_->OnScanErrorLocked();
308     scan_handle_ = nullptr;
309   }
310   central_->scans_.erase(scan_id_);
311   // This object has been destroyed.
312 }
313 
StopScanLocked(uint16_t scan_id)314 void Central::StopScanLocked(uint16_t scan_id) {
315   auto iter = scans_.find(scan_id);
316   if (iter == scans_.end()) {
317     return;
318   }
319   iter->second.OnScanHandleDestroyedLocked();
320 
321   pw::Status post_status = heap_dispatcher_.Post(
322       [self = self_, scan_id](pw::async::Context, pw::Status status) {
323         if (!status.ok() || !self.is_alive()) {
324           return;
325         }
326         std::lock_guard guard(lock());
327         self->scans_.erase(scan_id);
328       });
329   PW_CHECK(post_status.ok());
330 }
331 
OnConnectionResult(bt::PeerId peer_id,bt::gap::Adapter::LowEnergy::ConnectionResult result,async2::OnceSender<ConnectResult> result_sender)332 void Central::OnConnectionResult(
333     bt::PeerId peer_id,
334     bt::gap::Adapter::LowEnergy::ConnectionResult result,
335     async2::OnceSender<ConnectResult> result_sender) {
336   if (result.is_error()) {
337     if (result.error_value() == bt::HostError::kNotFound) {
338       result_sender.emplace(pw::unexpected(ConnectError::kUnknownPeer));
339     } else {
340       result_sender.emplace(
341           pw::unexpected(ConnectError::kCouldNotBeEstablished));
342     }
343     return;
344   }
345 
346   pw::bluetooth::low_energy::Connection2::Ptr connection_ptr(
347       new internal::Connection(
348           peer_id, std::move(result.value()), dispatcher_));
349   result_sender.emplace(std::move(connection_ptr));
350 }
351 
352 }  // namespace pw::bluetooth_sapphire
353