| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/connectivity/bluetooth/core/bt-host/data/domain.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/inspect/testing/cpp/inspect.h> |
| |
| #include <fbl/macros.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/hci.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/channel.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/test_packets.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/types.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/status.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/test_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/test_packets.h" |
| |
| // This test harness provides test cases for interations between L2CAP, RFCOMM, |
| // and SocketFactory in integration, as they are implemented by the domain |
| // object. These exercise a production data plane against raw HCI endpoints. |
| |
| namespace bt { |
| namespace data { |
| namespace { |
| |
| using namespace inspect::testing; |
| |
| using bt::testing::TestController; |
| using TestingBase = bt::testing::FakeControllerTest<TestController>; |
| |
| // Sized intentionally to cause fragmentation for outbound dynamic channel data but not others. May |
| // need adjustment if Signaling Channel Configuration {Request,Response} default transactions get |
| // much bigger. |
| constexpr size_t kMaxDataPacketLength = 64; |
| constexpr size_t kMaxPacketCount = 10; |
| |
| // 2x Information Requests: Extended Features, Fixed Channels Supported |
| constexpr size_t kConnectionCreationPacketCount = 2; |
| |
| constexpr l2cap::ChannelParameters kChannelParameters{l2cap::ChannelMode::kBasic, l2cap::kMaxMTU}; |
| constexpr l2cap::ExtendedFeatures kExtendedFeatures = |
| l2cap::kExtendedFeaturesBitEnhancedRetransmission; |
| |
| class DATA_DomainTest : public TestingBase { |
| public: |
| DATA_DomainTest() = default; |
| ~DATA_DomainTest() override = default; |
| |
| protected: |
| void SetUp() override { |
| TestingBase::SetUp(); |
| const auto bredr_buffer_info = hci::DataBufferInfo(kMaxDataPacketLength, kMaxPacketCount); |
| InitializeACLDataChannel(bredr_buffer_info); |
| |
| domain_ = |
| Domain::Create(transport(), inspector_.GetRoot().CreateChild(Domain::kInspectNodeName)); |
| |
| StartTestDevice(); |
| |
| test_device()->set_data_expectations_enabled(true); |
| |
| next_command_id_ = 1; |
| } |
| |
| void TearDown() override { |
| domain_ = nullptr; |
| TestingBase::TearDown(); |
| } |
| |
| l2cap::CommandId NextCommandId() { return next_command_id_++; } |
| |
| void QueueConfigNegotiation(hci::ConnectionHandle handle, l2cap::ChannelParameters local_params, |
| l2cap::ChannelParameters peer_params, l2cap::ChannelId local_cid, |
| l2cap::ChannelId remote_cid, l2cap::CommandId local_config_req_id, |
| l2cap::CommandId peer_config_req_id) { |
| const auto kPeerConfigRsp = |
| l2cap::testing::AclConfigRsp(local_config_req_id, handle, local_cid, local_params); |
| const auto kPeerConfigReq = |
| l2cap::testing::AclConfigReq(peer_config_req_id, handle, local_cid, peer_params); |
| EXPECT_ACL_PACKET_OUT( |
| test_device(), |
| l2cap::testing::AclConfigReq(local_config_req_id, handle, remote_cid, local_params), |
| &kPeerConfigRsp, &kPeerConfigReq); |
| EXPECT_ACL_PACKET_OUT(test_device(), l2cap::testing::AclConfigRsp(peer_config_req_id, handle, |
| remote_cid, peer_params)); |
| } |
| |
| void QueueInboundL2capConnection(hci::ConnectionHandle handle, l2cap::PSM psm, |
| l2cap::ChannelId local_cid, l2cap::ChannelId remote_cid, |
| l2cap::ChannelParameters local_params = kChannelParameters, |
| l2cap::ChannelParameters peer_params = kChannelParameters) { |
| const l2cap::CommandId kPeerConnReqId = 1; |
| const l2cap::CommandId kPeerConfigReqId = kPeerConnReqId + 1; |
| const auto kConfigReqId = NextCommandId(); |
| EXPECT_ACL_PACKET_OUT(test_device(), l2cap::testing::AclConnectionRsp(kPeerConnReqId, handle, |
| remote_cid, local_cid)); |
| QueueConfigNegotiation(handle, local_params, peer_params, local_cid, remote_cid, kConfigReqId, |
| kPeerConfigReqId); |
| |
| test_device()->SendACLDataChannelPacket( |
| l2cap::testing::AclConnectionReq(kPeerConnReqId, handle, remote_cid, psm)); |
| } |
| |
| template <typename CallbackT> |
| void QueueOutboundL2capConnection(hci::ConnectionHandle handle, l2cap::PSM psm, |
| l2cap::ChannelId local_cid, l2cap::ChannelId remote_cid, |
| CallbackT open_cb, |
| l2cap::ChannelParameters local_params = kChannelParameters, |
| l2cap::ChannelParameters peer_params = kChannelParameters) { |
| const l2cap::CommandId kPeerConfigReqId = 1; |
| const auto kConnReqId = NextCommandId(); |
| const auto kConfigReqId = NextCommandId(); |
| const auto kConnRsp = |
| l2cap::testing::AclConnectionRsp(kConnReqId, handle, local_cid, remote_cid); |
| EXPECT_ACL_PACKET_OUT(test_device(), |
| l2cap::testing::AclConnectionReq(kConnReqId, handle, local_cid, psm), |
| &kConnRsp); |
| QueueConfigNegotiation(handle, local_params, peer_params, local_cid, remote_cid, kConfigReqId, |
| kPeerConfigReqId); |
| |
| domain()->OpenL2capChannel(handle, psm, local_params, std::move(open_cb), dispatcher()); |
| } |
| |
| struct QueueAclConnectionRetVal { |
| l2cap::CommandId extended_features_id; |
| l2cap::CommandId fixed_channels_supported_id; |
| }; |
| |
| QueueAclConnectionRetVal QueueAclConnection( |
| hci::ConnectionHandle handle, hci::Connection::Role role = hci::Connection::Role::kMaster) { |
| QueueAclConnectionRetVal cmd_ids; |
| cmd_ids.extended_features_id = NextCommandId(); |
| cmd_ids.fixed_channels_supported_id = NextCommandId(); |
| |
| const auto kExtFeaturesRsp = l2cap::testing::AclExtFeaturesInfoRsp(cmd_ids.extended_features_id, |
| handle, kExtendedFeatures); |
| EXPECT_ACL_PACKET_OUT( |
| test_device(), l2cap::testing::AclExtFeaturesInfoReq(cmd_ids.extended_features_id, handle), |
| &kExtFeaturesRsp); |
| EXPECT_ACL_PACKET_OUT(test_device(), l2cap::testing::AclFixedChannelsSupportedInfoReq( |
| cmd_ids.fixed_channels_supported_id, handle)); |
| |
| acl_data_channel()->RegisterLink(handle, hci::Connection::LinkType::kACL); |
| domain()->AddACLConnection( |
| handle, role, /*link_error_callback=*/[]() {}, |
| /*security_upgrade_callback=*/[](auto, auto, auto) {}); |
| return cmd_ids; |
| } |
| |
| void QueueLEConnection(hci::ConnectionHandle handle, hci::Connection::Role role) { |
| acl_data_channel()->RegisterLink(handle, hci::Connection::LinkType::kLE); |
| domain()->AddLEConnection( |
| handle, role, /*link_error_callback=*/[] {}, /*conn_param_callback=*/[](auto&) {}, |
| /*channel_callback=*/[](auto, auto) {}, /*security_callback=*/[](auto, auto, auto) {}); |
| } |
| |
| Domain* domain() const { return domain_.get(); } |
| |
| inspect::Inspector& inspector() { return inspector_; } |
| |
| private: |
| fbl::RefPtr<Domain> domain_; |
| l2cap::CommandId next_command_id_; |
| inspect::Inspector inspector_; |
| |
| DISALLOW_COPY_ASSIGN_AND_MOVE(DATA_DomainTest); |
| }; |
| |
| TEST_F(DATA_DomainTest, InboundL2capSocket) { |
| constexpr l2cap::PSM kPSM = l2cap::kAVDTP; |
| constexpr l2cap::ChannelId kLocalId = 0x0040; |
| constexpr l2cap::ChannelId kRemoteId = 0x9042; |
| constexpr hci::ConnectionHandle kLinkHandle = 0x0001; |
| |
| QueueAclConnection(kLinkHandle); |
| |
| zx::socket sock; |
| ASSERT_FALSE(sock); |
| auto sock_cb = [&](auto chan_sock, hci::ConnectionHandle handle) { |
| EXPECT_EQ(kLinkHandle, handle); |
| sock = std::move(chan_sock.socket); |
| }; |
| |
| domain()->RegisterService(kPSM, kChannelParameters, std::move(sock_cb), dispatcher()); |
| RunLoopUntilIdle(); |
| |
| QueueInboundL2capConnection(kLinkHandle, kPSM, kLocalId, kRemoteId); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(sock); |
| |
| // Test basic channel<->socket interaction by verifying that an ACL packet |
| // gets routed to the socket. |
| test_device()->SendACLDataChannelPacket(CreateStaticByteBuffer( |
| // ACL data header (handle: 1, length 8) |
| 0x01, 0x00, 0x08, 0x00, |
| |
| // L2CAP B-frame: (length: 4, channel-id: 0x0040 (kLocalId)) |
| 0x04, 0x00, 0x40, 0x00, 't', 'e', 's', 't')); |
| |
| // Run until the packet is written to the socket buffer. |
| RunLoopUntilIdle(); |
| |
| // Allocate a larger buffer than the number of SDU bytes we expect (which is |
| // 4). |
| StaticByteBuffer<10> socket_bytes; |
| size_t bytes_read; |
| zx_status_t status = sock.read(0, socket_bytes.mutable_data(), socket_bytes.size(), &bytes_read); |
| EXPECT_EQ(ZX_OK, status); |
| ASSERT_EQ(4u, bytes_read); |
| EXPECT_EQ("test", socket_bytes.view(0, bytes_read).AsString()); |
| |
| const char write_data[81] = |
| u8"🚂🚃🚄🚅🚆🚈🚇🚈🚉🚊🚋🚌🚎🚝🚞🚟🚠🚡🛤🛲"; |
| |
| // Test outbound data fragments using |kMaxDataPacketLength|. |
| constexpr size_t kFirstFragmentPayloadSize = kMaxDataPacketLength - sizeof(l2cap::BasicHeader); |
| const auto kFirstFragment = |
| StaticByteBuffer<sizeof(hci::ACLDataHeader) + sizeof(l2cap::BasicHeader) + |
| kFirstFragmentPayloadSize>( |
| // ACL data header (handle: 1, length 64) |
| 0x01, 0x00, 0x40, 0x00, |
| |
| // L2CAP B-frame: (length: 80, channel-id: 0x9042 (kRemoteId)) |
| 0x50, 0x00, 0x42, 0x90, |
| |
| // L2CAP payload (fragmented) |
| 0xf0, 0x9f, 0x9a, 0x82, 0xf0, 0x9f, 0x9a, 0x83, 0xf0, 0x9f, 0x9a, 0x84, 0xf0, 0x9f, 0x9a, |
| 0x85, 0xf0, 0x9f, 0x9a, 0x86, 0xf0, 0x9f, 0x9a, 0x88, 0xf0, 0x9f, 0x9a, 0x87, 0xf0, 0x9f, |
| 0x9a, 0x88, 0xf0, 0x9f, 0x9a, 0x89, 0xf0, 0x9f, 0x9a, 0x8a, 0xf0, 0x9f, 0x9a, 0x8b, 0xf0, |
| 0x9f, 0x9a, 0x8c, 0xf0, 0x9f, 0x9a, 0x8e, 0xf0, 0x9f, 0x9a, 0x9d, 0xf0, 0x9f, 0x9a, 0x9e); |
| |
| constexpr size_t kSecondFragmentPayloadSize = sizeof(write_data) - 1 - kFirstFragmentPayloadSize; |
| const auto kSecondFragment = |
| StaticByteBuffer<sizeof(hci::ACLDataHeader) + kSecondFragmentPayloadSize>( |
| // ACL data header (handle: 1, pbf: continuing fr., length: 20) |
| 0x01, 0x10, 0x14, 0x00, |
| |
| // L2CAP payload (final fragment) |
| 0xf0, 0x9f, 0x9a, 0x9f, 0xf0, 0x9f, 0x9a, 0xa0, 0xf0, 0x9f, 0x9a, 0xa1, 0xf0, 0x9f, 0x9b, |
| 0xa4, 0xf0, 0x9f, 0x9b, 0xb2); |
| |
| // The 80-byte write should be fragmented over 64- and 20-byte HCI payloads in order to send it |
| // to the controller. |
| EXPECT_ACL_PACKET_OUT(test_device(), kFirstFragment); |
| EXPECT_ACL_PACKET_OUT(test_device(), kSecondFragment); |
| |
| size_t bytes_written = 0; |
| // Write 80 outbound bytes to the socket buffer. |
| status = sock.write(0, write_data, sizeof(write_data) - 1, &bytes_written); |
| EXPECT_EQ(ZX_OK, status); |
| EXPECT_EQ(80u, bytes_written); |
| |
| // Run until the data is flushed out to the TestController. |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| |
| domain()->RemoveConnection(kLinkHandle); |
| acl_data_channel()->UnregisterLink(kLinkHandle); |
| acl_data_channel()->ClearControllerPacketCount(kLinkHandle); |
| |
| // try resending data now that connection is closed |
| bytes_written = 0; |
| status = sock.write(0, write_data, sizeof(write_data) - 1, &bytes_written); |
| |
| EXPECT_EQ(ZX_OK, status); |
| EXPECT_EQ(80u, bytes_written); |
| |
| // no packets should be sent |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(DATA_DomainTest, InboundRfcommSocketFails) { |
| constexpr l2cap::PSM kPSM = l2cap::kRFCOMM; |
| constexpr l2cap::ChannelId kRemoteId = 0x9042; |
| constexpr hci::ConnectionHandle kLinkHandle = 0x0001; |
| |
| QueueAclConnection(kLinkHandle); |
| |
| RunLoopUntilIdle(); |
| |
| const l2cap::CommandId kPeerConnReqId = 1; |
| |
| // Incoming connection refused, RFCOMM is not routed. |
| EXPECT_ACL_PACKET_OUT( |
| test_device(), |
| l2cap::testing::AclConnectionRsp(kPeerConnReqId, kLinkHandle, kRemoteId, 0x0000 /*dest id*/, |
| l2cap::ConnectionResult::kPSMNotSupported)); |
| |
| test_device()->SendACLDataChannelPacket( |
| l2cap::testing::AclConnectionReq(kPeerConnReqId, kLinkHandle, kRemoteId, kPSM)); |
| |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(DATA_DomainTest, InboundPacketQueuedAfterChannelOpenIsNotDropped) { |
| constexpr l2cap::PSM kPSM = l2cap::kSDP; |
| constexpr l2cap::ChannelId kLocalId = 0x0040; |
| constexpr l2cap::ChannelId kRemoteId = 0x9042; |
| constexpr hci::ConnectionHandle kLinkHandle = 0x0001; |
| |
| QueueAclConnection(kLinkHandle); |
| |
| zx::socket sock; |
| ASSERT_FALSE(sock); |
| auto sock_cb = [&](auto chan_sock, hci::ConnectionHandle handle) { |
| EXPECT_EQ(kLinkHandle, handle); |
| sock = std::move(chan_sock.socket); |
| }; |
| |
| domain()->RegisterService(kPSM, kChannelParameters, std::move(sock_cb), dispatcher()); |
| RunLoopUntilIdle(); |
| |
| constexpr l2cap::CommandId kConnectionReqId = 1; |
| constexpr l2cap::CommandId kPeerConfigReqId = 6; |
| const l2cap::CommandId kConfigReqId = NextCommandId(); |
| EXPECT_ACL_PACKET_OUT(test_device(), l2cap::testing::AclConnectionRsp( |
| kConnectionReqId, kLinkHandle, kRemoteId, kLocalId)); |
| EXPECT_ACL_PACKET_OUT(test_device(), l2cap::testing::AclConfigReq(kConfigReqId, kLinkHandle, |
| kRemoteId, kChannelParameters)); |
| test_device()->SendACLDataChannelPacket( |
| l2cap::testing::AclConnectionReq(kConnectionReqId, kLinkHandle, kRemoteId, kPSM)); |
| |
| // Config negotiation will not complete yet. |
| RunLoopUntilIdle(); |
| |
| // Remaining config negotiation will be added to dispatch loop. |
| EXPECT_ACL_PACKET_OUT(test_device(), l2cap::testing::AclConfigRsp(kPeerConfigReqId, kLinkHandle, |
| kRemoteId, kChannelParameters)); |
| test_device()->SendACLDataChannelPacket( |
| l2cap::testing::AclConfigReq(kPeerConfigReqId, kLinkHandle, kLocalId, kChannelParameters)); |
| test_device()->SendACLDataChannelPacket( |
| l2cap::testing::AclConfigRsp(kConfigReqId, kLinkHandle, kLocalId, kChannelParameters)); |
| |
| // Queue up a data packet for the new channel before the channel configuration has been |
| // processed. |
| ASSERT_FALSE(sock); |
| test_device()->SendACLDataChannelPacket(CreateStaticByteBuffer( |
| // ACL data header (handle: 1, length 8) |
| 0x01, 0x00, 0x08, 0x00, |
| |
| // L2CAP B-frame: (length: 4, channel-id: 0x0040 (kLocalId)) |
| 0x04, 0x00, 0x40, 0x00, 0xf0, 0x9f, 0x94, 0xb0)); |
| |
| // Run until the socket opens and the packet is written to the socket buffer. |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(sock); |
| |
| // Allocate a larger buffer than the number of SDU bytes we expect (which is 4). |
| StaticByteBuffer<10> socket_bytes; |
| size_t bytes_read; |
| zx_status_t status = sock.read(0, socket_bytes.mutable_data(), socket_bytes.size(), &bytes_read); |
| EXPECT_EQ(ZX_OK, status); |
| ASSERT_EQ(4u, bytes_read); |
| EXPECT_EQ(u8"🔰", socket_bytes.view(0, bytes_read).AsString()); |
| } |
| |
| TEST_F(DATA_DomainTest, OutboundL2capSocket) { |
| constexpr l2cap::PSM kPSM = l2cap::kAVCTP; |
| constexpr l2cap::ChannelId kLocalId = 0x0040; |
| constexpr l2cap::ChannelId kRemoteId = 0x9042; |
| constexpr hci::ConnectionHandle kLinkHandle = 0x0001; |
| |
| QueueAclConnection(kLinkHandle); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| |
| zx::socket sock; |
| ASSERT_FALSE(sock); |
| auto sock_cb = [&](auto chan_sock, hci::ConnectionHandle handle) { |
| EXPECT_EQ(kLinkHandle, handle); |
| sock = std::move(chan_sock.socket); |
| }; |
| |
| QueueOutboundL2capConnection(kLinkHandle, kPSM, kLocalId, kRemoteId, std::move(sock_cb)); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| // We should have opened a channel successfully. |
| ASSERT_TRUE(sock); |
| |
| // Test basic channel<->socket interaction by verifying that an ACL packet |
| // gets routed to the socket. |
| test_device()->SendACLDataChannelPacket(CreateStaticByteBuffer( |
| // ACL data header (handle: 1, length 8) |
| 0x01, 0x00, 0x08, 0x00, |
| |
| // L2CAP B-frame: (length: 4, channel-id: 0x0040 (kLocalId)) |
| 0x04, 0x00, 0x40, 0x00, 't', 'e', 's', 't')); |
| |
| // Run until the packet is written to the socket buffer. |
| RunLoopUntilIdle(); |
| |
| // Allocate a larger buffer than the number of SDU bytes we expect (which is |
| // 4). |
| StaticByteBuffer<10> socket_bytes; |
| size_t bytes_read; |
| zx_status_t status = sock.read(0, socket_bytes.mutable_data(), socket_bytes.size(), &bytes_read); |
| EXPECT_EQ(ZX_OK, status); |
| ASSERT_EQ(4u, bytes_read); |
| EXPECT_EQ("test", socket_bytes.view(0, bytes_read).AsString()); |
| } |
| |
| TEST_F(DATA_DomainTest, OutboundSocketIsInvalidWhenL2capFailsToOpenChannel) { |
| constexpr l2cap::PSM kPSM = l2cap::kAVCTP; |
| constexpr hci::ConnectionHandle kLinkHandle = 0x0001; |
| |
| // Don't register any links. This should cause outbound channels to fail. |
| bool sock_cb_called = false; |
| auto sock_cb = [&sock_cb_called, kLinkHandle](auto chan_sock, hci::ConnectionHandle handle) { |
| sock_cb_called = true; |
| EXPECT_FALSE(chan_sock); |
| EXPECT_EQ(kLinkHandle, handle); |
| }; |
| |
| domain()->OpenL2capChannel(kLinkHandle, kPSM, kChannelParameters, std::move(sock_cb), |
| dispatcher()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(sock_cb_called); |
| } |
| |
| // Queue dynamic channel packets, then open a new dynamic channel. |
| // The signaling channel packets should be sent before the queued dynamic channel packets. |
| TEST_F(DATA_DomainTest, ChannelCreationPrioritizedOverDynamicChannelData) { |
| constexpr hci::ConnectionHandle kLinkHandle = 0x0001; |
| |
| constexpr l2cap::PSM kPSM0 = l2cap::kAVCTP; |
| constexpr l2cap::ChannelId kLocalId0 = 0x0040; |
| constexpr l2cap::ChannelId kRemoteId0 = 0x9042; |
| |
| constexpr l2cap::PSM kPSM1 = l2cap::kAVDTP; |
| constexpr l2cap::ChannelId kLocalId1 = 0x0041; |
| constexpr l2cap::ChannelId kRemoteId1 = 0x9043; |
| |
| // l2cap connection request (or response), config request, config response |
| constexpr size_t kChannelCreationPacketCount = 3; |
| |
| QueueAclConnection(kLinkHandle); |
| |
| zx::socket sock0; |
| ASSERT_FALSE(sock0); |
| auto sock_cb0 = [&](auto chan_sock, hci::ConnectionHandle handle) { |
| EXPECT_EQ(kLinkHandle, handle); |
| sock0 = std::move(chan_sock.socket); |
| }; |
| domain()->RegisterService(kPSM0, kChannelParameters, sock_cb0, dispatcher()); |
| |
| QueueInboundL2capConnection(kLinkHandle, kPSM0, kLocalId0, kRemoteId0); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| ASSERT_TRUE(sock0); |
| |
| test_device()->SendCommandChannelPacket(testing::NumberOfCompletedPacketsPacket( |
| kLinkHandle, kConnectionCreationPacketCount + kChannelCreationPacketCount)); |
| |
| // Dummy dynamic channel packet |
| const auto kPacket0 = CreateStaticByteBuffer( |
| // ACL data header (handle: 1, length 5) |
| 0x01, 0x00, 0x05, 0x00, |
| |
| // L2CAP B-frame: (length: 1, channel-id: 0x9042 (kRemoteId)) |
| 0x01, 0x00, 0x42, 0x90, |
| |
| // L2CAP payload |
| 0x01); |
| |
| // |kMaxPacketCount| packets should be sent to the controller, |
| // and 1 packet should be left in the queue |
| const char write_data[] = {0x01}; |
| for (size_t i = 0; i < kMaxPacketCount + 1; i++) { |
| if (i != kMaxPacketCount) { |
| EXPECT_ACL_PACKET_OUT(test_device(), kPacket0); |
| } |
| size_t bytes_written = 0; |
| zx_status_t status = sock0.write(0, write_data, sizeof(write_data), &bytes_written); |
| EXPECT_EQ(ZX_OK, status); |
| EXPECT_EQ(sizeof(write_data), bytes_written); |
| } |
| |
| EXPECT_FALSE(test_device()->AllExpectedDataPacketsSent()); |
| // Run until the data is flushed out to the TestController. |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| |
| zx::socket sock1; |
| ASSERT_FALSE(sock1); |
| auto sock_cb1 = [&](auto chan_sock, hci::ConnectionHandle handle) { |
| EXPECT_EQ(kLinkHandle, handle); |
| sock1 = std::move(chan_sock.socket); |
| }; |
| |
| QueueOutboundL2capConnection(kLinkHandle, kPSM1, kLocalId1, kRemoteId1, std::move(sock_cb1)); |
| |
| for (size_t i = 0; i < kChannelCreationPacketCount; i++) { |
| test_device()->SendCommandChannelPacket( |
| testing::NumberOfCompletedPacketsPacket(kLinkHandle, 1)); |
| // Wait for next connection creation packet to be queued (eg. configuration request/response). |
| RunLoopUntilIdle(); |
| } |
| |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| EXPECT_TRUE(sock1); |
| |
| // Make room in buffer for queued dynamic channel packet. |
| test_device()->SendCommandChannelPacket(testing::NumberOfCompletedPacketsPacket(kLinkHandle, 1)); |
| |
| EXPECT_ACL_PACKET_OUT(test_device(), kPacket0); |
| RunLoopUntilIdle(); |
| // 1 Queued dynamic channel data packet should have been sent. |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| } |
| |
| TEST_F(DATA_DomainTest, NegotiateChannelParametersOnOutboundL2capSocket) { |
| constexpr l2cap::PSM kPSM = l2cap::kAVDTP; |
| constexpr l2cap::ChannelId kLocalId = 0x0040; |
| constexpr l2cap::ChannelId kRemoteId = 0x9042; |
| constexpr hci::ConnectionHandle kLinkHandle = 0x0001; |
| constexpr uint16_t kMtu = l2cap::kMinACLMTU; |
| |
| l2cap::ChannelParameters chan_params; |
| chan_params.mode = l2cap::ChannelMode::kEnhancedRetransmission; |
| chan_params.max_rx_sdu_size = kMtu; |
| |
| QueueAclConnection(kLinkHandle); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| |
| fbl::RefPtr<l2cap::Channel> chan; |
| auto chan_cb = [&](fbl::RefPtr<l2cap::Channel> cb_chan) { chan = std::move(cb_chan); }; |
| |
| QueueOutboundL2capConnection(kLinkHandle, kPSM, kLocalId, kRemoteId, chan_cb, chan_params, |
| chan_params); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLinkHandle, chan->link_handle()); |
| EXPECT_EQ(*chan_params.max_rx_sdu_size, chan->max_rx_sdu_size()); |
| EXPECT_EQ(*chan_params.mode, chan->mode()); |
| } |
| |
| TEST_F(DATA_DomainTest, NegotiateChannelParametersOnInboundL2capSocket) { |
| constexpr l2cap::PSM kPSM = l2cap::kAVDTP; |
| constexpr l2cap::ChannelId kLocalId = 0x0040; |
| constexpr l2cap::ChannelId kRemoteId = 0x9042; |
| constexpr hci::ConnectionHandle kLinkHandle = 0x0001; |
| |
| l2cap::ChannelParameters chan_params; |
| chan_params.mode = l2cap::ChannelMode::kEnhancedRetransmission; |
| chan_params.max_rx_sdu_size = l2cap::kMinACLMTU; |
| |
| QueueAclConnection(kLinkHandle); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| |
| std::optional<l2cap::ChannelSocket> chan; |
| auto sock_cb = [&](auto cb_chan, auto /*handle*/) { chan.emplace(std::move(cb_chan)); }; |
| domain()->RegisterService(kPSM, chan_params, sock_cb, dispatcher()); |
| |
| QueueInboundL2capConnection(kLinkHandle, kPSM, kLocalId, kRemoteId, chan_params, chan_params); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent()); |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(*chan_params.max_rx_sdu_size, chan->params->max_rx_sdu_size); |
| EXPECT_EQ(*chan_params.mode, chan->params->mode); |
| } |
| |
| TEST_F(DATA_DomainTest, RequestConnectionParameterUpdateAndReceiveResponse) { |
| // Valid parameter values |
| constexpr uint16_t kIntervalMin = 6; |
| constexpr uint16_t kIntervalMax = 7; |
| constexpr uint16_t kSlaveLatency = 1; |
| constexpr uint16_t kTimeoutMult = 10; |
| const hci::LEPreferredConnectionParameters kParams(kIntervalMin, kIntervalMax, kSlaveLatency, |
| kTimeoutMult); |
| |
| constexpr hci::ConnectionHandle kLinkHandle = 0x0001; |
| QueueLEConnection(kLinkHandle, hci::Connection::Role::kSlave); |
| |
| std::optional<bool> accepted; |
| auto request_cb = [&accepted](bool cb_accepted) { accepted = cb_accepted; }; |
| |
| // Receive "Accepted" Response: |
| |
| l2cap::CommandId param_update_req_id = NextCommandId(); |
| EXPECT_ACL_PACKET_OUT(test_device(), l2cap::testing::AclConnectionParameterUpdateReq( |
| param_update_req_id, kLinkHandle, kIntervalMin, |
| kIntervalMax, kSlaveLatency, kTimeoutMult)); |
| domain()->RequestConnectionParameterUpdate(kLinkHandle, kParams, request_cb, dispatcher()); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(accepted.has_value()); |
| |
| test_device()->SendACLDataChannelPacket(l2cap::testing::AclConnectionParameterUpdateRsp( |
| param_update_req_id, kLinkHandle, l2cap::ConnectionParameterUpdateResult::kAccepted)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(accepted.has_value()); |
| EXPECT_TRUE(accepted.value()); |
| accepted.reset(); |
| } |
| |
| TEST_F(DATA_DomainTest, InspectHierarchy) { |
| auto hierarchy = inspect::ReadFromVmo(inspector().DuplicateVmo()); |
| ASSERT_TRUE(hierarchy); |
| auto domain_matcher = AllOf(NodeMatches(AllOf(PropertyList(::testing::IsEmpty()))), |
| ChildrenMatch(::testing::IsEmpty())); |
| EXPECT_THAT(hierarchy.value(), AllOf(ChildrenMatch(UnorderedElementsAre(domain_matcher)))); |
| } |
| |
| } // namespace |
| } // namespace data |
| } // namespace bt |