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