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/gap/low_energy_interrogator.h"
16
17 #include <pw_assert/check.h>
18
19 #include "pw_bluetooth_sapphire/internal/host/gap/peer.h"
20 #include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
21 #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
22 #include "pw_bluetooth_sapphire/internal/host/transport/command_channel.h"
23
24 namespace bt::gap {
25
LowEnergyInterrogator(Peer::WeakPtr peer,hci_spec::ConnectionHandle handle,hci::CommandChannel::WeakPtr cmd_channel,bool sca_supported)26 LowEnergyInterrogator::LowEnergyInterrogator(
27 Peer::WeakPtr peer,
28 hci_spec::ConnectionHandle handle,
29 hci::CommandChannel::WeakPtr cmd_channel,
30 bool sca_supported)
31 : peer_(std::move(peer)),
32 peer_id_(peer_->identifier()),
33 handle_(handle),
34 cmd_runner_(cmd_channel->AsWeakPtr()),
35 controller_supports_sca_(sca_supported),
36 weak_self_(this) {}
37
Start(ResultCallback callback)38 void LowEnergyInterrogator::Start(ResultCallback callback) {
39 PW_CHECK(!callback_);
40 callback_ = std::move(callback);
41
42 if (!peer_.is_alive()) {
43 Complete(ToResult(HostError::kFailed));
44 return;
45 }
46
47 PW_CHECK(peer_->le().has_value());
48
49 // Always read remote version information as a test of whether the connection
50 // was *actually* successfully established. If the connection failed to be
51 // established, the command status of the Read Remote Version Information
52 // command will be "Connection Failed to be Established". See
53 // fxbug.dev/42138706 for details.
54 QueueReadRemoteVersionInformation();
55
56 if (!peer_->le()->feature_interrogation_complete()) {
57 QueueReadLERemoteFeatures();
58 }
59
60 cmd_runner_.RunCommands([this](hci::Result<> result) {
61 // Accommodate unsupported remote feature interrogation (see
62 // http://b/361651988). In this case we know the peer doesn't support SCA,
63 // so we can return immediately.
64 if (peer_->le()->feature_interrogation_complete() &&
65 result == ToResult(pw::bluetooth::emboss::StatusCode::
66 UNSUPPORTED_REMOTE_FEATURE)) {
67 Complete(fit::ok());
68 return;
69 }
70
71 if (result.is_error() || !peer_->le()->features().has_value() ||
72 !controller_supports_sca_) {
73 Complete(result);
74 return;
75 }
76
77 // Verify the peer supports SCA updates
78 if (!(peer_->le()->features().value() &
79 static_cast<uint64_t>(
80 hci_spec::LESupportedFeature::kSleepClockAccuracyUpdates))) {
81 bt_log(INFO, "gap-le", "peer %s does not support SCA", bt_str(peer_id_));
82 Complete(result);
83 return;
84 }
85
86 QueueRequestPeerSca();
87 });
88 }
89
Cancel()90 void LowEnergyInterrogator::Cancel() {
91 if (!cmd_runner_.IsReady()) {
92 cmd_runner_.Cancel();
93 }
94 }
95
Complete(hci::Result<> result)96 void LowEnergyInterrogator::Complete(hci::Result<> result) {
97 if (!callback_) {
98 return;
99 }
100
101 auto self = weak_self_.GetWeakPtr();
102
103 // callback may destroy this object
104 callback_(result);
105
106 // Complete() may have been called by a command callback, in which case the
107 // runner needs to be canceled.
108 if (self.is_alive() && !cmd_runner_.IsReady()) {
109 cmd_runner_.Cancel();
110 }
111 }
112
QueueRequestPeerSca()113 void LowEnergyInterrogator::QueueRequestPeerSca() {
114 auto packet = hci::CommandPacket::New<
115 pw::bluetooth::emboss::LERequestPeerSCACommandWriter>(
116 hci_spec::kLERequestPeerSCA);
117 packet.view_t().connection_handle().Write(handle_);
118
119 // It's safe to capture |this| instead of a weak ptr to self because
120 // |cmd_runner_| guarantees that |cmd_cb| won't be invoked if |cmd_runner_| is
121 // destroyed, and |this| outlives |cmd_runner_|.
122 auto cmd_cb = [this](const hci::EventPacket& event) {
123 if (HCI_IS_ERROR(event, WARN, "gap-le", "LE request peer SCA failed")) {
124 return;
125 }
126 auto view = event.view<
127 pw::bluetooth::emboss::LERequestPeerSCACompleteSubeventView>();
128 bt_log(DEBUG,
129 "gap-le",
130 "LE request peer SCA complete (peer: %s, value: %d)",
131 bt_str(peer_id_),
132 static_cast<uint8_t>(view.peer_clock_accuracy().Read()));
133 peer_->MutLe().set_sleep_clock_accuracy(view.peer_clock_accuracy().Read());
134 };
135
136 bt_log(TRACE, "gap-le", "requesting SCA for peer %s", bt_str(peer_id_));
137 cmd_runner_.QueueLeAsyncCommand(
138 std::move(packet),
139 hci_spec::kLERequestPeerSCACompleteSubeventCode,
140 std::move(cmd_cb),
141 /*wait=*/true);
142 cmd_runner_.RunCommands([this](hci::Result<> result) {
143 // This shouldn't happen since we verified that the peer supports SCA
144 // updates
145 PW_DCHECK(!result.is_error(),
146 "request for SCA from peer %s failed",
147 bt_str(peer_id_));
148 // Report success since the data is not critical and we don't want to
149 // interrupt pairing
150 Complete(fit::ok());
151 });
152 }
153
QueueReadLERemoteFeatures()154 void LowEnergyInterrogator::QueueReadLERemoteFeatures() {
155 auto packet = hci::CommandPacket::New<
156 pw::bluetooth::emboss::LEReadRemoteFeaturesCommandWriter>(
157 hci_spec::kLEReadRemoteFeatures);
158 packet.view_t().connection_handle().Write(handle_);
159
160 // It's safe to capture |this| instead of a weak ptr to self because
161 // |cmd_runner_| guarantees that |cmd_cb| won't be invoked if |cmd_runner_| is
162 // destroyed, and |this| outlives |cmd_runner_|.
163 auto cmd_cb = [this](const hci::EventPacket& event) {
164 peer_->MutLe().SetFeatureInterrogationComplete();
165 if (HCI_IS_ERROR(event, WARN, "gap-le", "LE read remote features failed")) {
166 return;
167 }
168 bt_log(DEBUG,
169 "gap-le",
170 "LE read remote features complete (peer: %s)",
171 bt_str(peer_id_));
172 auto view = event.view<
173 pw::bluetooth::emboss::LEReadRemoteFeaturesCompleteSubeventView>();
174 peer_->MutLe().SetFeatures(hci_spec::LESupportedFeatures{
175 view.le_features().BackingStorage().ReadUInt()});
176 };
177
178 bt_log(TRACE,
179 "gap-le",
180 "sending LE read remote features command (peer id: %s)",
181 bt_str(peer_id_));
182 cmd_runner_.QueueLeAsyncCommand(
183 std::move(packet),
184 hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode,
185 std::move(cmd_cb),
186 /*wait=*/false);
187 }
188
QueueReadRemoteVersionInformation()189 void LowEnergyInterrogator::QueueReadRemoteVersionInformation() {
190 auto packet = hci::CommandPacket::New<
191 pw::bluetooth::emboss::ReadRemoteVersionInfoCommandWriter>(
192 hci_spec::kReadRemoteVersionInfo);
193 packet.view_t().connection_handle().Write(handle_);
194
195 // It's safe to capture |this| instead of a weak ptr to self because
196 // |cmd_runner_| guarantees that |cmd_cb| won't be invoked if |cmd_runner_| is
197 // destroyed, and |this| outlives |cmd_runner_|.
198 auto cmd_cb = [this](const hci::EventPacket& event) {
199 if (HCI_IS_ERROR(
200 event, WARN, "gap-le", "read remote version info failed")) {
201 return;
202 }
203 PW_DCHECK(event.event_code() ==
204 hci_spec::kReadRemoteVersionInfoCompleteEventCode);
205 bt_log(TRACE,
206 "gap-le",
207 "read remote version info completed (peer: %s)",
208 bt_str(peer_id_));
209 auto view = event.view<
210 pw::bluetooth::emboss::ReadRemoteVersionInfoCompleteEventView>();
211 peer_->set_version(view.version().Read(),
212 view.company_identifier().Read(),
213 view.subversion().Read());
214 };
215
216 bt_log(TRACE,
217 "gap-le",
218 "asking for version info (peer id: %s)",
219 bt_str(peer_id_));
220 cmd_runner_.QueueCommand(std::move(packet),
221 std::move(cmd_cb),
222 /*wait=*/false,
223 hci_spec::kReadRemoteVersionInfoCompleteEventCode);
224 }
225
226 } // namespace bt::gap
227