| // 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/public/pw_bluetooth_sapphire/internal/host/l2cap/bredr_dynamic_channel.h" |
| |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| #include <pw_async/fake_dispatcher_fixture.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_signaling_channel.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" |
| |
| #pragma clang diagnostic ignored "-Wshadow" |
| |
| namespace bt::l2cap::internal { |
| namespace { |
| |
| // TODO(https://fxbug.dev/42056068): Add integration test with FakeChannelTest |
| // and BrEdrSignalingChannel using snooped connection data to verify signaling |
| // channel traffic. |
| |
| constexpr uint16_t kPsm = 0x0001; |
| constexpr uint16_t kInvalidPsm = 0x0002; // Valid PSMs are odd. |
| constexpr ChannelId kLocalCId = 0x0040; |
| constexpr ChannelId kLocalCId2 = 0x0041; |
| constexpr ChannelId kRemoteCId = 0x60a3; |
| constexpr ChannelId kBadCId = 0x003f; // Not a dynamic channel. |
| |
| constexpr ChannelParameters kChannelParams; |
| constexpr ChannelParameters kERTMChannelParams{ |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| std::nullopt, |
| std::nullopt}; |
| |
| // Commands Reject |
| |
| const StaticByteBuffer kRejNotUnderstood( |
| // Reject Reason (Not Understood) |
| 0x00, |
| 0x00); |
| |
| // Connection Requests |
| |
| const StaticByteBuffer kConnReq( |
| // PSM |
| LowerBits(kPsm), |
| UpperBits(kPsm), |
| |
| // Source CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId)); |
| |
| auto MakeConnectionRequest(ChannelId src_id, Psm psm) { |
| return StaticByteBuffer( |
| // PSM |
| LowerBits(psm), |
| UpperBits(psm), |
| |
| // Source CID |
| LowerBits(src_id), |
| UpperBits(src_id)); |
| } |
| |
| const StaticByteBuffer kInboundConnReq( |
| // PSM |
| LowerBits(kPsm), |
| UpperBits(kPsm), |
| |
| // Source CID |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId)); |
| |
| const StaticByteBuffer kInboundInvalidPsmConnReq( |
| // PSM |
| LowerBits(kInvalidPsm), |
| UpperBits(kInvalidPsm), |
| |
| // Source CID |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId)); |
| |
| const StaticByteBuffer kInboundBadCIdConnReq( |
| // PSM |
| LowerBits(kPsm), |
| UpperBits(kPsm), |
| |
| // Source CID |
| LowerBits(kBadCId), |
| UpperBits(kBadCId)); |
| |
| // Connection Responses |
| |
| const StaticByteBuffer kPendingConnRsp( |
| // Destination CID |
| 0x00, |
| 0x00, |
| |
| // Source CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Result (Pending) |
| 0x01, |
| 0x00, |
| |
| // Status (Authorization Pending) |
| 0x02, |
| 0x00); |
| |
| const StaticByteBuffer kPendingConnRspWithId( |
| // Destination CID (Wrong endianness but valid) |
| UpperBits(kRemoteCId), |
| LowerBits(kRemoteCId), |
| |
| // Source CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Result (Pending) |
| 0x01, |
| 0x00, |
| |
| // Status (Authorization Pending) |
| 0x02, |
| 0x00); |
| |
| auto MakeConnectionResponseWithResultPending(ChannelId src_id, |
| ChannelId dst_id) { |
| return StaticByteBuffer( |
| // Destination CID |
| LowerBits(dst_id), |
| UpperBits(dst_id), |
| |
| // Source CID |
| LowerBits(src_id), |
| UpperBits(src_id), |
| |
| // Result (Pending) |
| 0x01, |
| 0x00, |
| |
| // Status (Authorization Pending) |
| 0x02, |
| 0x00); |
| } |
| |
| const StaticByteBuffer kOkConnRsp( |
| // Destination CID |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId), |
| |
| // Source CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Result (Successful) |
| 0x00, |
| 0x00, |
| |
| // Status (No further information available) |
| 0x00, |
| 0x00); |
| |
| auto MakeConnectionResponse(ChannelId src_id, ChannelId dst_id) { |
| return StaticByteBuffer( |
| // Destination CID |
| LowerBits(dst_id), |
| UpperBits(dst_id), |
| |
| // Source CID |
| LowerBits(src_id), |
| UpperBits(src_id), |
| |
| // Result (Successful) |
| 0x00, |
| 0x00, |
| |
| // Status (No further information available) |
| 0x00, |
| 0x00); |
| } |
| |
| const StaticByteBuffer kInvalidConnRsp( |
| // Destination CID (Not a dynamic channel ID) |
| LowerBits(kBadCId), |
| UpperBits(kBadCId), |
| |
| // Source CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Result (Successful) |
| 0x00, |
| 0x00, |
| |
| // Status (No further information available) |
| 0x00, |
| 0x00); |
| |
| const StaticByteBuffer kRejectConnRsp( |
| // Destination CID (Invalid) |
| LowerBits(kInvalidChannelId), |
| UpperBits(kInvalidChannelId), |
| |
| // Source CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Result (No resources) |
| 0x04, |
| 0x00, |
| |
| // Status (No further information available) |
| 0x00, |
| 0x00); |
| |
| const StaticByteBuffer kInboundOkConnRsp( |
| // Destination CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Source CID |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId), |
| |
| // Result (Successful) |
| 0x00, |
| 0x00, |
| |
| // Status (No further information available) |
| 0x00, |
| 0x00); |
| |
| const StaticByteBuffer kOutboundSourceCIdAlreadyAllocatedConnRsp( |
| // Destination CID (Invalid) |
| 0x00, |
| 0x00, |
| |
| // Source CID (Invalid) |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId), |
| |
| // Result (Connection refused - source CID already allocated) |
| 0x07, |
| 0x00, |
| |
| // Status (No further information available) |
| 0x00, |
| 0x00); |
| |
| const StaticByteBuffer kInboundBadPsmConnRsp( |
| // Destination CID (Invalid) |
| 0x00, |
| 0x00, |
| |
| // Source CID |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId), |
| |
| // Result (PSM Not Supported) |
| 0x02, |
| 0x00, |
| |
| // Status (No further information available) |
| 0x00, |
| 0x00); |
| |
| const StaticByteBuffer kInboundBadCIdConnRsp( |
| // Destination CID (Invalid) |
| 0x00, |
| 0x00, |
| |
| // Source CID |
| LowerBits(kBadCId), |
| UpperBits(kBadCId), |
| |
| // Result (Invalid Source CID) |
| 0x06, |
| 0x00, |
| |
| // Status (No further information available) |
| 0x00, |
| 0x00); |
| |
| // Disconnection Requests |
| |
| const StaticByteBuffer kDisconReq( |
| // Destination CID |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId), |
| |
| // Source CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId)); |
| |
| const StaticByteBuffer kInboundDisconReq( |
| // Destination CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Source CID |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId)); |
| |
| // Disconnection Responses |
| |
| const ByteBuffer& kInboundDisconRsp = kInboundDisconReq; |
| |
| const ByteBuffer& kDisconRsp = kDisconReq; |
| |
| // Configuration Requests |
| |
| auto MakeConfigReqWithMtuAndRfc(ChannelId dest_cid, |
| uint16_t mtu, |
| RetransmissionAndFlowControlMode mode, |
| uint8_t tx_window, |
| uint8_t max_transmit, |
| uint16_t retransmission_timeout, |
| uint16_t monitor_timeout, |
| uint16_t mps) { |
| return StaticByteBuffer( |
| // Destination CID |
| LowerBits(dest_cid), |
| UpperBits(dest_cid), |
| |
| // Flags |
| 0x00, |
| 0x00, |
| |
| // MTU option (Type, Length, MTU value) |
| 0x01, |
| 0x02, |
| LowerBits(mtu), |
| UpperBits(mtu), |
| |
| // Retransmission & Flow Control option (Type, Length = 9, mode, unused |
| // fields) |
| 0x04, |
| 0x09, |
| static_cast<uint8_t>(mode), |
| tx_window, |
| max_transmit, |
| LowerBits(retransmission_timeout), |
| UpperBits(retransmission_timeout), |
| LowerBits(monitor_timeout), |
| UpperBits(monitor_timeout), |
| LowerBits(mps), |
| UpperBits(mps)); |
| } |
| |
| auto MakeConfigReqWithMtu(ChannelId dest_cid, |
| uint16_t mtu = kMaxMTU, |
| uint16_t flags = 0x0000) { |
| return StaticByteBuffer( |
| // Destination CID |
| LowerBits(dest_cid), |
| UpperBits(dest_cid), |
| |
| // Flags |
| LowerBits(flags), |
| UpperBits(flags), |
| |
| // MTU option (Type, Length, MTU value) |
| 0x01, |
| 0x02, |
| LowerBits(mtu), |
| UpperBits(mtu)); |
| } |
| |
| const ByteBuffer& kOutboundConfigReq = MakeConfigReqWithMtu(kRemoteCId); |
| |
| const ByteBuffer& kOutboundConfigReqWithErtm = MakeConfigReqWithMtuAndRfc( |
| kRemoteCId, |
| kMaxInboundPduPayloadSize, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| kErtmMaxUnackedInboundFrames, |
| kErtmMaxInboundRetransmissions, |
| 0, |
| 0, |
| kMaxInboundPduPayloadSize); |
| |
| const StaticByteBuffer kInboundConfigReq( |
| // Destination CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Flags |
| 0x00, |
| 0x00); |
| |
| // Use plausible ERTM parameters that do not necessarily match values in |
| // production. See Core Spec v5.0 Vol 3, Part A, Sec 5.4 for meanings. |
| constexpr uint8_t kErtmNFramesInTxWindow = 32; |
| constexpr uint8_t kErtmMaxTransmissions = 8; |
| constexpr uint16_t kMaxTxPduPayloadSize = 1024; |
| |
| const StaticByteBuffer kInboundConfigReqWithERTM( |
| // Destination CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Flags |
| 0x00, |
| 0x00, |
| |
| // Retransmission & Flow Control option (Type, Length = 9, mode = ERTM, |
| // arbitrary parameters) |
| 0x04, |
| 0x09, |
| 0x03, |
| kErtmNFramesInTxWindow, |
| kErtmMaxTransmissions, |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| LowerBits(kMaxTxPduPayloadSize), |
| UpperBits(kMaxTxPduPayloadSize), |
| |
| // FCS Option (Type, Length = 1) - turn off which we should accept and |
| // ignore |
| 0x05, |
| 0x01, |
| static_cast<uint8_t>(FcsType::kNoFcs)); |
| |
| // Configuration Responses |
| |
| auto MakeEmptyConfigRsp( |
| ChannelId src_id, |
| ConfigurationResult result = ConfigurationResult::kSuccess, |
| uint16_t flags = 0x0000) { |
| return StaticByteBuffer( |
| // Source CID |
| LowerBits(src_id), |
| UpperBits(src_id), |
| |
| // Flags |
| LowerBits(flags), |
| UpperBits(flags), |
| |
| // Result |
| LowerBits(static_cast<uint16_t>(result)), |
| UpperBits(static_cast<uint16_t>(result))); |
| } |
| |
| const ByteBuffer& kOutboundEmptyContinuationConfigRsp = MakeEmptyConfigRsp( |
| kRemoteCId, ConfigurationResult::kSuccess, kConfigurationContinuation); |
| |
| const ByteBuffer& kInboundEmptyConfigRsp = MakeEmptyConfigRsp(kLocalCId); |
| |
| const ByteBuffer& kUnknownIdConfigRsp = MakeEmptyConfigRsp(kBadCId); |
| |
| const ByteBuffer& kOutboundEmptyPendingConfigRsp = |
| MakeEmptyConfigRsp(kRemoteCId, ConfigurationResult::kPending); |
| |
| const ByteBuffer& kInboundEmptyPendingConfigRsp = |
| MakeEmptyConfigRsp(kLocalCId, ConfigurationResult::kPending); |
| |
| auto MakeConfigRspWithMtu( |
| ChannelId source_cid, |
| uint16_t mtu, |
| ConfigurationResult result = ConfigurationResult::kSuccess, |
| uint16_t flags = 0x0000) { |
| return StaticByteBuffer( |
| // Source CID |
| LowerBits(source_cid), |
| UpperBits(source_cid), |
| |
| // Flags |
| LowerBits(flags), |
| UpperBits(flags), |
| |
| // Result |
| LowerBits(static_cast<uint16_t>(result)), |
| UpperBits(static_cast<uint16_t>(result)), |
| |
| // MTU option (Type, Length, MTU value) |
| 0x01, |
| 0x02, |
| LowerBits(mtu), |
| UpperBits(mtu)); |
| } |
| |
| const ByteBuffer& kOutboundOkConfigRsp = |
| MakeConfigRspWithMtu(kRemoteCId, kDefaultMTU); |
| |
| auto MakeConfigRspWithRfc(ChannelId source_cid, |
| ConfigurationResult result, |
| RetransmissionAndFlowControlMode mode, |
| uint8_t tx_window, |
| uint8_t max_transmit, |
| uint16_t retransmission_timeout, |
| uint16_t monitor_timeout, |
| uint16_t mps) { |
| return StaticByteBuffer( |
| // Source CID |
| LowerBits(source_cid), |
| UpperBits(source_cid), |
| |
| // Flags |
| 0x00, |
| 0x00, |
| |
| // Result |
| LowerBits(static_cast<uint16_t>(result)), |
| UpperBits(static_cast<uint16_t>(result)), |
| |
| // Retransmission & Flow Control option (Type, Length: 9, mode, unused |
| // parameters) |
| 0x04, |
| 0x09, |
| static_cast<uint8_t>(mode), |
| tx_window, |
| max_transmit, |
| LowerBits(retransmission_timeout), |
| UpperBits(retransmission_timeout), |
| LowerBits(monitor_timeout), |
| UpperBits(monitor_timeout), |
| LowerBits(mps), |
| UpperBits(mps)); |
| } |
| |
| const ByteBuffer& kInboundUnacceptableParamsWithRfcBasicConfigRsp = |
| MakeConfigRspWithRfc(kLocalCId, |
| ConfigurationResult::kUnacceptableParameters, |
| RetransmissionAndFlowControlMode::kBasic, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0); |
| |
| const ByteBuffer& kOutboundUnacceptableParamsWithRfcBasicConfigRsp = |
| MakeConfigRspWithRfc(kRemoteCId, |
| ConfigurationResult::kUnacceptableParameters, |
| RetransmissionAndFlowControlMode::kBasic, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0); |
| |
| const ByteBuffer& kOutboundUnacceptableParamsWithRfcERTMConfigRsp = |
| MakeConfigRspWithRfc( |
| kRemoteCId, |
| ConfigurationResult::kUnacceptableParameters, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0); |
| |
| auto MakeConfigRspWithMtuAndRfc(ChannelId source_cid, |
| ConfigurationResult result, |
| RetransmissionAndFlowControlMode mode, |
| uint16_t mtu, |
| uint8_t tx_window, |
| uint8_t max_transmit, |
| uint16_t retransmission_timeout, |
| uint16_t monitor_timeout, |
| uint16_t mps) { |
| return StaticByteBuffer( |
| // Source CID |
| LowerBits(source_cid), |
| UpperBits(source_cid), |
| |
| // Flags |
| 0x00, |
| 0x00, |
| |
| // Result |
| LowerBits(static_cast<uint16_t>(result)), |
| UpperBits(static_cast<uint16_t>(result)), |
| |
| // MTU option (Type, Length, MTU value) |
| 0x01, |
| 0x02, |
| LowerBits(mtu), |
| UpperBits(mtu), |
| |
| // Retransmission & Flow Control option (Type, Length = 9, mode, ERTM |
| // fields) |
| 0x04, |
| 0x09, |
| static_cast<uint8_t>(mode), |
| tx_window, |
| max_transmit, |
| LowerBits(retransmission_timeout), |
| UpperBits(retransmission_timeout), |
| LowerBits(monitor_timeout), |
| UpperBits(monitor_timeout), |
| LowerBits(mps), |
| UpperBits(mps)); |
| } |
| |
| // Corresponds to kInboundConfigReqWithERTM |
| const ByteBuffer& kOutboundOkConfigRspWithErtm = MakeConfigRspWithMtuAndRfc( |
| kRemoteCId, |
| ConfigurationResult::kSuccess, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| kDefaultMTU, |
| kErtmNFramesInTxWindow, |
| kErtmMaxTransmissions, |
| 2000, |
| 12000, |
| kMaxTxPduPayloadSize); |
| |
| // Information Requests |
| |
| auto MakeInfoReq(InformationType info_type) { |
| const auto type = static_cast<uint16_t>(info_type); |
| return StaticByteBuffer(LowerBits(type), UpperBits(type)); |
| } |
| |
| const ByteBuffer& kExtendedFeaturesInfoReq = |
| MakeInfoReq(InformationType::kExtendedFeaturesSupported); |
| |
| // Information Responses |
| |
| auto MakeExtendedFeaturesInfoRsp( |
| InformationResult result = InformationResult::kSuccess, |
| ExtendedFeatures features = 0u) { |
| const auto type = |
| static_cast<uint16_t>(InformationType::kExtendedFeaturesSupported); |
| const auto res = static_cast<uint16_t>(result); |
| const auto features_bytes = ToBytes(features); |
| return StaticByteBuffer( |
| // Type |
| LowerBits(type), |
| UpperBits(type), |
| |
| // Result |
| LowerBits(res), |
| UpperBits(res), |
| |
| // Data |
| features_bytes[0], |
| features_bytes[1], |
| features_bytes[2], |
| features_bytes[3]); |
| } |
| |
| const ByteBuffer& kExtendedFeaturesInfoRsp = |
| MakeExtendedFeaturesInfoRsp(InformationResult::kSuccess); |
| const ByteBuffer& kExtendedFeaturesInfoRspWithERTM = |
| MakeExtendedFeaturesInfoRsp(InformationResult::kSuccess, |
| kExtendedFeaturesBitEnhancedRetransmission); |
| |
| class BrEdrDynamicChannelTest : public pw::async::test::FakeDispatcherFixture { |
| public: |
| BrEdrDynamicChannelTest() = default; |
| ~BrEdrDynamicChannelTest() override = default; |
| |
| protected: |
| // Import types for brevity. |
| using DynamicChannelCallback = DynamicChannelRegistry::DynamicChannelCallback; |
| using ServiceRequestCallback = DynamicChannelRegistry::ServiceRequestCallback; |
| |
| // TestLoopFixture overrides |
| void SetUp() override { |
| channel_close_cb_ = nullptr; |
| service_request_cb_ = nullptr; |
| signaling_channel_ = |
| std::make_unique<testing::FakeSignalingChannel>(dispatcher()); |
| |
| ext_info_transaction_id_ = EXPECT_OUTBOUND_REQ( |
| *sig(), kInformationRequest, kExtendedFeaturesInfoReq.view()); |
| // TODO(https://fxbug.dev/42141538): Make these tests not rely on strict |
| // ordering of channel IDs. |
| registry_ = std::make_unique<BrEdrDynamicChannelRegistry>( |
| sig(), |
| fit::bind_member<&BrEdrDynamicChannelTest::OnChannelClose>(this), |
| fit::bind_member<&BrEdrDynamicChannelTest::OnServiceRequest>(this), |
| /*random_channel_ids=*/false); |
| } |
| |
| void TearDown() override { |
| RunUntilIdle(); |
| registry_ = nullptr; |
| signaling_channel_ = nullptr; |
| service_request_cb_ = nullptr; |
| channel_close_cb_ = nullptr; |
| } |
| |
| testing::FakeSignalingChannel* sig() const { |
| return signaling_channel_.get(); |
| } |
| |
| BrEdrDynamicChannelRegistry* registry() const { return registry_.get(); } |
| |
| void set_channel_close_cb(DynamicChannelCallback close_cb) { |
| channel_close_cb_ = std::move(close_cb); |
| } |
| |
| void set_service_request_cb(ServiceRequestCallback service_request_cb) { |
| service_request_cb_ = std::move(service_request_cb); |
| } |
| |
| testing::FakeSignalingChannel::TransactionId ext_info_transaction_id() { |
| return ext_info_transaction_id_; |
| } |
| |
| private: |
| void OnChannelClose(const DynamicChannel* channel) { |
| if (channel_close_cb_) { |
| channel_close_cb_(channel); |
| } |
| } |
| |
| // Default to rejecting all service requests if no test callback is set. |
| std::optional<DynamicChannelRegistry::ServiceInfo> OnServiceRequest(Psm psm) { |
| if (service_request_cb_) { |
| return service_request_cb_(psm); |
| } |
| return std::nullopt; |
| } |
| |
| DynamicChannelCallback channel_close_cb_; |
| ServiceRequestCallback service_request_cb_; |
| std::unique_ptr<testing::FakeSignalingChannel> signaling_channel_; |
| std::unique_ptr<BrEdrDynamicChannelRegistry> registry_; |
| testing::FakeSignalingChannel::TransactionId ext_info_transaction_id_; |
| |
| BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(BrEdrDynamicChannelTest); |
| }; |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| InboundConnectionResponseReusingChannelIdCausesInboundChannelFailure) { |
| // make successful connection |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_TRUE(chan->IsConnected()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| } |
| open_cb_count++; |
| }; |
| |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto chan) { |
| EXPECT_TRUE(chan); |
| close_cb_count++; |
| }); |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| |
| // simulate inbound request to make new connection using already allocated |
| // remote CId |
| sig()->ReceiveExpect(kConnectionRequest, |
| kInboundConnReq, |
| kOutboundSourceCIdAlreadyAllocatedConnRsp); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| PeerConnectionResponseReusingChannelIdCausesOutboundChannelFailure) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| // make successful connection |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_TRUE(chan->IsConnected()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| } |
| open_cb_count++; |
| }; |
| |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto chan) { |
| EXPECT_TRUE(chan); |
| close_cb_count++; |
| }); |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| |
| // peer responds with already allocated remote CID |
| const auto kConnReq2 = MakeConnectionRequest(kLocalCId2, kPsm); |
| const auto kOkConnRspSamePeerCId = |
| MakeConnectionResponse(kLocalCId2, kRemoteCId); |
| |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConnectionRequest, |
| kConnReq2.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRspSamePeerCId.view()}); |
| |
| auto channel = BrEdrDynamicChannel::MakeOutbound( |
| registry(), sig(), kPsm, kLocalCId2, kChannelParams, false); |
| EXPECT_FALSE(channel->IsConnected()); |
| EXPECT_FALSE(channel->IsOpen()); |
| |
| int close_cb_count2 = 0; |
| set_channel_close_cb([&close_cb_count2](auto) { close_cb_count2++; }); |
| |
| int open_cb_count2 = 0; |
| channel->Open([&open_cb_count2] { open_cb_count2++; }); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_FALSE(channel->IsConnected()); |
| EXPECT_FALSE(channel->IsOpen()); |
| EXPECT_EQ(open_cb_count2, 1); |
| EXPECT_EQ(close_cb_count2, 0); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F( |
| BrEdrDynamicChannelTest, |
| PeerPendingConnectionResponseReusingChannelIdCausesOutboundChannelFailure) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| // make successful connection |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_TRUE(chan->IsConnected()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| } |
| open_cb_count++; |
| }; |
| |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto chan) { |
| EXPECT_TRUE(chan); |
| close_cb_count++; |
| }); |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| |
| // peer responds with already allocated remote CID |
| const auto kConnReq2 = MakeConnectionRequest(kLocalCId2, kPsm); |
| const auto kOkConnRspWithResultPendingSamePeerCId = |
| MakeConnectionResponseWithResultPending(kLocalCId2, kRemoteCId); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq2.view(), |
| {SignalingChannel::Status::kSuccess, |
| kOkConnRspWithResultPendingSamePeerCId.view()}); |
| |
| int open_cb_count2 = 0; |
| int close_cb_count2 = 0; |
| set_channel_close_cb([&close_cb_count2](auto) { close_cb_count2++; }); |
| |
| registry()->OpenOutbound( |
| kPsm, kChannelParams, [&open_cb_count2](auto) { open_cb_count2++; }); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(open_cb_count2, 1); |
| // A failed-to-open channel should not invoke the close callback. |
| EXPECT_EQ(close_cb_count2, 0); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F( |
| BrEdrDynamicChannelTest, |
| PeerConnectionResponseWithSameRemoteChannelIdAsPeerPendingConnectionResponseSucceeds) { |
| const auto kOkPendingConnRsp = |
| MakeConnectionResponseWithResultPending(kLocalCId, kRemoteCId); |
| auto conn_rsp_id = EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkPendingConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_TRUE(chan->IsConnected()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| } |
| open_cb_count++; |
| }; |
| |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto) { close_cb_count++; }); |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveResponses( |
| conn_rsp_id, {{SignalingChannel::Status::kSuccess, kOkConnRsp.view()}})); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(open_cb_count, 1); |
| EXPECT_EQ(close_cb_count, 0); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, ChannelDeletedBeforeConnectionResponse) { |
| auto conn_id = |
| EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view()); |
| |
| // Build channel and operate it directly to be able to delete it. |
| auto channel = BrEdrDynamicChannel::MakeOutbound( |
| registry(), sig(), kPsm, kLocalCId, kChannelParams, false); |
| ASSERT_TRUE(channel); |
| |
| int open_result_cb_count = 0; |
| channel->Open([&open_result_cb_count] { open_result_cb_count++; }); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| channel = nullptr; |
| RETURN_IF_FATAL( |
| sig()->ReceiveResponses(conn_id, |
| {{SignalingChannel::Status::kSuccess, |
| kOutboundEmptyPendingConfigRsp.view()}})); |
| |
| EXPECT_EQ(0, open_result_cb_count); |
| |
| // No disconnection transaction expected because the channel isn't actually |
| // owned by the registry. |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, FailConnectChannel) { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kRejectConnRsp.view()}); |
| |
| // Build channel and operate it directly to be able to inspect it in the |
| // connected but not open state. |
| auto channel = BrEdrDynamicChannel::MakeOutbound( |
| registry(), sig(), kPsm, kLocalCId, kChannelParams, false); |
| EXPECT_FALSE(channel->IsConnected()); |
| EXPECT_FALSE(channel->IsOpen()); |
| EXPECT_EQ(kLocalCId, channel->local_cid()); |
| |
| int open_result_cb_count = 0; |
| auto open_result_cb = [&open_result_cb_count, &channel] { |
| if (open_result_cb_count == 0) { |
| EXPECT_FALSE(channel->IsConnected()); |
| EXPECT_FALSE(channel->IsOpen()); |
| } |
| open_result_cb_count++; |
| }; |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto) { close_cb_count++; }); |
| |
| channel->Open(std::move(open_result_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(1, open_result_cb_count); |
| EXPECT_FALSE(channel->IsConnected()); |
| EXPECT_FALSE(channel->IsOpen()); |
| EXPECT_EQ(kInvalidChannelId, channel->remote_cid()); |
| |
| // A failed-to-open channel should not invoke the close callback. |
| EXPECT_EQ(0, close_cb_count); |
| |
| // No disconnection transaction expected because the channel isn't actually |
| // owned by the registry. |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, ConnectChannelFailConfig) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kReject, kRejNotUnderstood.view()}); |
| |
| // Build channel and operate it directly to be able to inspect it in the |
| // connected but not open state. |
| auto channel = BrEdrDynamicChannel::MakeOutbound( |
| registry(), sig(), kPsm, kLocalCId, kChannelParams, false); |
| EXPECT_FALSE(channel->IsConnected()); |
| EXPECT_FALSE(channel->IsOpen()); |
| EXPECT_EQ(kLocalCId, channel->local_cid()); |
| |
| int open_result_cb_count = 0; |
| auto open_result_cb = [&open_result_cb_count, &channel] { |
| if (open_result_cb_count == 0) { |
| EXPECT_TRUE(channel->IsConnected()); |
| EXPECT_FALSE(channel->IsOpen()); |
| } |
| open_result_cb_count++; |
| }; |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto) { close_cb_count++; }); |
| |
| channel->Open(std::move(open_result_cb)); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel->IsConnected()); |
| |
| // A connected channel should have a valid remote channel ID. |
| EXPECT_EQ(kRemoteCId, channel->remote_cid()); |
| |
| EXPECT_FALSE(channel->IsOpen()); |
| EXPECT_EQ(1, open_result_cb_count); |
| |
| // A failed-to-open channel should not invoke the close callback. |
| EXPECT_EQ(0, close_cb_count); |
| |
| // No disconnection transaction expected because the channel isn't actually |
| // owned by the registry. |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, ConnectChannelFailInvalidResponse) { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kInvalidConnRsp.view()}); |
| |
| // Build channel and operate it directly to be able to inspect it in the |
| // connected but not open state. |
| auto channel = BrEdrDynamicChannel::MakeOutbound( |
| registry(), sig(), kPsm, kLocalCId, kChannelParams, false); |
| |
| int open_result_cb_count = 0; |
| auto open_result_cb = [&open_result_cb_count, &channel] { |
| if (open_result_cb_count == 0) { |
| EXPECT_FALSE(channel->IsConnected()); |
| EXPECT_FALSE(channel->IsOpen()); |
| } |
| open_result_cb_count++; |
| }; |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto) { close_cb_count++; }); |
| |
| channel->Open(std::move(open_result_cb)); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_FALSE(channel->IsConnected()); |
| EXPECT_FALSE(channel->IsOpen()); |
| EXPECT_EQ(1, open_result_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| |
| // No disconnection transaction expected because the channel isn't actually |
| // owned by the registry. |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| OutboundFailsAfterRtxExpiryForConnectionResponse) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kTimeOut, BufferView()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| if (open_cb_count == 0) { |
| EXPECT_FALSE(chan); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| // FakeSignalingChannel doesn't need to be clocked in order to simulate a |
| // timeout. |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| OutboundFailsAfterErtxExpiryForConnectionResponse) { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kPendingConnRsp.view()}, |
| {SignalingChannel::Status::kTimeOut, BufferView()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| if (open_cb_count == 0) { |
| EXPECT_FALSE(chan); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| // FakeSignalingChannel doesn't need to be clocked in order to simulate a |
| // timeout. |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| // In L2CAP Test Spec v5.0.2, this is L2CAP/COS/CED/BV-08-C [Disconnect on |
| // Timeout]. |
| TEST_F(BrEdrDynamicChannelTest, |
| OutboundFailsAndDisconnectsAfterRtxExpiryForConfigurationResponse) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kTimeOut, BufferView()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kTimeOut, BufferView()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| if (open_cb_count == 0) { |
| EXPECT_FALSE(chan); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| // FakeSignalingChannel doesn't need to be clocked in order to simulate a |
| // timeout. |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| // Simulate a ERTX timer expiry after a Configuration Response with "Pending" |
| // status. |
| TEST_F(BrEdrDynamicChannelTest, |
| OutboundFailsAndDisconnectsAfterErtxExpiryForConfigurationResponse) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundEmptyPendingConfigRsp.view()}, |
| {SignalingChannel::Status::kTimeOut, BufferView()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kTimeOut, BufferView()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| if (open_cb_count == 0) { |
| EXPECT_FALSE(chan); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| // FakeSignalingChannel doesn't need to be clocked in order to simulate a |
| // timeout. |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, OpenAndLocalCloseChannel) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_TRUE(chan->IsConnected()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| EXPECT_EQ(RetransmissionAndFlowControlMode::kBasic, chan->info().mode); |
| } |
| open_cb_count++; |
| }; |
| |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto chan) { |
| EXPECT_TRUE(chan); |
| close_cb_count++; |
| }); |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| |
| // Local channel closure shouldn't trigger the close callback. |
| EXPECT_EQ(0, close_cb_count); |
| |
| // Callback passed to CloseChannel should be called nonetheless. |
| EXPECT_TRUE(channel_close_cb_called); |
| |
| // Repeated closure of the same channel should not have any effect. |
| channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| EXPECT_TRUE(channel_close_cb_called); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, OpenAndRemoteCloseChannel) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { open_cb_count++; }; |
| |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto chan) { |
| ASSERT_TRUE(chan); |
| EXPECT_FALSE(chan->IsOpen()); |
| EXPECT_FALSE(chan->IsConnected()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| close_cb_count++; |
| }); |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kDisconnectionRequest, kInboundDisconReq, kInboundDisconRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| |
| // Remote channel closure should trigger the close callback. |
| EXPECT_EQ(1, close_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, OpenChannelWithPendingConn) { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kPendingConnRsp.view()}, |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| registry()->OpenOutbound(kPsm, kChannelParams, [&open_cb_count](auto chan) { |
| open_cb_count++; |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, OpenChannelMismatchConnRsp) { |
| // The first Connection Response (pending) has a different ID than the final |
| // Connection Response (success). |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kPendingConnRspWithId.view()}, |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| registry()->OpenOutbound(kPsm, kChannelParams, [&open_cb_count](auto chan) { |
| open_cb_count++; |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, OpenChannelConfigPending) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, |
| kOutboundEmptyPendingConfigRsp.view()}, |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| registry()->OpenOutbound(kPsm, kChannelParams, [&open_cb_count](auto chan) { |
| open_cb_count++; |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, OpenChannelRemoteDisconnectWhileConfiguring) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| auto config_id = EXPECT_OUTBOUND_REQ( |
| *sig(), kConfigurationRequest, kOutboundConfigReq.view()); |
| |
| int open_cb_count = 0; |
| registry()->OpenOutbound(kPsm, kChannelParams, [&open_cb_count](auto chan) { |
| open_cb_count++; |
| EXPECT_FALSE(chan); |
| }); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kDisconnectionRequest, kInboundDisconReq, kInboundDisconRsp)); |
| |
| // Response handler should return false ("no more responses") when called, so |
| // trigger single responses rather than a set of two. |
| RETURN_IF_FATAL( |
| sig()->ReceiveResponses(config_id, |
| {{SignalingChannel::Status::kSuccess, |
| kOutboundEmptyPendingConfigRsp.view()}})); |
| RETURN_IF_FATAL(sig()->ReceiveResponses( |
| config_id, |
| {{SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}})); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, ChannelIdNotReusedUntilDisconnectionCompletes) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| auto disconn_id = |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view()); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| ASSERT_TRUE(chan); |
| open_cb_count++; |
| }; |
| |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto chan) { |
| EXPECT_TRUE(chan); |
| close_cb_count++; |
| }); |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| // Complete opening the channel. |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| // Disconnection Response hasn't been received yet so the second channel |
| // should use a different channel ID. |
| const StaticByteBuffer kSecondChannelConnReq( |
| // PSM |
| LowerBits(kPsm), |
| UpperBits(kPsm), |
| |
| // Source CID |
| LowerBits(kLocalCId + 1), |
| UpperBits(kLocalCId + 1)); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kSecondChannelConnReq.view()); |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| // CloseChannel callback hasn't been called either. |
| EXPECT_FALSE(channel_close_cb_called); |
| |
| // Complete the disconnection on the first channel. |
| RETURN_IF_FATAL(sig()->ReceiveResponses( |
| disconn_id, {{SignalingChannel::Status::kSuccess, kDisconRsp.view()}})); |
| |
| EXPECT_TRUE(channel_close_cb_called); |
| |
| // Now the first channel ID gets reused. |
| EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view()); |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| } |
| |
| // DisconnectDoneCallback is load-bearing as a signal for the registry to delete |
| // a channel's state and recycle its channel ID, so test that it's called even |
| // when the peer doesn't actually send a Disconnect Response. |
| TEST_F(BrEdrDynamicChannelTest, |
| DisconnectDoneCallbackCalledAfterDisconnectResponseTimeOut) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| // Build channel and operate it directly to be able to disconnect it. |
| auto channel = BrEdrDynamicChannel::MakeOutbound( |
| registry(), sig(), kPsm, kLocalCId, kChannelParams, false); |
| ASSERT_TRUE(channel); |
| channel->Open([] {}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_TRUE(channel->IsConnected()); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kTimeOut, BufferView()}); |
| |
| bool disconnect_done_cb_called = false; |
| channel->Disconnect( |
| [&disconnect_done_cb_called] { disconnect_done_cb_called = true; }); |
| EXPECT_FALSE(channel->IsConnected()); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_TRUE(disconnect_done_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, OpenChannelConfigWrongId) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kUnknownIdConfigRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| registry()->OpenOutbound(kPsm, kChannelParams, [&open_cb_count](auto chan) { |
| open_cb_count++; |
| EXPECT_FALSE(chan); |
| }); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpectRejectInvalidChannelId( |
| kConfigurationRequest, kInboundConfigReq, kLocalCId, kInvalidChannelId)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, InboundConnectionOk) { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| DynamicChannelCallback open_cb = [&open_cb_count](auto chan) { |
| open_cb_count++; |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kPsm, chan->psm()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }; |
| |
| int service_request_cb_count = 0; |
| auto service_request_cb = [&service_request_cb_count, |
| open_cb = std::move(open_cb)](Psm psm) mutable |
| -> std::optional<DynamicChannelRegistry::ServiceInfo> { |
| service_request_cb_count++; |
| EXPECT_EQ(kPsm, psm); |
| if (psm == kPsm) { |
| return DynamicChannelRegistry::ServiceInfo(kChannelParams, |
| open_cb.share()); |
| } |
| return std::nullopt; |
| }; |
| |
| set_service_request_cb(std::move(service_request_cb)); |
| |
| int close_cb_count = 0; |
| set_channel_close_cb([&close_cb_count](auto chan) { close_cb_count++; }); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConnectionRequest, kInboundConnReq, kInboundOkConnRsp)); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(1, service_request_cb_count); |
| EXPECT_EQ(0, open_cb_count); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, service_request_cb_count); |
| EXPECT_EQ(1, open_cb_count); |
| |
| registry()->CloseChannel(kLocalCId, [] {}); |
| EXPECT_EQ(0, close_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| InboundConnectionRemoteDisconnectWhileConfiguring) { |
| auto config_id = EXPECT_OUTBOUND_REQ( |
| *sig(), kConfigurationRequest, kOutboundConfigReq.view()); |
| |
| int open_cb_count = 0; |
| DynamicChannelCallback open_cb = [&open_cb_count](auto chan) { |
| open_cb_count++; |
| FAIL() << "Failed-to-open inbound channels shouldn't trip open callback"; |
| }; |
| |
| int service_request_cb_count = 0; |
| auto service_request_cb = [&service_request_cb_count, |
| open_cb = std::move(open_cb)](Psm psm) mutable |
| -> std::optional<DynamicChannelRegistry::ServiceInfo> { |
| service_request_cb_count++; |
| EXPECT_EQ(kPsm, psm); |
| if (psm == kPsm) { |
| return DynamicChannelRegistry::ServiceInfo(kChannelParams, |
| open_cb.share()); |
| } |
| return std::nullopt; |
| }; |
| |
| set_service_request_cb(std::move(service_request_cb)); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConnectionRequest, kInboundConnReq, kInboundOkConnRsp)); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1, service_request_cb_count); |
| EXPECT_EQ(0, open_cb_count); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kDisconnectionRequest, kInboundDisconReq, kInboundDisconRsp)); |
| |
| // Drop response received after the channel is disconnected. |
| RETURN_IF_FATAL(sig()->ReceiveResponses( |
| config_id, |
| {{SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}})); |
| |
| EXPECT_EQ(1, service_request_cb_count); |
| |
| // Channel that failed to open shouldn't have triggered channel open callback. |
| EXPECT_EQ(0, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, InboundConnectionInvalidPsm) { |
| auto service_request_cb = |
| [](Psm psm) -> std::optional<DynamicChannelRegistry::ServiceInfo> { |
| // Write user code that accepts the invalid PSM, but control flow may not |
| // reach here. |
| EXPECT_EQ(kInvalidPsm, psm); |
| if (psm == kInvalidPsm) { |
| return DynamicChannelRegistry::ServiceInfo( |
| kChannelParams, [](auto /*unused*/) { |
| FAIL() << "Channel should fail to open for PSM"; |
| }); |
| } |
| return std::nullopt; |
| }; |
| |
| set_service_request_cb(std::move(service_request_cb)); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConnectionRequest, kInboundInvalidPsmConnReq, kInboundBadPsmConnRsp)); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, InboundConnectionUnsupportedPsm) { |
| int service_request_cb_count = 0; |
| auto service_request_cb = |
| [&service_request_cb_count]( |
| Psm psm) -> std::optional<DynamicChannelRegistry::ServiceInfo> { |
| service_request_cb_count++; |
| EXPECT_EQ(kPsm, psm); |
| |
| // Reject the service request. |
| return std::nullopt; |
| }; |
| |
| set_service_request_cb(std::move(service_request_cb)); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConnectionRequest, kInboundConnReq, kInboundBadPsmConnRsp)); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1, service_request_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, InboundConnectionInvalidSrcCId) { |
| auto service_request_cb = |
| [](Psm psm) -> std::optional<DynamicChannelRegistry::ServiceInfo> { |
| // Control flow may not reach here. |
| EXPECT_EQ(kPsm, psm); |
| if (psm == kPsm) { |
| return DynamicChannelRegistry::ServiceInfo( |
| kChannelParams, [](auto /*unused*/) { |
| FAIL() << "Channel from src_cid should fail to open"; |
| }); |
| } |
| return std::nullopt; |
| }; |
| |
| set_service_request_cb(std::move(service_request_cb)); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConnectionRequest, kInboundBadCIdConnReq, kInboundBadCIdConnRsp)); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, RejectConfigReqWithUnknownOptions) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| size_t open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| EXPECT_FALSE(chan); |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| const StaticByteBuffer kInboundConfigReqUnknownOption( |
| // Destination CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| |
| // Flags |
| 0x00, |
| 0x00, |
| |
| // Unknown Option: Type, Length, Data |
| 0x70, |
| 0x01, |
| 0x02); |
| |
| const StaticByteBuffer kOutboundConfigRspUnknownOption( |
| // Source CID |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId), |
| |
| // Flags |
| 0x00, |
| 0x00, |
| |
| // Result (Failure - unknown options) |
| 0x03, |
| 0x00, |
| |
| // Unknown Option: Type, Length, Data |
| 0x70, |
| 0x01, |
| 0x02); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqUnknownOption, |
| kOutboundConfigRspUnknownOption)); |
| |
| EXPECT_EQ(0u, open_cb_count); |
| |
| RunUntilIdle(); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| ClampErtmChannelInfoMaxTxSduSizeToMaxPduPayloadSize) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| const auto kPeerMps = 1024; |
| const auto kPeerMtu = kPeerMps + 1; |
| bool channel_opened = false; |
| auto open_cb = [&](auto chan) { |
| channel_opened = true; |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| |
| // Note that max SDU size is the peer's MPS because it's smaller than its |
| // MTU. |
| EXPECT_EQ(kPeerMps, chan->info().max_tx_sdu_size); |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| const auto kInboundConfigReq = MakeConfigReqWithMtuAndRfc( |
| kLocalCId, |
| kPeerMtu, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| kErtmNFramesInTxWindow, |
| kErtmMaxTransmissions, |
| 0, |
| 0, |
| kPeerMps); |
| const auto kOutboundConfigRsp = MakeConfigRspWithMtuAndRfc( |
| kRemoteCId, |
| ConfigurationResult::kSuccess, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| kPeerMtu, |
| kErtmNFramesInTxWindow, |
| kErtmMaxTransmissions, |
| 2000, |
| 12000, |
| kPeerMps); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundConfigRsp)); |
| |
| EXPECT_TRUE(channel_opened); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| struct ReceiveMtuTestParams { |
| std::optional<uint16_t> request_mtu; |
| uint16_t response_mtu; |
| ConfigurationResult response_status; |
| }; |
| class ReceivedMtuTest |
| : public BrEdrDynamicChannelTest, |
| public ::testing::WithParamInterface<ReceiveMtuTestParams> {}; |
| |
| TEST_P(ReceivedMtuTest, ResponseMtuAndStatus) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| bool channel_opened = false; |
| auto open_cb = [&](auto chan) { |
| channel_opened = true; |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(chan->info().max_tx_sdu_size, GetParam().response_mtu); |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| const auto kOutboundConfigRsp = MakeConfigRspWithMtu( |
| kRemoteCId, GetParam().response_mtu, GetParam().response_status); |
| |
| if (GetParam().request_mtu) { |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, |
| MakeConfigReqWithMtu(kLocalCId, *GetParam().request_mtu), |
| kOutboundConfigRsp)); |
| } else { |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundConfigRsp)); |
| } |
| |
| EXPECT_EQ(GetParam().response_status == ConfigurationResult::kSuccess, |
| channel_opened); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| BrEdrDynamicChannelTest, |
| ReceivedMtuTest, |
| ::testing::Values( |
| ReceiveMtuTestParams{ |
| std::nullopt, kDefaultMTU, ConfigurationResult::kSuccess}, |
| ReceiveMtuTestParams{ |
| kMinACLMTU, kMinACLMTU, ConfigurationResult::kSuccess}, |
| ReceiveMtuTestParams{kMinACLMTU - 1, |
| kMinACLMTU, |
| ConfigurationResult::kUnacceptableParameters}, |
| ReceiveMtuTestParams{ |
| kDefaultMTU + 1, kDefaultMTU + 1, ConfigurationResult::kSuccess})); |
| |
| class ConfigRspWithMtuTest : public BrEdrDynamicChannelTest, |
| public ::testing::WithParamInterface< |
| std::optional<uint16_t> /*response mtu*/> {}; |
| |
| TEST_P(ConfigRspWithMtuTest, ConfiguredLocalMtu) { |
| const auto kExpectedConfiguredLocalMtu = GetParam() ? *GetParam() : kMaxMTU; |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| |
| const auto kInboundConfigRspWithParamMtu = |
| MakeConfigRspWithMtu(kLocalCId, GetParam() ? *GetParam() : 0); |
| if (GetParam()) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundConfigRspWithParamMtu.view()}); |
| } else { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| } |
| |
| size_t open_cb_count = 0; |
| auto open_cb = [&](auto chan) { |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kExpectedConfiguredLocalMtu, chan->info().max_rx_sdu_size); |
| open_cb_count++; |
| }; |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1u, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_P(ConfigRspWithMtuTest, ConfiguredLocalMtuWithPendingRsp) { |
| const auto kExpectedConfiguredLocalMtu = GetParam() ? *GetParam() : kMaxMTU; |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| |
| const auto kInboundPendingConfigRspWithMtu = MakeConfigRspWithMtu( |
| kLocalCId, GetParam() ? *GetParam() : 0, ConfigurationResult::kPending); |
| if (GetParam()) { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundPendingConfigRspWithMtu.view()}, |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| } else { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundEmptyPendingConfigRsp.view()}, |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| } |
| |
| size_t open_cb_count = 0; |
| auto open_cb = [&](auto chan) { |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kExpectedConfiguredLocalMtu, chan->info().max_rx_sdu_size); |
| open_cb_count++; |
| }; |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1u, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(BrEdrDynamicChannelTest, |
| ConfigRspWithMtuTest, |
| ::testing::Values(std::nullopt, kMinACLMTU)); |
| |
| TEST_F(BrEdrDynamicChannelTest, RespondsToInboundExtendedFeaturesRequest) { |
| const auto kExpectedExtendedFeatures = |
| kExtendedFeaturesBitFixedChannels | kExtendedFeaturesBitFCSOption | |
| kExtendedFeaturesBitEnhancedRetransmission; |
| const auto kExpectedExtendedFeaturesInfoRsp = MakeExtendedFeaturesInfoRsp( |
| InformationResult::kSuccess, kExpectedExtendedFeatures); |
| sig()->ReceiveExpect(kInformationRequest, |
| kExtendedFeaturesInfoReq, |
| kExpectedExtendedFeaturesInfoRsp); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, ExtendedFeaturesResponseSaved) { |
| const auto kExpectedExtendedFeatures = |
| kExtendedFeaturesBitFixedChannels | kExtendedFeaturesBitFCSOption | |
| kExtendedFeaturesBitEnhancedRetransmission; |
| const auto kInfoRsp = MakeExtendedFeaturesInfoRsp(InformationResult::kSuccess, |
| kExpectedExtendedFeatures); |
| |
| EXPECT_FALSE(registry()->extended_features()); |
| |
| sig()->ReceiveResponses( |
| ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, kInfoRsp.view()}}); |
| EXPECT_TRUE(registry()->extended_features()); |
| EXPECT_EQ(kExpectedExtendedFeatures, *registry()->extended_features()); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, ExtendedFeaturesResponseInvalidFailureResult) { |
| constexpr auto kResult = static_cast<InformationResult>(0xFFFF); |
| const auto kInfoRsp = MakeExtendedFeaturesInfoRsp(kResult); |
| |
| EXPECT_FALSE(registry()->extended_features()); |
| |
| sig()->ReceiveResponses( |
| ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, kInfoRsp.view()}}); |
| EXPECT_FALSE(registry()->extended_features()); |
| } |
| |
| class InformationResultTest |
| : public BrEdrDynamicChannelTest, |
| public ::testing::WithParamInterface<InformationResult> {}; |
| |
| TEST_P( |
| InformationResultTest, |
| ERTMChannelWaitsForExtendedFeaturesResultBeforeFallingBackToBasicModeAndStartingConfigFlow) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| |
| size_t open_cb_count = 0; |
| auto open_cb = [&open_cb_count](auto chan) { |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| // Config request should not be sent. |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| const auto kExtendedFeaturesInfoRsp = MakeExtendedFeaturesInfoRsp(GetParam()); |
| sig()->ReceiveResponses( |
| ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, kExtendedFeaturesInfoRsp.view()}}); |
| |
| RunUntilIdle(); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| // Config should have been sent, so channel should be open. |
| EXPECT_EQ(1u, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| BrEdrDynamicChannelTest, |
| InformationResultTest, |
| ::testing::Values(InformationResult::kSuccess, |
| InformationResult::kNotSupported, |
| static_cast<InformationResult>(0xFFFF))); |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| ERTChannelDoesNotSendConfigReqBeforeConnRspReceived) { |
| auto conn_id = |
| EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view(), {}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| // Channel will be notified that extended features received. |
| sig()->ReceiveResponses( |
| ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, kExtendedFeaturesInfoRsp.view()}}); |
| |
| // Config request should not be sent before connection response received. |
| RunUntilIdle(); |
| |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| sig()->ReceiveResponses( |
| conn_id, {{SignalingChannel::Status::kSuccess, kOkConnRsp.view()}}); |
| RunUntilIdle(); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, SendAndReceiveERTMConfigReq) { |
| constexpr uint16_t kPreferredMtu = kDefaultMTU + 1; |
| const auto kExpectedOutboundConfigReq = MakeConfigReqWithMtuAndRfc( |
| kRemoteCId, |
| kPreferredMtu, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| kErtmMaxUnackedInboundFrames, |
| kErtmMaxInboundRetransmissions, |
| 0, |
| 0, |
| kMaxInboundPduPayloadSize); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kExpectedOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [kPreferredMtu, &open_cb_count](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| |
| // Check values of ChannelInfo fields. |
| EXPECT_EQ(RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| chan->info().mode); |
| |
| // Receive capability even under ERTM is based on MTU option, not on the |
| // MPS in R&FC option. |
| EXPECT_EQ(kPreferredMtu, chan->info().max_rx_sdu_size); |
| |
| // Inbound request has no MTU option, so the peer's receive capability is |
| // the default. |
| EXPECT_EQ(kDefaultMTU, chan->info().max_tx_sdu_size); |
| |
| // These values should match the contents of kInboundConfigReqWithERTM. |
| EXPECT_EQ(kErtmNFramesInTxWindow, chan->info().n_frames_in_tx_window); |
| EXPECT_EQ(kErtmMaxTransmissions, chan->info().max_transmissions); |
| EXPECT_EQ(kMaxTxPduPayloadSize, chan->info().max_tx_pdu_payload_size); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound( |
| kPsm, |
| {RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| kPreferredMtu, |
| std::nullopt}, |
| std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithERTM, |
| kOutboundOkConfigRspWithErtm)); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| // When the peer rejects ERTM with the result Unacceptable Parameters and the |
| // R&FC option specifying basic mode, the local device should send a new request |
| // with basic mode. When the peer then requests basic mode, it should be |
| // accepted. PTS: L2CAP/CMC/BV-03-C |
| TEST_F(BrEdrDynamicChannelTest, PeerRejectsERTM) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundUnacceptableParamsWithRfcBasicConfigRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| // Local device that prefers ERTM will renegotiate channel mode to basic mode |
| // after peer negotiates basic mode and rejects ERTM. PTS: L2CAP/CMC/BV-07-C |
| TEST_F(BrEdrDynamicChannelTest, |
| RenegotiateChannelModeAfterPeerRequestsBasicModeAndRejectsERTM) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| auto config_req_id = EXPECT_OUTBOUND_REQ( |
| *sig(), kConfigurationRequest, kOutboundConfigReqWithErtm.view()); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RunUntilIdle(); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| RunUntilIdle(); |
| |
| // Peer requests basic mode. |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| RunUntilIdle(); |
| |
| // New config request requesting basic mode should be sent in response to |
| // unacceptable params response. |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| sig()->ReceiveResponses( |
| config_req_id, |
| {{SignalingChannel::Status::kSuccess, |
| kInboundUnacceptableParamsWithRfcBasicConfigRsp.view()}}); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| // The local device should configure basic mode if peer does not indicate |
| // support for ERTM when it is preferred. PTS: L2CAP/CMC/BV-10-C |
| TEST_F(BrEdrDynamicChannelTest, |
| PreferredModeIsERTMButERTMIsNotInPeerFeatureMask) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| // Receive features mask without ERTM bit set. |
| sig()->ReceiveResponses( |
| ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, kExtendedFeaturesInfoRsp.view()}}); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, RejectERTMRequestWhenPreferredModeIsBasic) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| // Peer requests ERTM. Local device should reject with unacceptable params. |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithERTM, |
| kOutboundUnacceptableParamsWithRfcBasicConfigRsp)); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| // Core Spec v5.1, Vol 3, Part A, Sec 5.4: |
| // If the mode in the remote device's negative Configuration Response does |
| // not match the mode in the remote device's Configuration Request then the |
| // local device shall disconnect the channel. |
| // |
| // Inbound config request received BEFORE outbound config request: |
| // <- ConfigurationRequest (with ERTM) |
| // -> ConfigurationResponse (Ok) |
| // -> ConfigurationRequest (with ERTM) |
| // <- ConfigurationResponse (Unacceptable, with Basic) |
| TEST_F( |
| BrEdrDynamicChannelTest, |
| DisconnectWhenInboundConfigReqReceivedBeforeOutboundConfigReqSentModeInInboundUnacceptableParamsConfigRspDoesNotMatchPeerConfigReq) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundUnacceptableParamsWithRfcBasicConfigRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| EXPECT_FALSE(chan); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| // Receive inbound config request. |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithERTM, |
| kOutboundOkConfigRspWithErtm)); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| // Send outbound config request. |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| // Same as above, but inbound config request received AFTER outbound |
| // configuration request: |
| // -> ConfigurationRequest (with ERTM) |
| // <- ConfigurationRequest (with ERTM) |
| // -> ConfigurationResponse (Ok) |
| // <- ConfigurationResponse (Unacceptable, with Basic) |
| TEST_F( |
| BrEdrDynamicChannelTest, |
| DisconnectWhenInboundConfigReqReceivedAfterOutboundConfigReqSentAndModeInInboundUnacceptableParamsConfigRspDoesNotMatchPeerConfigReq) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| const auto outbound_config_req_id = EXPECT_OUTBOUND_REQ( |
| *sig(), kConfigurationRequest, kOutboundConfigReqWithErtm.view()); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| EXPECT_FALSE(chan); |
| } |
| open_cb_count++; |
| }; |
| |
| ChannelParameters params; |
| params.mode = RetransmissionAndFlowControlMode::kEnhancedRetransmission; |
| registry()->OpenOutbound(kPsm, params, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| // Send outbound config request. |
| RunUntilIdle(); |
| |
| // Receive inbound config request. |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithERTM, |
| kOutboundOkConfigRspWithErtm)); |
| |
| sig()->ReceiveResponses( |
| outbound_config_req_id, |
| {{SignalingChannel::Status::kSuccess, |
| kInboundUnacceptableParamsWithRfcBasicConfigRsp.view()}}); |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| DisconnectAfterReceivingTwoConfigRequestsWithoutDesiredMode) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| EXPECT_FALSE(chan); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithERTM, |
| kOutboundUnacceptableParamsWithRfcBasicConfigRsp)); |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithERTM, |
| kOutboundUnacceptableParamsWithRfcBasicConfigRsp)); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, RetryWhenPeerRejectsConfigReqWithBasicMode) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundUnacceptableParamsWithRfcBasicConfigRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| EXPECT_FALSE(chan); |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(0, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| RetryNTimesWhenPeerRejectsConfigReqWithBasicMode) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| uint8_t retry_limit = 2; |
| for (int i = 0; i < retry_limit; i++) { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundUnacceptableParamsWithRfcBasicConfigRsp.view()}); |
| } |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| ASSERT_TRUE(chan == nullptr); |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| RetryNTimesWhenPeerRejectsERTMConfigReqWithBasicMode) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundUnacceptableParamsWithRfcBasicConfigRsp.view()}); |
| uint8_t retry_limit = 2; |
| for (int i = 0; i < retry_limit; i++) { |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, |
| kInboundUnacceptableParamsWithRfcBasicConfigRsp.view()}); |
| } |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| ASSERT_TRUE(chan == nullptr); |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| SendUnacceptableParamsResponseWhenPeerRequestsUnsupportedChannelMode) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| // Retransmission mode is not supported. |
| const auto kInboundConfigReqWithRetransmissionMode = |
| MakeConfigReqWithMtuAndRfc( |
| kLocalCId, |
| kMaxMTU, |
| RetransmissionAndFlowControlMode::kRetransmission, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0); |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithRetransmissionMode, |
| kOutboundUnacceptableParamsWithRfcBasicConfigRsp)); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F( |
| BrEdrDynamicChannelTest, |
| SendUnacceptableParamsResponseWhenPeerRequestsUnsupportedChannelModeAndSupportsErtm) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), kConfigurationRequest, kOutboundConfigReqWithErtm.view(), {}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| // Retransmission mode is not supported. |
| const auto kInboundConfigReqWithRetransmissionMode = |
| MakeConfigReqWithMtuAndRfc( |
| kLocalCId, |
| kMaxMTU, |
| RetransmissionAndFlowControlMode::kRetransmission, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0); |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithRetransmissionMode, |
| kOutboundUnacceptableParamsWithRfcERTMConfigRsp)); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| SendUnacceptableParamsResponseWhenPeerRequestErtmWithZeroTxWindow) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| constexpr uint8_t kMaxTransmit = 31; |
| constexpr auto kMps = kMaxTxPduPayloadSize; |
| |
| // TxWindow of zero is out of range. |
| const auto kInboundConfigReqWithZeroTxWindow = MakeConfigReqWithMtuAndRfc( |
| kLocalCId, |
| kDefaultMTU, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| /*tx_window=*/0, |
| /*max_transmit=*/kMaxTransmit, |
| /*retransmission_timeout=*/0, |
| /*monitor_timeout=*/0, |
| /*mps=*/kMps); |
| const auto kOutboundConfigRsp = MakeConfigRspWithRfc( |
| kRemoteCId, |
| ConfigurationResult::kUnacceptableParameters, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| /*tx_window=*/1, |
| /*max_transmit=*/kMaxTransmit, |
| /*retransmission_timeout=*/0, |
| /*monitor_timeout=*/0, |
| /*mps=*/kMps); |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithZeroTxWindow, |
| kOutboundConfigRsp)); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| SendUnacceptableParamsResponseWhenPeerRequestErtmWithOversizeTxWindow) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| constexpr uint8_t kMaxTransmit = 31; |
| constexpr auto kMps = kMaxTxPduPayloadSize; |
| |
| // TxWindow of 200 is out of range. |
| const auto kInboundConfigReqWithOversizeTxWindow = MakeConfigReqWithMtuAndRfc( |
| kLocalCId, |
| kDefaultMTU, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| /*tx_window=*/200, |
| /*max_transmit=*/kMaxTransmit, |
| /*retransmission_timeout=*/0, |
| /*monitor_timeout=*/0, |
| /*mps=*/kMps); |
| const auto kOutboundConfigRsp = MakeConfigRspWithRfc( |
| kRemoteCId, |
| ConfigurationResult::kUnacceptableParameters, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| /*tx_window=*/63, |
| /*max_transmit=*/kMaxTransmit, |
| /*retransmission_timeout=*/0, |
| /*monitor_timeout=*/0, |
| /*mps=*/kMps); |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithOversizeTxWindow, |
| kOutboundConfigRsp)); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| SendUnacceptableParamsResponseWhenPeerRequestErtmWithUndersizeMps) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| constexpr uint8_t kMaxTransmit = 31; |
| constexpr uint8_t kTxWindow = kErtmMaxUnackedInboundFrames; |
| |
| // MPS of 16 would not be able to fit a 48-byte (minimum MTU) SDU without |
| // segmentation. |
| const auto kInboundConfigReqWithUndersizeMps = MakeConfigReqWithMtuAndRfc( |
| kLocalCId, |
| kDefaultMTU, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| /*tx_window=*/kTxWindow, |
| /*max_transmit=*/kMaxTransmit, |
| /*retransmission_timeout=*/0, |
| /*monitor_timeout=*/0, |
| /*mps=*/16); |
| const auto kOutboundConfigRsp = MakeConfigRspWithRfc( |
| kRemoteCId, |
| ConfigurationResult::kUnacceptableParameters, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| /*tx_window=*/kTxWindow, |
| /*max_transmit=*/kMaxTransmit, |
| /*retransmission_timeout=*/0, |
| /*monitor_timeout=*/0, |
| /*mps=*/kMinACLMTU); |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithUndersizeMps, |
| kOutboundConfigRsp)); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| // Local config with ERTM incorrectly accepted by peer, then peer requests basic |
| // mode which the local device must accept. These modes are incompatible, so the |
| // local device should default to Basic Mode. |
| TEST_F(BrEdrDynamicChannelTest, |
| OpenBasicModeChannelAfterPeerAcceptsErtmThenPeerRequestsBasicMode) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(RetransmissionAndFlowControlMode::kBasic, chan->info().mode); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| // Request ERTM. |
| RunUntilIdle(); |
| |
| // Peer requests basic mode. |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| // Disconnect |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| // Same as above, but the peer sends its positive response after sending its |
| // Basic Mode request. |
| TEST_F(BrEdrDynamicChannelTest, |
| OpenBasicModeChannelAfterPeerRequestsBasicModeThenPeerAcceptsErtm) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&open_cb_count](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(RetransmissionAndFlowControlMode::kBasic, chan->info().mode); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| // Peer requests basic mode. |
| RETURN_IF_FATAL(sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| // Local device will request ERTM. |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| // Request ERTM & Disconnect |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, MtuChannelParameterSentInConfigReq) { |
| constexpr uint16_t kPreferredMtu = kDefaultMTU + 1; |
| const auto kExpectedOutboundConfigReq = |
| MakeConfigReqWithMtu(kRemoteCId, kPreferredMtu); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kExpectedOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kPreferredMtu, chan->info().max_rx_sdu_size); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound( |
| kPsm, |
| {RetransmissionAndFlowControlMode::kBasic, kPreferredMtu, std::nullopt}, |
| open_cb); |
| RunUntilIdle(); |
| |
| sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp); |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, UseMinMtuWhenMtuChannelParameterIsBelowMin) { |
| constexpr uint16_t kMtu = kMinACLMTU - 1; |
| const auto kExpectedOutboundConfigReq = |
| MakeConfigReqWithMtu(kRemoteCId, kMinACLMTU); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kExpectedOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kMinACLMTU, chan->info().max_rx_sdu_size); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound( |
| kPsm, |
| {RetransmissionAndFlowControlMode::kBasic, kMtu, std::nullopt}, |
| open_cb); |
| RunUntilIdle(); |
| |
| sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp); |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| UseMaxPduPayloadSizeWhenMtuChannelParameterExceedsItWithErtm) { |
| constexpr uint16_t kPreferredMtu = kMaxInboundPduPayloadSize + 1; |
| const auto kExpectedOutboundConfigReq = MakeConfigReqWithMtuAndRfc( |
| kRemoteCId, |
| kMaxInboundPduPayloadSize, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| kErtmMaxUnackedInboundFrames, |
| kErtmMaxInboundRetransmissions, |
| 0, |
| 0, |
| kMaxInboundPduPayloadSize); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kExpectedOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kMaxInboundPduPayloadSize, chan->info().max_rx_sdu_size); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound( |
| kPsm, |
| {RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| kPreferredMtu, |
| std::nullopt}, |
| std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithERTM, |
| kOutboundOkConfigRspWithErtm)); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| BasicModeChannelReportsChannelInfoWithBasicModeAndSduCapacities) { |
| constexpr uint16_t kPreferredMtu = kDefaultMTU + 1; |
| const auto kExpectedOutboundConfigReq = |
| MakeConfigReqWithMtu(kRemoteCId, kPreferredMtu); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kExpectedOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| constexpr uint16_t kPeerMtu = kDefaultMTU + 2; |
| const auto kInboundConfigReq = MakeConfigReqWithMtu(kLocalCId, kPeerMtu); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&](const DynamicChannel* chan) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(RetransmissionAndFlowControlMode::kBasic, chan->info().mode); |
| EXPECT_EQ(kPreferredMtu, chan->info().max_rx_sdu_size); |
| EXPECT_EQ(kPeerMtu, chan->info().max_tx_sdu_size); |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound( |
| kPsm, |
| {RetransmissionAndFlowControlMode::kBasic, kPreferredMtu, std::nullopt}, |
| open_cb); |
| RunUntilIdle(); |
| |
| const ByteBuffer& kExpectedOutboundOkConfigRsp = |
| MakeConfigRspWithMtu(kRemoteCId, kPeerMtu); |
| sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReq, kExpectedOutboundOkConfigRsp); |
| RunUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| TEST_F(BrEdrDynamicChannelTest, |
| Receive2ConfigReqsWithContinuationFlagInFirstReq) { |
| constexpr uint16_t kTxMtu = kMinACLMTU; |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReqWithErtm.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| const auto kInboundConfigReq0 = |
| MakeConfigReqWithMtu(kLocalCId, kTxMtu, kConfigurationContinuation); |
| const auto kOutboundConfigRsp1 = MakeConfigRspWithMtuAndRfc( |
| kRemoteCId, |
| ConfigurationResult::kSuccess, |
| RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| kTxMtu, |
| kErtmNFramesInTxWindow, |
| kErtmMaxTransmissions, |
| 2000, |
| 12000, |
| kMaxTxPduPayloadSize); |
| |
| size_t open_cb_count = 0; |
| auto open_cb = [&](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kTxMtu, chan->info().max_tx_sdu_size); |
| EXPECT_EQ(RetransmissionAndFlowControlMode::kEnhancedRetransmission, |
| chan->info().mode); |
| } |
| open_cb_count++; |
| }; |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, open_cb); |
| RunUntilIdle(); |
| |
| sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReq0, |
| kOutboundEmptyContinuationConfigRsp); |
| RunUntilIdle(); |
| EXPECT_EQ(0u, open_cb_count); |
| |
| sig()->ReceiveExpect( |
| kConfigurationRequest, kInboundConfigReqWithERTM, kOutboundConfigRsp1); |
| RunUntilIdle(); |
| EXPECT_EQ(1u, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kDisconnectionRequest, |
| kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| bool channel_close_cb_called = false; |
| registry()->CloseChannel(kLocalCId, [&] { channel_close_cb_called = true; }); |
| RETURN_IF_FATAL(RunUntilIdle()); |
| EXPECT_TRUE(channel_close_cb_called); |
| } |
| |
| // The unknown options from both configuration requests should be included when |
| // responding with the "unknown options" result. |
| TEST_F( |
| BrEdrDynamicChannelTest, |
| Receive2ConfigReqsWithContinuationFlagInFirstReqAndUnknownOptionInBothReqs) { |
| EXPECT_OUTBOUND_REQ(*sig(), |
| kConnectionRequest, |
| kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ( |
| *sig(), |
| kConfigurationRequest, |
| kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| constexpr uint8_t kUnknownOption0Type = 0x70; |
| constexpr uint8_t kUnknownOption1Type = 0x71; |
| const StaticByteBuffer kInboundConfigReq0( |
| // Destination CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| // Flags (C = 1) |
| 0x01, |
| 0x00, |
| // Unknown Option |
| kUnknownOption0Type, |
| 0x01, |
| 0x00); |
| const StaticByteBuffer kInboundConfigReq1( |
| // Destination CID |
| LowerBits(kLocalCId), |
| UpperBits(kLocalCId), |
| // Flags (C = 0) |
| 0x00, |
| 0x00, |
| // Unknown Option |
| kUnknownOption1Type, |
| 0x01, |
| 0x00); |
| |
| const StaticByteBuffer kOutboundUnknownOptionsConfigRsp( |
| // Source CID |
| LowerBits(kRemoteCId), |
| UpperBits(kRemoteCId), |
| // Flags (C = 0) |
| 0x00, |
| 0x00, |
| // Result |
| LowerBits(static_cast<uint16_t>(ConfigurationResult::kUnknownOptions)), |
| UpperBits(static_cast<uint16_t>(ConfigurationResult::kUnknownOptions)), |
| // Unknown Options |
| kUnknownOption0Type, |
| 0x01, |
| 0x00, |
| kUnknownOption1Type, |
| 0x01, |
| 0x00); |
| |
| size_t open_cb_count = 0; |
| auto open_cb = [&](const DynamicChannel* chan) { |
| EXPECT_FALSE(chan); |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, open_cb); |
| RunUntilIdle(); |
| |
| sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReq0, |
| kOutboundEmptyContinuationConfigRsp); |
| RunUntilIdle(); |
| EXPECT_EQ(0u, open_cb_count); |
| |
| sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReq1, |
| kOutboundUnknownOptionsConfigRsp); |
| RunUntilIdle(); |
| EXPECT_EQ(0u, open_cb_count); |
| } |
| |
| } // namespace |
| } // namespace bt::l2cap::internal |