// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_sapphire/internal/host/testing/fake_dynamic_channel.h" #include #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h" #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h" #include "pw_bluetooth_sapphire/internal/host/l2cap/test_packets.h" #include "pw_bluetooth_sapphire/internal/host/testing/fake_l2cap.h" #include "pw_bluetooth_sapphire/internal/host/testing/fake_signaling_server.h" #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" #include "pw_unit_test/framework.h" namespace bt::testing { namespace { hci_spec::ConnectionHandle kConnectionHandle = 0x01; l2cap::CommandId kCommandId = 0x02; l2cap::Psm kPsm = l2cap::kSDP; TEST(FakeDynamicChannelTest, ConnectOpenDisconnectChannel) { std::unique_ptr received_packet; auto send_cb = [&received_packet]( auto /*kConnectionHandle*/, auto /*cid*/, auto& buffer) { received_packet = std::make_unique(buffer); }; auto fake_l2cap = FakeL2cap(send_cb); auto server = std::make_unique(); server->RegisterWithL2cap(&fake_l2cap); auto channel_cb = [](auto /*fake_dynamic_channel*/) {}; fake_l2cap.RegisterService(kPsm, channel_cb); l2cap::ChannelId src_id = l2cap::kFirstDynamicChannelId; l2cap::ChannelParameters params; // Assemble and send the ConnectionRequest to connect, but not open, the // channel. auto connection_acl_packet = l2cap::testing::AclConnectionReq( kCommandId, kConnectionHandle, src_id, kPsm); const auto& connection_header = connection_acl_packet.To(); auto connection_header_len = sizeof(connection_header); uint16_t connection_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, connection_header.data_total_length); auto connection_packet = DynamicByteBuffer(connection_payload_len); connection_acl_packet.Copy( &connection_packet, connection_header_len, connection_payload_len); fake_l2cap.HandlePdu(kConnectionHandle, connection_packet); // Anticipate that we then receive a ConfigurationRequest. HandlePdu will // first send a ConnectionResponse, but the most recent packet should be a // ConfigurationRequest. The channel should also be connected, but not open, // at this time. // Manually create the expected ConfigurationRequest with no payload. StaticByteBuffer expected_request( // Configuration request command code, CommandId associated with the test l2cap::kConfigurationRequest, kCommandId, // Payload length (4 total bytes) 0x04, 0x00, // Source ID (2 bytes) LowerBits(src_id), UpperBits(src_id), // No continuation flags (2 bytes) 0x00, 0x00); EXPECT_TRUE(ContainersEqual(expected_request, *received_packet)); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_request_received()); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_response_received()); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByRemoteId(kConnectionHandle, src_id) ->opened()); // Send a ConfigurationResponse to the received ConfigurationRequest. auto configuration_response_acl_packet = l2cap::testing::AclConfigRsp( kCommandId, kConnectionHandle, src_id, params); const auto& configuration_response_header = configuration_response_acl_packet.To(); auto configuration_response_header_len = sizeof(configuration_response_header); uint16_t configuration_response_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, configuration_response_header.data_total_length); auto configuration_response_packet = DynamicByteBuffer(configuration_response_payload_len); configuration_response_acl_packet.Copy(&configuration_response_packet, configuration_response_header_len, configuration_response_payload_len); fake_l2cap.HandlePdu(kConnectionHandle, configuration_response_packet); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_request_received()); EXPECT_TRUE(fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_response_received()); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByRemoteId(kConnectionHandle, src_id) ->opened()); // Assemble and send the ConfigurationRequest to open up the channel. // In this isolated test, we can assume that the src_id and dest_id are // identical. auto configuration_request_acl_packet = l2cap::testing::AclConfigReq( kCommandId, kConnectionHandle, src_id, params); const auto& configuration_request_header = configuration_request_acl_packet.To(); auto configuration_request_header_len = sizeof(configuration_request_header); uint16_t configuration_request_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, configuration_request_header.data_total_length); auto configuration_request_packet = DynamicByteBuffer(configuration_request_payload_len); configuration_request_acl_packet.Copy(&configuration_request_packet, configuration_request_header_len, configuration_request_payload_len); fake_l2cap.HandlePdu(kConnectionHandle, configuration_request_packet); // Anticipate that we then receive a ConfigurationResponse after we send a // Manually create the expected ConfigurationRequest with no payload. StaticByteBuffer expected_response( // Configuration request command code, CommandId associated with the test l2cap::kConfigurationResponse, kCommandId, // Payload length (6 total bytes) 0x06, 0x00, // Source ID (2 bytes) LowerBits(src_id), UpperBits(src_id), // No continuation flags (2 bytes) 0x00, 0x00, // Result (Success) LowerBits(0x0000), UpperBits(0x0000)); EXPECT_TRUE(ContainersEqual(expected_response, *received_packet)); EXPECT_TRUE(fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_request_received()); EXPECT_TRUE(fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_response_received()); EXPECT_TRUE( fake_l2cap.FindDynamicChannelByRemoteId(kConnectionHandle, src_id) ->opened()); // Assemble and send the DisconnectionRequest to open up the channel. // In this isolated test, we can assume that the src_id and dest_id are // identical. auto disconnection_acl_packet = l2cap::testing::AclDisconnectionReq( kCommandId, kConnectionHandle, src_id, src_id); const auto& disconnection_header = disconnection_acl_packet.To(); auto disconnection_header_len = sizeof(disconnection_header); uint16_t disconnection_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, disconnection_header.data_total_length); auto disconnection_packet = DynamicByteBuffer(disconnection_payload_len); disconnection_acl_packet.Copy(&disconnection_packet, disconnection_header_len, disconnection_payload_len); fake_l2cap.HandlePdu(kConnectionHandle, disconnection_packet); // Anticipate that we receive a DisconnectionResponse after we send the // request and that the channel has been deleted. StaticByteBuffer disconnection_response( // Configuration request command code, CommandId associated with the test l2cap::kDisconnectionResponse, kCommandId, // Payload length (4 total bytes) 0x04, 0x00, // Source ID (2 bytes) LowerBits(src_id), UpperBits(src_id), // Dest ID (2 bytes) LowerBits(src_id), UpperBits(src_id)); EXPECT_TRUE(ContainersEqual(disconnection_response, *received_packet)); EXPECT_FALSE(fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) .is_alive()); } TEST(FakeDynamicChannelTest, FailToRegisterChannelWithoutRegisteredService) { // Create a custom FakeL2cap with no registered services. std::unique_ptr received_packet; auto send_cb = [&received_packet]( auto /*kConnectionHandle*/, auto /*cid*/, auto& buffer) { received_packet = std::make_unique(buffer); }; auto fake_l2cap_without_service = FakeL2cap(send_cb); auto server = std::make_unique(); server->RegisterWithL2cap(&fake_l2cap_without_service); l2cap::ChannelId src_id = l2cap::kFirstDynamicChannelId; // Assemble and send the ConnectionRequest to connect, but not open, the // channel. auto connection_acl_packet = l2cap::testing::AclConnectionReq( kCommandId, kConnectionHandle, src_id, kPsm); const auto& connection_header = connection_acl_packet.To(); auto connection_header_len = sizeof(connection_header); uint16_t connection_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, connection_header.data_total_length); auto connection_packet = DynamicByteBuffer(connection_payload_len); connection_acl_packet.Copy( &connection_packet, connection_header_len, connection_payload_len); fake_l2cap_without_service.HandlePdu(kConnectionHandle, connection_packet); // Anticipate that we will receive a rejection as the packet is not supported. // As this is an isolated test case, assume that src_id and dst_id are the // same. auto expected_acl_response = l2cap::testing::AclConnectionRsp( kCommandId, kConnectionHandle, src_id, l2cap::kInvalidChannelId, l2cap::ConnectionResult::kPsmNotSupported); auto expected_response = expected_acl_response.view( sizeof(hci_spec::ACLDataHeader) + sizeof(l2cap::CommandHeader)); EXPECT_TRUE(ContainersEqual(expected_response, *received_packet)); EXPECT_FALSE(fake_l2cap_without_service .FindDynamicChannelByLocalId(kConnectionHandle, src_id) .is_alive()); } TEST(FakeDynamicChannelTest, FailToRegisterChannelWithInvalidCid) { // Configure FakeSignalingServer to copy any received signaling packets. std::unique_ptr received_packet; auto send_cb = [&received_packet]( auto /*kConnectionHandle*/, auto /*cid*/, auto& buffer) { received_packet = std::make_unique(buffer); }; auto fake_l2cap = FakeL2cap(send_cb); auto server = std::make_unique(); server->RegisterWithL2cap(&fake_l2cap); auto channel_cb = [](auto /*fake_dynamic_channel*/) {}; fake_l2cap.RegisterService(kPsm, channel_cb); l2cap::ChannelId src_id = l2cap::kInvalidChannelId; // Assemble and send the ConnectionRequest to connect, but not open, the // channel. auto connection_acl_packet = l2cap::testing::AclConnectionReq( kCommandId, kConnectionHandle, src_id, kPsm); const auto& connection_header = connection_acl_packet.To(); auto connection_header_len = sizeof(connection_header); uint16_t connection_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, connection_header.data_total_length); auto connection_packet = DynamicByteBuffer(connection_payload_len); connection_acl_packet.Copy( &connection_packet, connection_header_len, connection_payload_len); fake_l2cap.HandlePdu(kConnectionHandle, connection_packet); // Anticipate that we will receive a rejection as the ID is not supported. auto expected_acl_response = l2cap::testing::AclConnectionRsp( kCommandId, kConnectionHandle, src_id, l2cap::kInvalidChannelId, l2cap::ConnectionResult::kInvalidSourceCID); auto expected_response = expected_acl_response.view( sizeof(hci_spec::ACLDataHeader) + sizeof(l2cap::CommandHeader)); EXPECT_TRUE(ContainersEqual(expected_response, *received_packet)); EXPECT_FALSE(fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) .is_alive()); } TEST(FakeDynamicChannelTest, FailToRegisterDuplicateRemoteId) { std::unique_ptr received_packet; auto send_cb = [&received_packet]( auto /*kConnectionHandle*/, auto /*cid*/, auto& buffer) { received_packet = std::make_unique(buffer); }; auto fake_l2cap = FakeL2cap(send_cb); auto server = std::make_unique(); server->RegisterWithL2cap(&fake_l2cap); auto channel_cb = [](auto /*fake_dynamic_channel*/) {}; fake_l2cap.RegisterService(kPsm, channel_cb); l2cap::ChannelId src_id = l2cap::kFirstDynamicChannelId; l2cap::ChannelParameters params; // Assemble and send the ConnectionRequest to connect, but not open, the // channel. auto connection_acl_packet = l2cap::testing::AclConnectionReq( kCommandId, kConnectionHandle, src_id, kPsm); const auto& connection_header = connection_acl_packet.To(); auto connection_header_len = sizeof(connection_header); uint16_t connection_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, connection_header.data_total_length); auto connection_packet = DynamicByteBuffer(connection_payload_len); connection_acl_packet.Copy( &connection_packet, connection_header_len, connection_payload_len); fake_l2cap.HandlePdu(kConnectionHandle, connection_packet); // Anticipate that we then receive a ConfigurationRequest. HandlePdu will // first send a ConnectionResponse, but the most recent packet should be a // ConfigurationRequest. The channel should also be connected, but not open, // at this time. // Manually create the expected ConfigurationRequest with no payload. StaticByteBuffer expected_request( // Configuration request command code, CommandId associated with the test l2cap::kConfigurationRequest, kCommandId, // Payload length (4 total bytes) 0x04, 0x00, // Source ID (2 bytes) LowerBits(src_id), UpperBits(src_id), // No continuation flags (2 bytes) 0x00, 0x00); EXPECT_TRUE(ContainersEqual(expected_request, *received_packet)); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_request_received()); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_response_received()); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->opened()); // Send a ConfigurationResponse to the received ConfigurationRequest. auto configuration_response_acl_packet = l2cap::testing::AclConfigRsp( kCommandId, kConnectionHandle, src_id, params); const auto& configuration_response_header = configuration_response_acl_packet.To(); auto configuration_response_header_len = sizeof(configuration_response_header); uint16_t configuration_response_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, configuration_response_header.data_total_length); auto configuration_response_packet = DynamicByteBuffer(configuration_response_payload_len); configuration_response_acl_packet.Copy(&configuration_response_packet, configuration_response_header_len, configuration_response_payload_len); fake_l2cap.HandlePdu(kConnectionHandle, configuration_response_packet); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_request_received()); EXPECT_TRUE(fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_response_received()); EXPECT_FALSE( fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->opened()); // Assemble and send the ConfigurationRequest to open up the channel. // In this isolated test, we can assume that the src_id and dest_id are // identical. auto configuration_request_acl_packet = l2cap::testing::AclConfigReq( kCommandId, kConnectionHandle, src_id, params); const auto& configuration_request_header = configuration_request_acl_packet.To(); auto configuration_request_header_len = sizeof(configuration_request_header); uint16_t configuration_request_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, configuration_request_header.data_total_length); auto configuration_request_packet = DynamicByteBuffer(configuration_request_payload_len); configuration_request_acl_packet.Copy(&configuration_request_packet, configuration_request_header_len, configuration_request_payload_len); fake_l2cap.HandlePdu(kConnectionHandle, configuration_request_packet); // Anticipate that we then receive a ConfigurationResponse after we send a // Manually create the expected ConfigurationRequest with no payload. StaticByteBuffer expected_response( // Configuration request command code, CommandId associated with the test l2cap::kConfigurationResponse, kCommandId, // Payload length (6 total bytes) 0x06, 0x00, // Source ID (2 bytes) LowerBits(src_id), UpperBits(src_id), // No continuation flags (2 bytes) 0x00, 0x00, // Result (Success) LowerBits(0x0000), UpperBits(0x0000)); EXPECT_TRUE(ContainersEqual(expected_response, *received_packet)); EXPECT_TRUE(fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_request_received()); EXPECT_TRUE(fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->configuration_response_received()); EXPECT_TRUE(fake_l2cap.FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->opened()); // Try to open up the same channel again. auto second_connection_acl_packet = l2cap::testing::AclConnectionReq( kCommandId, kConnectionHandle, src_id, kPsm); const auto& second_connection_header = second_connection_acl_packet.To(); auto second_connection_header_len = sizeof(second_connection_header); uint16_t second_connection_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, second_connection_header.data_total_length); auto second_connection_packet = DynamicByteBuffer(second_connection_payload_len); second_connection_acl_packet.Copy(&second_connection_packet, second_connection_header_len, second_connection_payload_len); fake_l2cap.HandlePdu(kConnectionHandle, second_connection_packet); // Anticipate that we will receive a rejection as the remote ID has already // been registered. auto second_expected_acl_response = l2cap::testing::AclConnectionRsp( kCommandId, kConnectionHandle, src_id, l2cap::kInvalidChannelId, l2cap::ConnectionResult::kSourceCIDAlreadyAllocated); auto second_expected_response = second_expected_acl_response.view( sizeof(hci_spec::ACLDataHeader) + sizeof(l2cap::CommandHeader)); EXPECT_TRUE(ContainersEqual(second_expected_response, *received_packet)); } TEST(FakeDynamicChannelTest, FailWhenOutOfIds) { auto unexpected_cb = [](auto /*handle*/, auto& /*pdu*/) {}; std::unique_ptr received_packet; auto send_cb = [&received_packet]( auto /*kConnectionHandle*/, auto /*cid*/, auto& buffer) { received_packet = std::make_unique(buffer); }; auto fewer_ids_fake_l2cap_ = FakeL2cap(send_cb, unexpected_cb, l2cap::kFirstDynamicChannelId); auto server = std::make_unique(); server->RegisterWithL2cap(&fewer_ids_fake_l2cap_); auto channel_cb = [](auto /*fake_dynamic_channel*/) {}; fewer_ids_fake_l2cap_.RegisterService(kPsm, channel_cb); l2cap::ChannelId src_id = l2cap::kFirstDynamicChannelId; // Assemble and send the ConnectionRequest to connect, but not open, the // channel. auto connection_acl_packet = l2cap::testing::AclConnectionReq( kCommandId, kConnectionHandle, src_id, kPsm); const auto& connection_header = connection_acl_packet.To(); auto connection_header_len = sizeof(connection_header); uint16_t connection_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, connection_header.data_total_length); auto connection_packet = DynamicByteBuffer(connection_payload_len); connection_acl_packet.Copy( &connection_packet, connection_header_len, connection_payload_len); fewer_ids_fake_l2cap_.HandlePdu(kConnectionHandle, connection_packet); EXPECT_FALSE(fewer_ids_fake_l2cap_ .FindDynamicChannelByLocalId(kConnectionHandle, src_id) ->opened()); // The FakeL2cap instance should now be out of ChannelIds to assign. l2cap::ChannelId second_src_id = l2cap::kFirstDynamicChannelId + 1; auto second_connection_acl_packet = l2cap::testing::AclConnectionReq( kCommandId, kConnectionHandle, second_src_id, kPsm); const auto& second_connection_header = second_connection_acl_packet.To(); auto second_connection_header_len = sizeof(second_connection_header); uint16_t second_connection_payload_len = pw::bytes::ConvertOrderFrom( cpp20::endian::little, second_connection_header.data_total_length); auto second_connection_packet = DynamicByteBuffer(second_connection_payload_len); second_connection_acl_packet.Copy(&second_connection_packet, second_connection_header_len, second_connection_payload_len); fewer_ids_fake_l2cap_.HandlePdu(kConnectionHandle, second_connection_packet); // Anticipate that we will receive a rejection as there are no Ids left. auto expected_acl_response = l2cap::testing::AclConnectionRsp(kCommandId, kConnectionHandle, second_src_id, l2cap::kInvalidChannelId, l2cap::ConnectionResult::kNoResources); auto expected_response = expected_acl_response.view( sizeof(hci_spec::ACLDataHeader) + sizeof(l2cap::CommandHeader)); EXPECT_TRUE(ContainersEqual(expected_response, *received_packet)); EXPECT_FALSE( fewer_ids_fake_l2cap_ .FindDynamicChannelByLocalId(kConnectionHandle, second_src_id) .is_alive()); } } // namespace } // namespace bt::testing