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 "pw_bluetooth_sapphire/fuchsia/host/fidl/adapter_test_fixture.h"
18 #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
19 #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
20 #include "pw_bluetooth_sapphire/internal/host/l2cap/types.h"
21
22 namespace bthost {
23 namespace {
24
25 namespace fbt = fuchsia::bluetooth;
26 namespace fble = fuchsia::bluetooth::le;
27 namespace fbg = fuchsia::bluetooth::gatt2;
28
29 const bt::DeviceAddress kTestAddr(bt::DeviceAddress::Type::kLEPublic,
30 {0x01, 0, 0, 0, 0, 0});
31
32 // Used for test cases that require all of the test infrastructure, but don't
33 // want to auto- configure the client and server at startup.
34 class LowEnergyConnectionServerTest
35 : public bthost::testing::AdapterTestFixture {
36 public:
37 LowEnergyConnectionServerTest() = default;
38 ~LowEnergyConnectionServerTest() override = default;
39
client()40 fble::Connection* client() { return client_.get(); }
41
UnbindClient()42 void UnbindClient() { client_.Unbind(); }
43
peer_id() const44 bt::PeerId peer_id() const { return peer_id_; }
45
server_closed_cb_called() const46 bool server_closed_cb_called() const { return server_closed_cb_called_; }
47
connection_handle()48 bt::hci_spec::ConnectionHandle connection_handle() {
49 return connection_handle_;
50 }
51
52 protected:
EstablishConnectionAndStartServer()53 void EstablishConnectionAndStartServer() {
54 // Since LowEnergyConnectionHandle must be created by
55 // LowEnergyConnectionManager, we discover and connect to a fake peer to get
56 // a LowEnergyConnectionHandle.
57 std::unique_ptr<bt::testing::FakePeer> fake_peer =
58 std::make_unique<bt::testing::FakePeer>(
59 kTestAddr, pw_dispatcher(), /*connectable=*/true);
60 test_device()->AddPeer(std::move(fake_peer));
61
62 std::optional<bt::PeerId> peer_id;
63 bt::gap::LowEnergyDiscoverySessionPtr session;
64 adapter()->le()->StartDiscovery(
65 /*active=*/true, [&](bt::gap::LowEnergyDiscoverySessionPtr cb_session) {
66 session = std::move(cb_session);
67 session->SetResultCallback(
68 [&](const bt::gap::Peer& peer) { peer_id = peer.identifier(); });
69 });
70 RunLoopUntilIdle();
71 PW_CHECK(peer_id);
72 peer_id_ = *peer_id;
73
74 std::optional<bt::gap::LowEnergyConnectionManager::ConnectionResult>
75 conn_result;
76 adapter()->le()->Connect(
77 peer_id_,
78 [&](bt::gap::LowEnergyConnectionManager::ConnectionResult result) {
79 conn_result = std::move(result);
80 },
81 bt::gap::LowEnergyConnectionOptions());
82 RunLoopUntilIdle();
83 PW_CHECK(conn_result);
84 PW_CHECK(conn_result->is_ok());
85 std::unique_ptr<bt::gap::LowEnergyConnectionHandle> connection =
86 std::move(*conn_result).value();
87
88 connection_handle_ = connection->handle();
89
90 // Start our FIDL connection server.
91 fidl::InterfaceHandle<fuchsia::bluetooth::le::Connection> handle;
92 server_ = std::make_unique<LowEnergyConnectionServer>(
93 adapter()->AsWeakPtr(),
94 gatt()->GetWeakPtr(),
95 std::move(connection),
96 handle.NewRequest().TakeChannel(),
97 /*closed_cb=*/[this] {
98 server_closed_cb_called_ = true;
99 server_.reset();
100 });
101 client_ = handle.Bind();
102 }
103
104 private:
105 std::unique_ptr<LowEnergyConnectionServer> server_;
106 fble::ConnectionPtr client_;
107 bool server_closed_cb_called_ = false;
108 bt::PeerId peer_id_;
109 bt::hci_spec::ConnectionHandle connection_handle_;
110 };
111
112 // Tests that want to automatically allocate and start the client and server
113 // before entering the test body.
114 class LowEnergyConnectionServerAutoStartTest
115 : public LowEnergyConnectionServerTest {
116 public:
117 LowEnergyConnectionServerAutoStartTest() = default;
118 ~LowEnergyConnectionServerAutoStartTest() override = default;
119
SetUp()120 void SetUp() override {
121 LowEnergyConnectionServerTest::SetUp();
122 EstablishConnectionAndStartServer();
123 }
124
125 protected:
126 void RunGetCodecDelayRangeTest(
127 fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest&& params,
128 std::optional<zx_status_t> err);
129 };
130
TEST_F(LowEnergyConnectionServerAutoStartTest,RequestGattClientTwice)131 TEST_F(LowEnergyConnectionServerAutoStartTest, RequestGattClientTwice) {
132 fidl::InterfaceHandle<fuchsia::bluetooth::gatt2::Client> handle_0;
133 client()->RequestGattClient(handle_0.NewRequest());
134 fbg::ClientPtr client_0 = handle_0.Bind();
135 std::optional<zx_status_t> client_epitaph_0;
136 client_0.set_error_handler(
137 [&](zx_status_t epitaph) { client_epitaph_0 = epitaph; });
138 RunLoopUntilIdle();
139 EXPECT_FALSE(client_epitaph_0);
140
141 fidl::InterfaceHandle<fuchsia::bluetooth::gatt2::Client> handle_1;
142 client()->RequestGattClient(handle_1.NewRequest());
143 fbg::ClientPtr client_1 = handle_1.Bind();
144 std::optional<zx_status_t> client_epitaph_1;
145 client_1.set_error_handler(
146 [&](zx_status_t epitaph) { client_epitaph_1 = epitaph; });
147 RunLoopUntilIdle();
148 EXPECT_FALSE(client_epitaph_0);
149 ASSERT_TRUE(client_epitaph_1);
150 EXPECT_EQ(client_epitaph_1.value(), ZX_ERR_ALREADY_BOUND);
151 }
152
TEST_F(LowEnergyConnectionServerAutoStartTest,GattClientServerError)153 TEST_F(LowEnergyConnectionServerAutoStartTest, GattClientServerError) {
154 fidl::InterfaceHandle<fuchsia::bluetooth::gatt2::Client> handle_0;
155 client()->RequestGattClient(handle_0.NewRequest());
156 fbg::ClientPtr client_0 = handle_0.Bind();
157 std::optional<zx_status_t> client_epitaph_0;
158 client_0.set_error_handler(
159 [&](zx_status_t epitaph) { client_epitaph_0 = epitaph; });
160 RunLoopUntilIdle();
161 EXPECT_FALSE(client_epitaph_0);
162
163 // Calling WatchServices twice should cause a server error.
164 client_0->WatchServices({}, [](auto, auto) {});
165 client_0->WatchServices({}, [](auto, auto) {});
166 RunLoopUntilIdle();
167 EXPECT_TRUE(client_epitaph_0);
168
169 // Requesting a new GATT client should succeed.
170 fidl::InterfaceHandle<fuchsia::bluetooth::gatt2::Client> handle_1;
171 client()->RequestGattClient(handle_1.NewRequest());
172 fbg::ClientPtr client_1 = handle_1.Bind();
173 std::optional<zx_status_t> client_epitaph_1;
174 client_1.set_error_handler(
175 [&](zx_status_t epitaph) { client_epitaph_1 = epitaph; });
176 RunLoopUntilIdle();
177 EXPECT_FALSE(client_epitaph_1);
178 }
179
TEST_F(LowEnergyConnectionServerAutoStartTest,RequestGattClientThenUnbindThenRequestAgainShouldSucceed)180 TEST_F(LowEnergyConnectionServerAutoStartTest,
181 RequestGattClientThenUnbindThenRequestAgainShouldSucceed) {
182 fidl::InterfaceHandle<fuchsia::bluetooth::gatt2::Client> handle_0;
183 client()->RequestGattClient(handle_0.NewRequest());
184 fbg::ClientPtr client_0 = handle_0.Bind();
185 std::optional<zx_status_t> client_epitaph_0;
186 client_0.set_error_handler(
187 [&](zx_status_t epitaph) { client_epitaph_0 = epitaph; });
188 RunLoopUntilIdle();
189 EXPECT_FALSE(client_epitaph_0);
190 client_0.Unbind();
191 RunLoopUntilIdle();
192
193 // Requesting a new GATT client should succeed.
194 fidl::InterfaceHandle<fuchsia::bluetooth::gatt2::Client> handle_1;
195 client()->RequestGattClient(handle_1.NewRequest());
196 fbg::ClientPtr client_1 = handle_1.Bind();
197 std::optional<zx_status_t> client_epitaph_1;
198 client_1.set_error_handler(
199 [&](zx_status_t epitaph) { client_epitaph_1 = epitaph; });
200 RunLoopUntilIdle();
201 EXPECT_FALSE(client_epitaph_1);
202 }
203
204 static ::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest
CreateDelayRangeRequestParams(bool has_vendor_config)205 CreateDelayRangeRequestParams(bool has_vendor_config) {
206 ::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest params;
207
208 // params.logical_transport_type
209 params.set_logical_transport_type(
210 fuchsia::bluetooth::LogicalTransportType::LE_CIS);
211
212 // params.data_direction
213 params.set_data_direction(fuchsia::bluetooth::DataDirection::INPUT);
214
215 // params.codec_attributes
216 if (has_vendor_config) {
217 uint16_t kCompanyId = 0x1234;
218 uint16_t kVendorId = 0xfedc;
219 fuchsia::bluetooth::VendorCodingFormat vendor_coding_format;
220 vendor_coding_format.set_company_id(kCompanyId);
221 vendor_coding_format.set_vendor_id(kVendorId);
222 fuchsia::bluetooth::CodecAttributes codec_attributes;
223 codec_attributes.mutable_codec_id()->set_vendor_format(
224 std::move(vendor_coding_format));
225 std::vector<uint8_t> codec_configuration{0x4f, 0x77, 0x65, 0x6e};
226 codec_attributes.set_codec_configuration(codec_configuration);
227 params.set_codec_attributes(std::move(codec_attributes));
228 } else {
229 fuchsia::bluetooth::CodecAttributes codec_attributes;
230 codec_attributes.mutable_codec_id()->set_assigned_format(
231 fuchsia::bluetooth::AssignedCodingFormat::LINEAR_PCM);
232 params.set_codec_attributes(std::move(codec_attributes));
233 }
234
235 return params;
236 }
237
RunGetCodecDelayRangeTest(::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest && params,std::optional<zx_status_t> err=std::nullopt)238 void LowEnergyConnectionServerAutoStartTest::RunGetCodecDelayRangeTest(
239 ::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest&& params,
240 std::optional<zx_status_t> err = std::nullopt) {
241 fuchsia::bluetooth::le::CodecDelay_GetCodecLocalDelayRange_Result result;
242 LowEnergyConnectionServer::GetCodecLocalDelayRangeCallback callback =
243 [&](fuchsia::bluetooth::le::CodecDelay_GetCodecLocalDelayRange_Result
244 cb_result) { result = std::move(cb_result); };
245 client()->GetCodecLocalDelayRange(std::move(params), std::move(callback));
246 RunLoopUntilIdle();
247
248 if (err.has_value()) {
249 ASSERT_TRUE(result.is_err());
250 EXPECT_EQ(result.err(), err.value());
251 } else {
252 ASSERT_TRUE(result.is_response());
253 auto& response = result.response();
254 // These are the default values returned by the FakeController
255 EXPECT_EQ(response.min_controller_delay(), zx::sec(0).get());
256 EXPECT_EQ(response.max_controller_delay(), zx::sec(4).get());
257 }
258 }
259
260 // Invoking GetCodecLocalDelay with a spec-defined coding format
TEST_F(LowEnergyConnectionServerAutoStartTest,GetCodecLocalDelaySpecCodingFormat)261 TEST_F(LowEnergyConnectionServerAutoStartTest,
262 GetCodecLocalDelaySpecCodingFormat) {
263 ::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest params =
264 CreateDelayRangeRequestParams(/*has_vendor_config=*/false);
265 RunGetCodecDelayRangeTest(std::move(params));
266 }
267
268 // Invoking GetCodecLocalDelay with a vendor-defined coding format
TEST_F(LowEnergyConnectionServerAutoStartTest,GetCodecLocalDelayVendorCodingFormat)269 TEST_F(LowEnergyConnectionServerAutoStartTest,
270 GetCodecLocalDelayVendorCodingFormat) {
271 ::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest params =
272 CreateDelayRangeRequestParams(/*has_vendor_config=*/true);
273 RunGetCodecDelayRangeTest(std::move(params));
274 }
275
276 // Invoking GetCodecLocalDelay with missing parameters
TEST_F(LowEnergyConnectionServerAutoStartTest,GetCodecLocalDelayMissingParams)277 TEST_F(LowEnergyConnectionServerAutoStartTest,
278 GetCodecLocalDelayMissingParams) {
279 // Logical transport type missing
280 ::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest params =
281 CreateDelayRangeRequestParams(/*has_vendor_config=*/false);
282 params.clear_logical_transport_type();
283 RunGetCodecDelayRangeTest(std::move(params), ZX_ERR_INVALID_ARGS);
284
285 // Data direction is missing
286 params = CreateDelayRangeRequestParams(/*has_vendor_config=*/false);
287 params.clear_data_direction();
288 RunGetCodecDelayRangeTest(std::move(params), ZX_ERR_INVALID_ARGS);
289
290 // Codec attributes is missing
291 params = CreateDelayRangeRequestParams(/*has_vendor_config=*/true);
292 params.clear_codec_attributes();
293 RunGetCodecDelayRangeTest(std::move(params), ZX_ERR_INVALID_ARGS);
294
295 // codec_attributes.codec_id is missing
296 params = CreateDelayRangeRequestParams(/*has_vendor_config=*/true);
297 params.mutable_codec_attributes()->clear_codec_id();
298 RunGetCodecDelayRangeTest(std::move(params), ZX_ERR_INVALID_ARGS);
299 }
300
301 // Calling GetCodecLocalDelay when the controller doesn't support it
TEST_F(LowEnergyConnectionServerAutoStartTest,GetCodecLocalDelayCommandNotSupported)302 TEST_F(LowEnergyConnectionServerAutoStartTest,
303 GetCodecLocalDelayCommandNotSupported) {
304 // Disable the Read Local Supported Controller Delay instruction
305 bt::testing::FakeController::Settings settings;
306 pw::bluetooth::emboss::MakeSupportedCommandsView(
307 settings.supported_commands, sizeof(settings.supported_commands))
308 .read_local_supported_controller_delay()
309 .Write(false);
310 test_device()->set_settings(settings);
311
312 ::fuchsia::bluetooth::le::CodecDelayGetCodecLocalDelayRangeRequest params =
313 CreateDelayRangeRequestParams(/*has_vendor_config=*/false);
314 RunGetCodecDelayRangeTest(std::move(params), ZX_ERR_INTERNAL);
315 }
316
317 // Class that creates and manages an AcceptCis request and associated objects
318 class AcceptCisRequest {
319 public:
AcceptCisRequest(fble::Connection * connection_client,bt::iso::CigCisIdentifier id)320 AcceptCisRequest(fble::Connection* connection_client,
321 bt::iso::CigCisIdentifier id) {
322 fuchsia::bluetooth::le::ConnectionAcceptCisRequest params;
323 params.set_cig_id(id.cig_id());
324 params.set_cis_id(id.cis_id());
325 params.set_connection_stream(stream_handle_.NewRequest());
326 client_stream_ptr_ = stream_handle_.Bind();
327 client_stream_ptr_.set_error_handler(
328 [this](zx_status_t epitaph) { epitaph_ = epitaph; });
329 connection_client->AcceptCis(std::move(params));
330 }
epitaph()331 std::optional<zx_status_t> epitaph() { return epitaph_; }
332
333 private:
334 fidl::InterfaceHandle<fuchsia::bluetooth::le::IsochronousStream>
335 stream_handle_;
336 fuchsia::bluetooth::le::IsochronousStreamPtr client_stream_ptr_;
337 std::optional<zx_status_t> epitaph_;
338 };
339
340 // Verify that all calls to AcceptCis() with unique CIG/CIS pairs are accepted
341 // and duplicate calls are rejected and the IsochronousStream handle is closed
342 // with an INVALID_ARGS epitaph.
TEST_F(LowEnergyConnectionServerTest,MultipleAcceptCisCalls)343 TEST_F(LowEnergyConnectionServerTest, MultipleAcceptCisCalls) {
344 // AcceptCis() should only be called on a connection where we are acting as
345 // the peripheral.
346 bt::testing::FakeController::Settings settings;
347 settings.le_connection_role =
348 pw::bluetooth::emboss::ConnectionRole::PERIPHERAL;
349 test_device()->set_settings(settings);
350 EstablishConnectionAndStartServer();
351
352 AcceptCisRequest request1(client(), {/*cig_id=*/0x10, /*cis_id=*/0x08});
353 AcceptCisRequest request2(client(), {/*cig_id=*/0x11, /*cis_id=*/0x08});
354 AcceptCisRequest request3(client(), {/*cig_id=*/0x10, /*cis_id=*/0x07});
355 AcceptCisRequest request1_dup(client(), {/*cig_id=*/0x10, /*cis_id=*/0x08});
356 RunLoopUntilIdle();
357
358 // All unique requests are pending
359 EXPECT_FALSE(request1.epitaph());
360 EXPECT_FALSE(request2.epitaph());
361 EXPECT_FALSE(request3.epitaph());
362
363 // Duplicate request is rejected
364 ASSERT_TRUE(request1_dup.epitaph());
365 EXPECT_EQ(*(request1_dup.epitaph()), ZX_ERR_INVALID_ARGS);
366 }
367
368 // Calling AcceptCis when we are the central should fail with
369 // ZX_ERR_NOT_SUPPORTED
TEST_F(LowEnergyConnectionServerTest,AcceptCisCalledFromCentral)370 TEST_F(LowEnergyConnectionServerTest, AcceptCisCalledFromCentral) {
371 bt::testing::FakeController::Settings settings;
372 settings.le_connection_role = pw::bluetooth::emboss::ConnectionRole::CENTRAL;
373 test_device()->set_settings(settings);
374 EstablishConnectionAndStartServer();
375
376 AcceptCisRequest request(client(), {/*cig_id=*/0x10, /*cis_id=*/0x08});
377 RunLoopUntilIdle();
378 ASSERT_TRUE(request.epitaph());
379 EXPECT_EQ(*(request.epitaph()), ZX_ERR_NOT_SUPPORTED);
380 }
381
TEST_F(LowEnergyConnectionServerAutoStartTest,ServerClosedOnConnectionClosed)382 TEST_F(LowEnergyConnectionServerAutoStartTest, ServerClosedOnConnectionClosed) {
383 adapter()->le()->Disconnect(peer_id());
384 RunLoopUntilIdle();
385 EXPECT_TRUE(server_closed_cb_called());
386 }
387
TEST_F(LowEnergyConnectionServerAutoStartTest,ServerClosedWhenFIDLClientClosesConnection)388 TEST_F(LowEnergyConnectionServerAutoStartTest,
389 ServerClosedWhenFIDLClientClosesConnection) {
390 UnbindClient();
391 RunLoopUntilIdle();
392 EXPECT_TRUE(server_closed_cb_called());
393 }
394
TEST_F(LowEnergyConnectionServerAutoStartTest,OpenL2capHappyDefault)395 TEST_F(LowEnergyConnectionServerAutoStartTest, OpenL2capHappyDefault) {
396 constexpr bt::l2cap::Psm kPsm = 15;
397 constexpr bt::l2cap::ChannelParameters kExpectedChannelParameters{
398 .mode = bt::l2cap::CreditBasedFlowControlMode::kLeCreditBasedFlowControl,
399 .max_rx_sdu_size = std::nullopt,
400 .flush_timeout = std::nullopt,
401 };
402
403 l2cap()->ExpectOutboundL2capChannel(
404 connection_handle(), kPsm, 0x40, 0x41, kExpectedChannelParameters);
405
406 std::optional<zx_status_t> error;
407 fbt::ChannelPtr channel_client;
408 channel_client.set_error_handler([&](auto status) { error = status; });
409 auto request = std::move(fble::ConnectionConnectL2capRequest()
410 .set_parameters(fbt::ChannelParameters())
411 .set_psm(kPsm)
412 .set_channel(channel_client.NewRequest()));
413
414 client()->ConnectL2cap(std::move(request));
415 RunLoopUntilIdle();
416 EXPECT_FALSE(error);
417 EXPECT_TRUE(channel_client);
418 }
419
TEST_F(LowEnergyConnectionServerAutoStartTest,OpenL2capHappyParams)420 TEST_F(LowEnergyConnectionServerAutoStartTest, OpenL2capHappyParams) {
421 constexpr bt::l2cap::Psm kPsm = 15;
422 constexpr bt::l2cap::ChannelParameters kChannelParameters{
423 .mode = bt::l2cap::CreditBasedFlowControlMode::kLeCreditBasedFlowControl,
424 .max_rx_sdu_size = 32,
425 .flush_timeout = std::nullopt,
426 };
427
428 l2cap()->ExpectOutboundL2capChannel(
429 connection_handle(), kPsm, 0x40, 0x41, kChannelParameters);
430
431 auto params = std::move(
432 fbt::ChannelParameters()
433 .set_channel_mode(fbt::ChannelMode::LE_CREDIT_BASED_FLOW_CONTROL)
434 .set_max_rx_packet_size(*kChannelParameters.max_rx_sdu_size));
435
436 std::optional<zx_status_t> error;
437 fbt::ChannelPtr channel_client;
438 channel_client.set_error_handler([&](auto status) { error = status; });
439 auto request = std::move(fble::ConnectionConnectL2capRequest()
440 .set_parameters(std::move(params))
441 .set_psm(kPsm)
442 .set_channel(channel_client.NewRequest()));
443
444 client()->ConnectL2cap(std::move(request));
445 RunLoopUntilIdle();
446 EXPECT_FALSE(error);
447 EXPECT_TRUE(channel_client);
448 }
449
TEST_F(LowEnergyConnectionServerAutoStartTest,OpenL2capBadMode)450 TEST_F(LowEnergyConnectionServerAutoStartTest, OpenL2capBadMode) {
451 constexpr bt::l2cap::Psm kPsm = 15;
452 auto params = std::move(
453 fbt::ChannelParameters().set_channel_mode(fbt::ChannelMode::BASIC));
454
455 std::optional<zx_status_t> error;
456 fbt::ChannelPtr channel_client;
457 channel_client.set_error_handler([&](auto status) { error = status; });
458 auto request = std::move(fble::ConnectionConnectL2capRequest()
459 .set_parameters(std::move(params))
460 .set_psm(kPsm)
461 .set_channel(channel_client.NewRequest()));
462
463 client()->ConnectL2cap(std::move(request));
464 RunLoopUntilIdle();
465 ASSERT_TRUE(error);
466 EXPECT_EQ(*error, ZX_ERR_INVALID_ARGS);
467 EXPECT_FALSE(channel_client);
468 }
469
TEST_F(LowEnergyConnectionServerAutoStartTest,OpenL2capFailFlushTimeout)470 TEST_F(LowEnergyConnectionServerAutoStartTest, OpenL2capFailFlushTimeout) {
471 constexpr bt::l2cap::Psm kPsm = 15;
472 auto params = std::move(fbt::ChannelParameters().set_flush_timeout(150));
473
474 std::optional<zx_status_t> error;
475 fbt::ChannelPtr channel_client;
476 channel_client.set_error_handler([&](auto status) { error = status; });
477 auto request = std::move(fble::ConnectionConnectL2capRequest()
478 .set_parameters(std::move(params))
479 .set_psm(kPsm)
480 .set_channel(channel_client.NewRequest()));
481
482 client()->ConnectL2cap(std::move(request));
483 RunLoopUntilIdle();
484 ASSERT_TRUE(error);
485 EXPECT_EQ(*error, ZX_ERR_INVALID_ARGS);
486 EXPECT_FALSE(channel_client);
487 }
488
489 } // namespace
490 } // namespace bthost
491