// 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/l2cap/channel_configuration.h" #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h" #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" #include "pw_unit_test/framework.h" namespace bt::l2cap::internal { namespace { using MtuOption = ChannelConfiguration::MtuOption; using RetransmissionAndFlowControlOption = ChannelConfiguration::RetransmissionAndFlowControlOption; using FlushTimeoutOption = ChannelConfiguration::FlushTimeoutOption; using UnknownOption = ChannelConfiguration::UnknownOption; constexpr auto kUnknownOptionType = static_cast(0x10); const MtuOption kMtuOption(48); const RetransmissionAndFlowControlOption kRetransmissionAndFlowControlOption = RetransmissionAndFlowControlOption::MakeBasicMode(); const FlushTimeoutOption kFlushTimeoutOption(200); class ChannelConfigurationTest : public ::testing::Test { public: ChannelConfigurationTest() = default; ~ChannelConfigurationTest() override = default; BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ChannelConfigurationTest); }; TEST_F(ChannelConfigurationTest, ReadAllOptionTypes) { const StaticByteBuffer kOptionBuffer( // MTU Option static_cast(OptionType::kMTU), MtuOption::kPayloadLength, LowerBits(kMinACLMTU), UpperBits(kMinACLMTU), // Rtx Option static_cast(OptionType::kRetransmissionAndFlowControl), RetransmissionAndFlowControlOption::kPayloadLength, static_cast(RetransmissionAndFlowControlMode::kRetransmission), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Flush Timeout option (timeout = 200) static_cast(OptionType::kFlushTimeout), FlushTimeoutOption::kPayloadLength, 0xc8, 0x00, // Unknown option, Type = 0x70, Length = 1, payload = 0x03 0x70, 0x01, 0x03); ChannelConfiguration config; EXPECT_TRUE(config.ReadOptions(kOptionBuffer)); EXPECT_TRUE(config.mtu_option()); EXPECT_EQ(kMinACLMTU, config.mtu_option()->mtu()); EXPECT_TRUE(config.retransmission_flow_control_option()); EXPECT_EQ(RetransmissionAndFlowControlMode::kRetransmission, config.retransmission_flow_control_option()->mode()); EXPECT_TRUE(config.flush_timeout_option()); EXPECT_EQ(200u, config.flush_timeout_option()->flush_timeout()); EXPECT_EQ(1u, config.unknown_options().size()); auto& option = config.unknown_options().front(); EXPECT_EQ(0x70, static_cast(option.type())); EXPECT_EQ(1u, option.payload().size()); EXPECT_EQ(0x03, option.payload().data()[0]); } TEST_F(ChannelConfigurationTest, ReadTooShortOption) { // clang-format off // missing required Length field StaticByteBuffer kEncodedOption( // Type = QoS 0x03); //clang-format on ChannelConfiguration config; EXPECT_FALSE(config.ReadOptions(kEncodedOption)); } TEST_F(ChannelConfigurationTest, ReadInvalidOptionField) { // clang-format off StaticByteBuffer kEncodedOption( // Length = 255 static_cast(kUnknownOptionType), 0xFF); // clang-format on ChannelConfiguration config; EXPECT_FALSE(config.ReadOptions(kEncodedOption)); } TEST_F(ChannelConfigurationTest, ReadIncorrectOptionLength) { // clang-format off StaticByteBuffer kEncodedMtuOption( // Type = MTU, Length = 3 (spec length is 2) OptionType::kMTU, 0x03, 0x00, 0x00, 0x00); //clang-format on ChannelConfiguration config; EXPECT_FALSE(config.ReadOptions(kEncodedMtuOption)); // clang-format off StaticByteBuffer kEncodedRetransmissionOption( // Type, Length = 1 (spec length is 9) OptionType::kRetransmissionAndFlowControl, 0x01, 0x00); //clang-format on EXPECT_FALSE(config.ReadOptions(kEncodedRetransmissionOption)); StaticByteBuffer kEncodedFlushTimeoutOption( // Type, Length = 1 (spec length is 2) OptionType::kFlushTimeout, 0x01, 0x00); EXPECT_FALSE(config.ReadOptions(kEncodedFlushTimeoutOption)); } TEST_F(ChannelConfigurationTest, MtuOptionDecodeEncode) { constexpr uint16_t kMTU = 48; // clang-format off StaticByteBuffer kExpectedEncodedMtuOption( // Type = MTU, Length = 2 0x01, 0x02, // MTU = 48 LowerBits(kMTU), UpperBits(kMTU)); //clang-format on ChannelConfiguration::MtuOption mtu_option(kExpectedEncodedMtuOption.view(sizeof(ConfigurationOption))); EXPECT_EQ(mtu_option.mtu(), kMTU); EXPECT_TRUE(ContainersEqual(mtu_option.Encode(), kExpectedEncodedMtuOption)); } TEST_F(ChannelConfigurationTest, RetransmissionAndFlowControlOptionDecodeEncode) { const auto kMode = RetransmissionAndFlowControlMode::kRetransmission; const uint8_t kTxWindow = 32; const uint8_t kMaxTransmit = 1; const uint16_t kRtxTimeout = 512; const uint16_t kMonitorTimeout = 528; const uint16_t kMaxPDUPayloadSize = 256; // clang-format off StaticByteBuffer kExpectedEncodedRetransmissionAndFlowControlOption( // Type = rtx and flow control, Length = 9 0x04, 0x09, static_cast(kMode), kTxWindow, kMaxTransmit, LowerBits(kRtxTimeout), UpperBits(kRtxTimeout), LowerBits(kMonitorTimeout), UpperBits(kMonitorTimeout), LowerBits(kMaxPDUPayloadSize), UpperBits(kMaxPDUPayloadSize) ); // clang-format on ChannelConfiguration::RetransmissionAndFlowControlOption rtx_option( kExpectedEncodedRetransmissionAndFlowControlOption.view( sizeof(ConfigurationOption))); EXPECT_EQ(kTxWindow, rtx_option.tx_window_size()); EXPECT_EQ(kMaxTransmit, rtx_option.max_transmit()); EXPECT_EQ(kRtxTimeout, rtx_option.rtx_timeout()); EXPECT_EQ(kMonitorTimeout, rtx_option.monitor_timeout()); EXPECT_EQ(kMaxPDUPayloadSize, rtx_option.mps()); EXPECT_TRUE(ContainersEqual( rtx_option.Encode(), kExpectedEncodedRetransmissionAndFlowControlOption)); } TEST_F(ChannelConfigurationTest, FlushTimeoutOptionDecodeEncode) { const uint16_t kFlushTimeout = 200; StaticByteBuffer kExpectedEncodedFlushTimeoutOption( // flush timeout type = 0x02, length = 2 0x02, 0x02, LowerBits(kFlushTimeout), UpperBits(kFlushTimeout)); FlushTimeoutOption option( kExpectedEncodedFlushTimeoutOption.view(sizeof(ConfigurationOption))); EXPECT_EQ(kFlushTimeout, option.flush_timeout()); EXPECT_EQ(kExpectedEncodedFlushTimeoutOption, option.Encode()); } TEST_F(ChannelConfigurationTest, UnknownOptionDecodeEncode) { // clang-format off StaticByteBuffer kExpectedEncodedUnknownOption( kUnknownOptionType, 0x02, // Length = 2 0x01, 0x02); // random data // clang-format on ChannelConfiguration::UnknownOption unknown_option( kUnknownOptionType, 2, kExpectedEncodedUnknownOption.view(sizeof(ConfigurationOption))); EXPECT_TRUE( ContainersEqual(unknown_option.Encode(), kExpectedEncodedUnknownOption)); } TEST_F(ChannelConfigurationTest, UnknownOptionHints) { constexpr auto kHintOptionType = static_cast(0x80); constexpr auto kNotHintOptionType = static_cast(0x70); StaticByteBuffer data(0x01); ChannelConfiguration::UnknownOption unknown_option_hint( kHintOptionType, 1, data); EXPECT_TRUE(unknown_option_hint.IsHint()); ChannelConfiguration::UnknownOption unknown_option( kNotHintOptionType, 1, data); EXPECT_FALSE(unknown_option.IsHint()); } TEST_F(ChannelConfigurationTest, OptionsReturnsKnownOptions) { // Empty configuration EXPECT_EQ(0u, ChannelConfiguration().Options().size()); // Single option ChannelConfiguration config; config.set_mtu_option(kMtuOption); auto options0 = config.Options(); // |options0| should not include all options EXPECT_EQ(1u, options0.size()); EXPECT_EQ(OptionType::kMTU, options0[0]->type()); // All options config.set_retransmission_flow_control_option( kRetransmissionAndFlowControlOption); config.set_flush_timeout_option(kFlushTimeoutOption); auto options1 = config.Options(); EXPECT_EQ(3u, options1.size()); const auto OptionsContainsOptionOfType = [](ChannelConfiguration::ConfigurationOptions& options, OptionType type) { return std::find_if(options.begin(), options.end(), [&](auto& option) { return option->type() == type; }) != options.end(); }; constexpr std::array AllOptionTypes = { OptionType::kRetransmissionAndFlowControl, OptionType::kMTU, OptionType::kFlushTimeout}; for (auto& type : AllOptionTypes) { EXPECT_TRUE(OptionsContainsOptionOfType(options1, type)); } // Remove mtu option config.set_mtu_option(std::nullopt); auto options2 = config.Options(); EXPECT_EQ(2u, options2.size()); EXPECT_FALSE(OptionsContainsOptionOfType(options2, OptionType::kMTU)); } TEST_F(ChannelConfigurationTest, MergingConfigurations) { ChannelConfiguration config0; config0.set_mtu_option(kMtuOption); config0.set_retransmission_flow_control_option( kRetransmissionAndFlowControlOption); config0.set_flush_timeout_option(kFlushTimeoutOption); // Test merging options to empty config ChannelConfiguration config1; config1.Merge(config0); EXPECT_EQ(kMtuOption.mtu(), config1.mtu_option()->mtu()); EXPECT_EQ(kRetransmissionAndFlowControlOption.mode(), config1.retransmission_flow_control_option()->mode()); EXPECT_EQ(kFlushTimeoutOption.flush_timeout(), config1.flush_timeout_option()->flush_timeout()); // Test overwriting options constexpr uint16_t kMtu = 96; config0.set_mtu_option(MtuOption(kMtu)); constexpr auto kMode = RetransmissionAndFlowControlMode::kEnhancedRetransmission; config0.set_retransmission_flow_control_option( RetransmissionAndFlowControlOption::MakeEnhancedRetransmissionMode( 0, 0, 0, 0, 0)); constexpr uint16_t kTimeout = 150; config0.set_flush_timeout_option(FlushTimeoutOption(kTimeout)); config1.Merge(config0); EXPECT_EQ(kMtu, config1.mtu_option()->mtu()); EXPECT_EQ(kMode, config1.retransmission_flow_control_option()->mode()); EXPECT_EQ(kTimeout, config1.flush_timeout_option()->flush_timeout()); } TEST_F(ChannelConfigurationTest, MergingUnknownOptionsAppendsThem) { const StaticByteBuffer kUnknownOption0( // code, length, payload 0x70, 0x01, 0x01); const StaticByteBuffer kUnknownOption1( // code, length, payload 0x71, 0x01, 0x01); ChannelConfiguration config0; EXPECT_TRUE(config0.ReadOptions(kUnknownOption0)); EXPECT_EQ(1u, config0.unknown_options().size()); ChannelConfiguration config1; EXPECT_TRUE(config1.ReadOptions(kUnknownOption1)); EXPECT_EQ(1u, config1.unknown_options().size()); config0.Merge(std::move(config1)); EXPECT_EQ(2u, config0.unknown_options().size()); EXPECT_EQ(kUnknownOption0.data()[0], static_cast(config0.unknown_options()[0].type())); EXPECT_EQ(kUnknownOption1.data()[0], static_cast(config0.unknown_options()[1].type())); ChannelConfiguration config2; EXPECT_TRUE(config2.ReadOptions(kUnknownOption0)); EXPECT_EQ(1u, config2.unknown_options().size()); // Merge duplicate of kUnknownOption0. Duplicates should be appended. config0.Merge(std::move(config2)); EXPECT_EQ(3u, config0.unknown_options().size()); EXPECT_EQ(kUnknownOption0.data()[0], static_cast(config0.unknown_options()[0].type())); EXPECT_EQ(kUnknownOption0.data()[0], static_cast(config0.unknown_options()[2].type())); } TEST_F(ChannelConfigurationTest, ReadOptions) { const StaticByteBuffer kOptionBuffer( // MTU Option OptionType::kMTU, MtuOption::kPayloadLength, LowerBits(kMinACLMTU), UpperBits(kMinACLMTU), // Rtx Option OptionType::kRetransmissionAndFlowControl, RetransmissionAndFlowControlOption::kPayloadLength, static_cast(RetransmissionAndFlowControlMode::kRetransmission), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Flush Timeout option (timeout = 200) static_cast(OptionType::kFlushTimeout), FlushTimeoutOption::kPayloadLength, 0xc8, 0x00, // Unknown option, Type = 0x70, Length = 1 0x70, 0x01, 0x03, // Unknown option (hint), Type = 0x80, Length = 0 0x80, 0x00); ChannelConfiguration config; EXPECT_TRUE(config.ReadOptions(kOptionBuffer)); EXPECT_TRUE(config.mtu_option().has_value()); EXPECT_EQ(kMinACLMTU, config.mtu_option()->mtu()); EXPECT_TRUE(config.retransmission_flow_control_option().has_value()); EXPECT_EQ(RetransmissionAndFlowControlMode::kRetransmission, config.retransmission_flow_control_option()->mode()); EXPECT_TRUE(config.flush_timeout_option().has_value()); EXPECT_EQ(200u, config.flush_timeout_option()->flush_timeout()); // Hint should have been dropped EXPECT_EQ(1u, config.unknown_options().size()); auto& option = config.unknown_options().front(); EXPECT_EQ(0x70, static_cast(option.type())); EXPECT_EQ(1u, option.payload().size()); EXPECT_EQ(0x03, option.payload().data()[0]); } TEST_F(ChannelConfigurationTest, ConfigToString) { const StaticByteBuffer kOptionBuffer( // MTU Option static_cast(OptionType::kMTU), MtuOption::kPayloadLength, LowerBits(kMinACLMTU), UpperBits(kMinACLMTU), // Rtx Option static_cast(OptionType::kRetransmissionAndFlowControl), RetransmissionAndFlowControlOption::kPayloadLength, static_cast(RetransmissionAndFlowControlMode::kRetransmission), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Flush Timeout option (timeout = 200) static_cast(OptionType::kFlushTimeout), FlushTimeoutOption::kPayloadLength, 0xc8, 0x00, // Unknown option, Type = 0x70, Length = 1, payload = 0x03 0x70, 0x01, 0x03); ChannelConfiguration config; EXPECT_TRUE(config.ReadOptions(kOptionBuffer)); const std::string kExpected = "{[type: MTU, mtu: 48], " "[type: RtxFlowControl, mode: 1, tx window size: 0, max transmit: 0, rtx " "timeout: 0, monitor " "timeout: 0, max pdu payload size: 0], " "[type: FlushTimeout, flush timeout: 200], " "[type: 0x70, length: 1]}"; EXPECT_EQ(kExpected, config.ToString()); } } // namespace } // namespace bt::l2cap::internal