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/fuchsia/host/fidl/low_energy_connection_server.h"
16
17 #include <fuchsia/bluetooth/cpp/fidl.h>
18 #include <pw_assert/check.h>
19 #include <pw_status/status.h>
20 #include <pw_status/try.h>
21
22 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h"
23 #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
24 #include "pw_bluetooth_sapphire/internal/host/sm/types.h"
25
26 namespace fbt = fuchsia::bluetooth;
27 namespace fbg = fuchsia::bluetooth::gatt2;
28
29 namespace bthost {
30 namespace {
31
ConvertModeFromFidl(fbt::ChannelMode mode)32 pw::Result<bt::l2cap::CreditBasedFlowControlMode> ConvertModeFromFidl(
33 fbt::ChannelMode mode) {
34 switch (mode) {
35 case fbt::ChannelMode::LE_CREDIT_BASED_FLOW_CONTROL:
36 return bt::l2cap::CreditBasedFlowControlMode::kLeCreditBasedFlowControl;
37 case fbt::ChannelMode::ENHANCED_CREDIT_BASED_FLOW_CONTROL:
38 return bt::l2cap::CreditBasedFlowControlMode::
39 kEnhancedCreditBasedFlowControl;
40 default:
41 return pw::Status::Unimplemented();
42 }
43 }
44
ConvertParamsFromFidl(const fbt::ChannelParameters & fidl)45 pw::Result<bt::l2cap::ChannelParameters> ConvertParamsFromFidl(
46 const fbt::ChannelParameters& fidl) {
47 bt::l2cap::ChannelParameters params;
48
49 if (fidl.has_flush_timeout()) {
50 // Request included parameters that must not be set for an LE L2CAP channel.
51 return pw::Status::InvalidArgument();
52 }
53
54 if (fidl.has_channel_mode()) {
55 PW_TRY_ASSIGN(params.mode, ConvertModeFromFidl(fidl.channel_mode()));
56 } else {
57 params.mode =
58 bt::l2cap::CreditBasedFlowControlMode::kLeCreditBasedFlowControl;
59 }
60
61 if (fidl.has_max_rx_packet_size()) {
62 params.max_rx_sdu_size = fidl.max_rx_packet_size();
63 }
64
65 return params;
66 }
67
ConvertSecurityRequirementsFromFidl(const fbt::ChannelParameters & fidl)68 bt::sm::SecurityLevel ConvertSecurityRequirementsFromFidl(
69 const fbt::ChannelParameters& fidl) {
70 bt::sm::SecurityLevel security_level = bt::sm::SecurityLevel::kEncrypted;
71
72 if (!fidl.has_security_requirements()) {
73 return security_level;
74 }
75
76 auto& security_reqs = fidl.security_requirements();
77 if (security_reqs.has_authentication_required() &&
78 security_reqs.authentication_required()) {
79 security_level = bt::sm::SecurityLevel::kAuthenticated;
80 }
81 if (security_reqs.has_secure_connections_required() &&
82 security_reqs.secure_connections_required()) {
83 security_level = bt::sm::SecurityLevel::kSecureAuthenticated;
84 }
85 return security_level;
86 }
87
88 } // namespace
89
LowEnergyConnectionServer(bt::gap::Adapter::WeakPtr adapter,bt::gatt::GATT::WeakPtr gatt,std::unique_ptr<bt::gap::LowEnergyConnectionHandle> connection,zx::channel handle,fit::callback<void ()> closed_cb)90 LowEnergyConnectionServer::LowEnergyConnectionServer(
91 bt::gap::Adapter::WeakPtr adapter,
92 bt::gatt::GATT::WeakPtr gatt,
93 std::unique_ptr<bt::gap::LowEnergyConnectionHandle> connection,
94 zx::channel handle,
95 fit::callback<void()> closed_cb)
96 : ServerBase(this, std::move(handle)),
97 conn_(std::move(connection)),
98 closed_handler_(std::move(closed_cb)),
99 peer_id_(conn_->peer_identifier()),
100 adapter_(std::move(adapter)),
101 gatt_(std::move(gatt)),
102 weak_self_(this) {
103 PW_DCHECK(conn_);
104
105 set_error_handler([this](zx_status_t) { OnClosed(); });
106 conn_->set_closed_callback(
107 fit::bind_member<&LowEnergyConnectionServer::OnClosed>(this));
108 }
109
OnClosed()110 void LowEnergyConnectionServer::OnClosed() {
111 if (closed_handler_) {
112 binding()->Close(ZX_ERR_CONNECTION_RESET);
113 closed_handler_();
114 }
115 }
116
RequestGattClient(fidl::InterfaceRequest<fbg::Client> client)117 void LowEnergyConnectionServer::RequestGattClient(
118 fidl::InterfaceRequest<fbg::Client> client) {
119 if (gatt_client_server_.has_value()) {
120 bt_log(INFO,
121 "fidl",
122 "%s: gatt client server already bound (peer: %s)",
123 __FUNCTION__,
124 bt_str(peer_id_));
125 client.Close(ZX_ERR_ALREADY_BOUND);
126 return;
127 }
128
129 fit::callback<void()> server_error_cb = [this] {
130 bt_log(
131 TRACE, "fidl", "gatt client server error (peer: %s)", bt_str(peer_id_));
132 gatt_client_server_.reset();
133 };
134 gatt_client_server_.emplace(
135 peer_id_, gatt_, std::move(client), std::move(server_error_cb));
136 }
137
AcceptCis(fuchsia::bluetooth::le::ConnectionAcceptCisRequest parameters)138 void LowEnergyConnectionServer::AcceptCis(
139 fuchsia::bluetooth::le::ConnectionAcceptCisRequest parameters) {
140 if (!parameters.has_connection_stream()) {
141 bt_log(WARN, "fidl", "AcceptCis invoked without a connection stream");
142 return;
143 }
144 ::fidl::InterfaceRequest<::fuchsia::bluetooth::le::IsochronousStream>*
145 connection_stream = parameters.mutable_connection_stream();
146 uint8_t cig_id = parameters.cig_id();
147 uint8_t cis_id = parameters.cis_id();
148 bt::iso::CigCisIdentifier id(cig_id, cis_id);
149
150 // Check for existing stream with same CIG/CIS combination
151 if (iso_streams_.count(id) != 0) {
152 bt_log(WARN,
153 "fidl",
154 "AcceptCis invoked with duplicate ID (CIG: %u, CIS: %u)",
155 cig_id,
156 cis_id);
157 connection_stream->Close(ZX_ERR_INVALID_ARGS);
158 return;
159 }
160 auto stream_server = std::make_unique<IsoStreamServer>(
161 std::move(*connection_stream), [id, this]() { iso_streams_.erase(id); });
162 auto weak_stream_server = stream_server->GetWeakPtr();
163 iso_streams_[id] = std::move(stream_server);
164
165 bt::iso::AcceptCisStatus result = conn_->AcceptCis(
166 id,
167 [weak_stream_server](
168 pw::bluetooth::emboss::StatusCode status,
169 std::optional<bt::iso::IsoStream::WeakPtr> weak_stream_ptr,
170 const std::optional<bt::iso::CisEstablishedParameters>&
171 connection_params) {
172 if (weak_stream_server.is_alive()) {
173 if (status == pw::bluetooth::emboss::StatusCode::SUCCESS) {
174 PW_CHECK(weak_stream_ptr.has_value());
175 PW_CHECK(connection_params.has_value());
176 weak_stream_server->OnStreamEstablished(*weak_stream_ptr,
177 *connection_params);
178 } else {
179 weak_stream_server->OnStreamEstablishmentFailed(status);
180 }
181 }
182 });
183
184 switch (result) {
185 case bt::iso::AcceptCisStatus::kSuccess:
186 bt_log(INFO,
187 "fidl",
188 "waiting for incoming CIS connection (CIG: %u, CIS: %u)",
189 cig_id,
190 cis_id);
191 return;
192 case bt::iso::AcceptCisStatus::kNotPeripheral:
193 bt_log(WARN,
194 "fidl",
195 "attempt to wait for incoming CIS on Central not allowed");
196 iso_streams_[id]->Close(ZX_ERR_NOT_SUPPORTED);
197 return;
198 case bt::iso::AcceptCisStatus::kAlreadyExists:
199 bt_log(WARN,
200 "fidl",
201 "redundant request to wait for incoming CIS (CIG: %u, CIS: %u)",
202 cig_id,
203 cis_id);
204 iso_streams_[id]->Close(ZX_ERR_INVALID_ARGS);
205 return;
206 default:
207 PW_CRASH("Invalid AcceptCisStatus value %d", static_cast<int>(result));
208 }
209 }
210
GetCodecLocalDelayRange(::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest parameters,GetCodecLocalDelayRangeCallback callback)211 void LowEnergyConnectionServer::GetCodecLocalDelayRange(
212 ::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest
213 parameters,
214 GetCodecLocalDelayRangeCallback callback) {
215 bt_log(INFO, "fidl", "request received to read controller supported delay");
216
217 if (!parameters.has_logical_transport_type()) {
218 bt_log(WARN,
219 "fidl",
220 "request to read controller delay missing logical_transport_type");
221 callback(fpromise::error(ZX_ERR_INVALID_ARGS));
222 return;
223 }
224
225 if (!parameters.has_data_direction()) {
226 bt_log(WARN,
227 "fidl",
228 "request to read controller delay missing data_direction");
229 callback(fpromise::error(ZX_ERR_INVALID_ARGS));
230 return;
231 }
232
233 if (!parameters.has_codec_attributes()) {
234 bt_log(WARN,
235 "fidl",
236 "request to read controller delay missing codec_attributes");
237 callback(fpromise::error(ZX_ERR_INVALID_ARGS));
238 return;
239 }
240
241 if (!parameters.codec_attributes().has_codec_id()) {
242 bt_log(WARN, "fidl", "request to read controller delay missing codec_id");
243 callback(fpromise::error(ZX_ERR_INVALID_ARGS));
244 return;
245 }
246
247 // Process required parameters
248 pw::bluetooth::emboss::LogicalTransportType transport_type =
249 fidl_helpers::LogicalTransportTypeFromFidl(
250 parameters.logical_transport_type());
251 pw::bluetooth::emboss::DataPathDirection direction =
252 fidl_helpers::DataPathDirectionFromFidl(parameters.data_direction());
253 bt::StaticPacket<pw::bluetooth::emboss::CodecIdWriter> codec_id =
254 fidl_helpers::CodecIdFromFidl(parameters.codec_attributes().codec_id());
255
256 // Codec configuration is optional
257 std::optional<std::vector<uint8_t>> codec_configuration;
258 if (parameters.codec_attributes().has_codec_configuration()) {
259 codec_configuration = parameters.codec_attributes().codec_configuration();
260 } else {
261 codec_configuration = std::nullopt;
262 }
263
264 adapter_->GetSupportedDelayRange(
265 codec_id,
266 transport_type,
267 direction,
268 codec_configuration,
269 [callback = std::move(callback)](
270 pw::Status status, uint32_t min_delay_us, uint32_t max_delay_us) {
271 if (!status.ok()) {
272 bt_log(WARN, "fidl", "failed to get controller supported delay");
273 callback(fpromise::error(ZX_ERR_INTERNAL));
274 return;
275 }
276 bt_log(INFO,
277 "fidl",
278 "controller supported delay [%d, %d] microseconds",
279 min_delay_us,
280 max_delay_us);
281 fuchsia::bluetooth::le::CodecDelay_GetCodecLocalDelayRange_Response
282 response;
283 zx::duration min_delay = zx::usec(min_delay_us);
284 zx::duration max_delay = zx::usec(max_delay_us);
285 response.set_min_controller_delay(min_delay.get());
286 response.set_max_controller_delay(max_delay.get());
287 callback(
288 fuchsia::bluetooth::le::CodecDelay_GetCodecLocalDelayRange_Result::
289 WithResponse(std::move(response)));
290 });
291 }
292
ConnectL2cap(fuchsia::bluetooth::le::ConnectionConnectL2capRequest request)293 void LowEnergyConnectionServer::ConnectL2cap(
294 fuchsia::bluetooth::le::ConnectionConnectL2capRequest request) {
295 // Make available in callback below.
296 constexpr auto kFuncName = __FUNCTION__;
297
298 if (!request.has_channel()) {
299 bt_log(WARN,
300 "fidl",
301 "%s: No channel request, cannot fulfill call.",
302 kFuncName);
303 return;
304 }
305
306 if (!request.has_psm()) {
307 bt_log(ERROR, "fidl", "%s: missing psm.", kFuncName);
308 request.mutable_channel()->Close(ZX_ERR_INVALID_ARGS);
309 return;
310 }
311
312 if (!request.has_parameters()) {
313 bt_log(DEBUG,
314 "fidl",
315 "%s: No parameters provided, using default parameters.",
316 kFuncName);
317 }
318
319 auto parameters = ConvertParamsFromFidl(*request.mutable_parameters());
320 if (!parameters.ok()) {
321 bt_log(ERROR, "fidl", "%s: Parameters invalid.", kFuncName);
322 request.mutable_channel()->Close(ZX_ERR_INVALID_ARGS);
323 return;
324 }
325
326 bt::sm::SecurityLevel security_level =
327 ConvertSecurityRequirementsFromFidl(*request.mutable_parameters());
328 auto cb = [self = weak_self_.GetWeakPtr(),
329 request = std::move(*request.mutable_channel())](
330 bt::l2cap::Channel::WeakPtr channel) mutable {
331 if (!self.is_alive()) {
332 bt_log(
333 WARN,
334 "fidl",
335 "%s (cb): Connection server was destroyed before callback completed.",
336 kFuncName);
337 request.Close(ZX_ERR_INTERNAL);
338 return;
339 }
340 self->ServeChannel(std::move(channel), std::move(request));
341 };
342
343 PW_CHECK(adapter_.is_alive());
344 adapter_->le()->OpenL2capChannel(
345 peer_id_, request.psm(), *parameters, security_level, std::move(cb));
346 }
347
ServeChannel(bt::l2cap::Channel::WeakPtr channel,fidl::InterfaceRequest<fuchsia::bluetooth::Channel> request)348 void LowEnergyConnectionServer::ServeChannel(
349 bt::l2cap::Channel::WeakPtr channel,
350 fidl::InterfaceRequest<fuchsia::bluetooth::Channel> request) {
351 if (!channel.is_alive()) {
352 bt_log(WARN,
353 "fidl",
354 "%s: Channel was destroyed before it could be served.",
355 __FUNCTION__);
356 request.Close(ZX_ERR_INTERNAL);
357 }
358
359 bt::l2cap::Channel::UniqueId unique_id = channel->unique_id();
360
361 auto on_close = [this, unique_id]() { channel_servers_.erase(unique_id); };
362
363 auto server = ChannelServer::Create(
364 std::move(request), std::move(channel), std::move(on_close));
365
366 if (!server) {
367 bt_log(ERROR,
368 "fidl",
369 "%s: Channel server could not be created.",
370 __FUNCTION__);
371 request.Close(ZX_ERR_INTERNAL);
372 return;
373 }
374
375 channel_servers_[unique_id] = std::move(server);
376 }
377
378 } // namespace bthost
379