| // 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/l2cap/bredr_dynamic_channel.h" |
| |
| #include <lib/async/cpp/task.h> |
| |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| #include "lib/gtest/test_loop_fixture.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/hci.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_signaling_channel.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap.h" |
| |
| namespace bt { |
| namespace l2cap { |
| namespace internal { |
| namespace { |
| |
| // TODO(NET-1093): 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{ChannelMode::kEnhancedRetransmission, std::nullopt}; |
| |
| // Commands Reject |
| |
| const ByteBuffer& kRejNotUnderstood = CreateStaticByteBuffer( |
| // Reject Reason (Not Understood) |
| 0x00, 0x00); |
| |
| // Connection Requests |
| |
| const ByteBuffer& kConnReq = CreateStaticByteBuffer( |
| // PSM |
| LowerBits(kPsm), UpperBits(kPsm), |
| |
| // Source CID |
| LowerBits(kLocalCId), UpperBits(kLocalCId)); |
| |
| auto MakeConnectionRequest(ChannelId src_id, PSM psm) { |
| return CreateStaticByteBuffer( |
| // PSM |
| LowerBits(psm), UpperBits(psm), |
| |
| // Source CID |
| LowerBits(src_id), UpperBits(src_id)); |
| } |
| |
| const ByteBuffer& kInboundConnReq = CreateStaticByteBuffer( |
| // PSM |
| LowerBits(kPsm), UpperBits(kPsm), |
| |
| // Source CID |
| LowerBits(kRemoteCId), UpperBits(kRemoteCId)); |
| |
| const ByteBuffer& kInboundInvalidPsmConnReq = CreateStaticByteBuffer( |
| // PSM |
| LowerBits(kInvalidPsm), UpperBits(kInvalidPsm), |
| |
| // Source CID |
| LowerBits(kRemoteCId), UpperBits(kRemoteCId)); |
| |
| const ByteBuffer& kInboundBadCIdConnReq = CreateStaticByteBuffer( |
| // PSM |
| LowerBits(kPsm), UpperBits(kPsm), |
| |
| // Source CID |
| LowerBits(kBadCId), UpperBits(kBadCId)); |
| |
| // Connection Responses |
| |
| const ByteBuffer& kPendingConnRsp = CreateStaticByteBuffer( |
| // Destination CID |
| 0x00, 0x00, |
| |
| // Source CID |
| LowerBits(kLocalCId), UpperBits(kLocalCId), |
| |
| // Result (Pending) |
| 0x01, 0x00, |
| |
| // Status (Authorization Pending) |
| 0x02, 0x00); |
| |
| const ByteBuffer& kPendingConnRspWithId = CreateStaticByteBuffer( |
| // 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 CreateStaticByteBuffer( |
| // 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 ByteBuffer& kOkConnRsp = CreateStaticByteBuffer( |
| // 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 CreateStaticByteBuffer( |
| // 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 ByteBuffer& kInvalidConnRsp = CreateStaticByteBuffer( |
| // 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 ByteBuffer& kRejectConnRsp = CreateStaticByteBuffer( |
| // 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 ByteBuffer& kInboundOkConnRsp = CreateStaticByteBuffer( |
| // Destination CID |
| LowerBits(kLocalCId), UpperBits(kLocalCId), |
| |
| // Source CID |
| LowerBits(kRemoteCId), UpperBits(kRemoteCId), |
| |
| // Result (Successful) |
| 0x00, 0x00, |
| |
| // Status (No further information available) |
| 0x00, 0x00); |
| |
| const ByteBuffer& kOutboundSourceCIdAlreadyAllocatedConnRsp = CreateStaticByteBuffer( |
| // 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 ByteBuffer& kInboundBadPsmConnRsp = CreateStaticByteBuffer( |
| // 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 ByteBuffer& kInboundBadCIdConnRsp = CreateStaticByteBuffer( |
| // 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 ByteBuffer& kDisconReq = CreateStaticByteBuffer( |
| // Destination CID |
| LowerBits(kRemoteCId), UpperBits(kRemoteCId), |
| |
| // Source CID |
| LowerBits(kLocalCId), UpperBits(kLocalCId)); |
| |
| const ByteBuffer& kInboundDisconReq = CreateStaticByteBuffer( |
| // 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, ChannelMode 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, kMaxMTU, ChannelMode::kEnhancedRetransmission, kErtmMaxUnackedInboundFrames, |
| kErtmMaxInboundRetransmissions, 0, 0, kMaxInboundPduPayloadSize); |
| |
| const ByteBuffer& kInboundConfigReq = CreateStaticByteBuffer( |
| // Destination CID |
| LowerBits(kLocalCId), UpperBits(kLocalCId), |
| |
| // Flags |
| 0x00, 0x00); |
| |
| const ByteBuffer& kInboundConfigReq2 = CreateStaticByteBuffer( |
| // Destination CID |
| LowerBits(kLocalCId2), UpperBits(kLocalCId2), |
| |
| // 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 ByteBuffer& kInboundConfigReqWithERTM = CreateStaticByteBuffer( |
| // Destination CID |
| LowerBits(kLocalCId), UpperBits(kLocalCId), |
| |
| // Flags |
| 0x00, 0x00, |
| |
| // Retransmission & Flow Control option (Type, Length = 9, mode = ERTM, dummy parameters) |
| 0x04, 0x09, 0x03, kErtmNFramesInTxWindow, kErtmMaxTransmissions, 0x00, 0x00, 0x00, 0x00, |
| LowerBits(kMaxTxPduPayloadSize), UpperBits(kMaxTxPduPayloadSize)); |
| |
| // Configuration Responses |
| |
| auto MakeEmptyConfigRsp(ChannelId src_id, |
| ConfigurationResult result = ConfigurationResult::kSuccess, |
| uint16_t flags = 0x0000) { |
| return CreateStaticByteBuffer( |
| // 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 CreateStaticByteBuffer( |
| // 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, ChannelMode mode, |
| uint8_t tx_window, uint8_t max_transmit, uint16_t retransmission_timeout, |
| uint16_t monitor_timeout, uint16_t mps) { |
| return CreateStaticByteBuffer( |
| // 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, ChannelMode::kBasic, 0, 0, 0, 0, 0); |
| |
| const ByteBuffer& kOutboundUnacceptableParamsWithRfcBasicConfigRsp = MakeConfigRspWithRfc( |
| kRemoteCId, ConfigurationResult::kUnacceptableParameters, ChannelMode::kBasic, 0, 0, 0, 0, 0); |
| |
| const ByteBuffer& kOutboundUnacceptableParamsWithRfcERTMConfigRsp = |
| MakeConfigRspWithRfc(kRemoteCId, ConfigurationResult::kUnacceptableParameters, |
| ChannelMode::kEnhancedRetransmission, 0, 0, 0, 0, 0); |
| |
| auto MakeConfigRspWithMtuAndRfc(ChannelId source_cid, ConfigurationResult result, ChannelMode 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, ChannelMode::kEnhancedRetransmission, kDefaultMTU, |
| kErtmNFramesInTxWindow, kErtmMaxTransmissions, 2000, 12000, kMaxTxPduPayloadSize); |
| |
| // Information Requests |
| |
| auto MakeInfoReq(InformationType info_type) { |
| const auto type = static_cast<uint16_t>(info_type); |
| return CreateStaticByteBuffer(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 CreateStaticByteBuffer( |
| // 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 L2CAP_BrEdrDynamicChannelTest : public ::gtest::TestLoopFixture { |
| public: |
| L2CAP_BrEdrDynamicChannelTest() = default; |
| ~L2CAP_BrEdrDynamicChannelTest() override = default; |
| |
| protected: |
| // Import types for brevity. |
| using DynamicChannelCallback = DynamicChannelRegistry::DynamicChannelCallback; |
| using ServiceRequestCallback = DynamicChannelRegistry::ServiceRequestCallback; |
| |
| // TestLoopFixture overrides |
| void SetUp() override { |
| TestLoopFixture::SetUp(); |
| 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()); |
| registry_ = std::make_unique<BrEdrDynamicChannelRegistry>( |
| sig(), fit::bind_member(this, &L2CAP_BrEdrDynamicChannelTest::OnChannelClose), |
| fit::bind_member(this, &L2CAP_BrEdrDynamicChannelTest::OnServiceRequest)); |
| } |
| |
| void TearDown() override { |
| RunLoopUntilIdle(); |
| registry_ = nullptr; |
| signaling_channel_ = nullptr; |
| service_request_cb_ = nullptr; |
| channel_close_cb_ = nullptr; |
| TestLoopFixture::TearDown(); |
| } |
| |
| 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_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(L2CAP_BrEdrDynamicChannelTest); |
| }; |
| |
| TEST_F(L2CAP_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()}); |
| 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()); |
| } |
| 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(RunLoopUntilIdle()); |
| |
| 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); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| 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(RunLoopUntilIdle()); |
| |
| 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()}); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| 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(RunLoopUntilIdle()); |
| |
| 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()}); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveResponses( |
| conn_rsp_id, {{SignalingChannel::Status::kSuccess, kOkConnRsp.view()}})); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| EXPECT_EQ(open_cb_count, 1); |
| EXPECT_EQ(close_cb_count, 0); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| 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(L2CAP_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(RunLoopUntilIdle()); |
| |
| 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(L2CAP_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(RunLoopUntilIdle()); |
| 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(L2CAP_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(RunLoopUntilIdle()); |
| 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(L2CAP_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(RunLoopUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| 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(L2CAP_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(RunLoopUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| // Simulate a ERTX timer expiry after a Configuration Response with "Pending" status. |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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(ChannelMode::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(RunLoopUntilIdle()); |
| |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| |
| registry()->CloseChannel(kLocalCId); |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| |
| // Local channel closure shouldn't trigger the close callback. |
| EXPECT_EQ(0, close_cb_count); |
| |
| // Repeated closure of the same channel should not have any effect. |
| registry()->CloseChannel(kLocalCId); |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| 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(L2CAP_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()}); |
| 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++; |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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()}); |
| 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++; |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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()}); |
| 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++; |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kLocalCId, chan->local_cid()); |
| EXPECT_EQ(kRemoteCId, chan->remote_cid()); |
| }); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| 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(L2CAP_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(RunLoopUntilIdle()); |
| |
| // Complete opening the channel. |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| EXPECT_EQ(0, close_cb_count); |
| |
| registry()->CloseChannel(kLocalCId); |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| // Disconnection Response hasn't been received yet so the second channel |
| // should use a different channel ID. |
| const ByteBuffer& kSecondChannelConnReq = CreateStaticByteBuffer( |
| // 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)); |
| |
| // Complete the disconnection on the first channel. |
| RETURN_IF_FATAL(sig()->ReceiveResponses( |
| disconn_id, {{SignalingChannel::Status::kSuccess, kDisconRsp.view()}})); |
| |
| // 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(L2CAP_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(RunLoopUntilIdle()); |
| |
| 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(RunLoopUntilIdle()); |
| |
| EXPECT_TRUE(disconnect_done_cb_called); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpectRejectInvalidChannelId( |
| kConfigurationRequest, kInboundConfigReq, kLocalCId, kInvalidChannelId)); |
| |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| 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(L2CAP_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)); |
| RunLoopUntilIdle(); |
| |
| 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(L2CAP_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)); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(L2CAP_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)); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1, service_request_cb_count); |
| } |
| |
| TEST_F(L2CAP_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)); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(L2CAP_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) { open_cb_count++; }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| const ByteBuffer& kInboundConfigReqUnknownOption = CreateStaticByteBuffer( |
| // Destination CID |
| LowerBits(kLocalCId), UpperBits(kLocalCId), |
| |
| // Flags |
| 0x00, 0x00, |
| |
| // Unknown Option: Type, Length, Data |
| 0x70, 0x01, 0x02); |
| |
| const ByteBuffer& kOutboundConfigRspUnknownOption = CreateStaticByteBuffer( |
| // 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); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| } |
| |
| struct ReceiveMtuTestParams { |
| std::optional<uint16_t> request_mtu; |
| uint16_t response_mtu; |
| ConfigurationResult response_status; |
| }; |
| class ReceivedMtuTest : public L2CAP_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(chan->info().max_tx_sdu_size, GetParam().response_mtu); |
| }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| 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()}); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| L2CAP_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 L2CAP_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(kExpectedConfiguredLocalMtu, chan->info().max_rx_sdu_size); |
| open_cb_count++; |
| }; |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| 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()}); |
| } |
| |
| 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(kExpectedConfiguredLocalMtu, chan->info().max_rx_sdu_size); |
| open_cb_count++; |
| }; |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| 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()}); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(L2CAP_BrEdrDynamicChannelTest, ConfigRspWithMtuTest, |
| ::testing::Values(std::nullopt, kMinACLMTU)); |
| |
| TEST_F(L2CAP_BrEdrDynamicChannelTest, RespondsToInboundExtendedFeaturesRequest) { |
| const auto kExpectedExtendedFeatures = |
| kExtendedFeaturesBitFixedChannels | kExtendedFeaturesBitEnhancedRetransmission; |
| const auto kExpectedExtendedFeaturesInfoRsp = |
| MakeExtendedFeaturesInfoRsp(InformationResult::kSuccess, kExpectedExtendedFeatures); |
| sig()->ReceiveExpect(kInformationRequest, kExtendedFeaturesInfoReq, |
| kExpectedExtendedFeaturesInfoRsp); |
| } |
| |
| TEST_F(L2CAP_BrEdrDynamicChannelTest, ExtendedFeaturesResponseSaved) { |
| const auto kExpectedExtendedFeatures = |
| kExtendedFeaturesBitFixedChannels | 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(L2CAP_BrEdrDynamicChannelTest, ERTMChannelWaitsForExtendedFeaturesBeforeStartingConfigFlow) { |
| 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) { open_cb_count++; }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| // Config request should not be sent. |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), kConfigurationRequest, kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, kExtendedFeaturesInfoRsp.view()}}); |
| |
| RunLoopUntilIdle(); |
| |
| 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()}); |
| } |
| |
| TEST_F(L2CAP_BrEdrDynamicChannelTest, ERTChannelDoesNotSendConfigReqBeforeConnRspReceived) { |
| auto conn_id = EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view(), {}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| // 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. |
| RunLoopUntilIdle(); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), kConfigurationRequest, kOutboundConfigReq.view(), |
| {SignalingChannel::Status::kSuccess, kInboundEmptyConfigRsp.view()}); |
| sig()->ReceiveResponses(conn_id, {{SignalingChannel::Status::kSuccess, kOkConnRsp.view()}}); |
| RunLoopUntilIdle(); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| } |
| |
| TEST_F(L2CAP_BrEdrDynamicChannelTest, SendAndReceiveERTMConfigReq) { |
| constexpr uint16_t kPreferredMtu = kDefaultMTU + 1; |
| const auto kExpectedOutboundConfigReq = MakeConfigReqWithMtuAndRfc( |
| kRemoteCId, kPreferredMtu, ChannelMode::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()}); |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.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()); |
| |
| // Check values of ChannelInfo fields. |
| EXPECT_EQ(ChannelMode::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, {ChannelMode::kEnhancedRetransmission, kPreferredMtu}, |
| std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReqWithERTM, |
| kOutboundOkConfigRspWithErtm)); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| // 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(L2CAP_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()}); |
| 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) { |
| ASSERT_TRUE(chan); |
| EXPECT_TRUE(chan->IsOpen()); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| // 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(L2CAP_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()); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RunLoopUntilIdle(); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| RunLoopUntilIdle(); |
| |
| // Peer requests basic mode. |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| RunLoopUntilIdle(); |
| |
| // 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()}}); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| } |
| |
| // 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(L2CAP_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()}); |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| // Receive features mask without ERTM bit set. |
| sig()->ReceiveResponses(ext_info_transaction_id(), |
| {{SignalingChannel::Status::kSuccess, kExtendedFeaturesInfoRsp.view()}}); |
| } |
| |
| TEST_F(L2CAP_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()}); |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| // Peer requests ERTM. Local device should reject with unacceptable params. |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReqWithERTM, |
| kOutboundUnacceptableParamsWithRfcBasicConfigRsp)); |
| } |
| |
| // 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( |
| L2CAP_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(RunLoopUntilIdle()); |
| |
| // 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. |
| RunLoopUntilIdle(); |
| 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( |
| L2CAP_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 = ChannelMode::kEnhancedRetransmission; |
| registry()->OpenOutbound(kPsm, params, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| // Send outbound config request. |
| RunLoopUntilIdle(); |
| |
| // Receive inbound config request. |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReqWithERTM, |
| kOutboundOkConfigRspWithErtm)); |
| |
| sig()->ReceiveResponses(outbound_config_req_id, |
| {{SignalingChannel::Status::kSuccess, |
| kInboundUnacceptableParamsWithRfcBasicConfigRsp.view()}}); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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(RunLoopUntilIdle()); |
| |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReqWithERTM, |
| kOutboundUnacceptableParamsWithRfcBasicConfigRsp)); |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReqWithERTM, |
| kOutboundUnacceptableParamsWithRfcBasicConfigRsp)); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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()}); |
| 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) { open_cb_count++; }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_BrEdrDynamicChannelTest, |
| SendUnacceptableParamsResponseWhenPeerRequestsUnsupportedChannelMode) { |
| EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view(), |
| {SignalingChannel::Status::kSuccess, kOkConnRsp.view()}); |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, {}); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| // Retransmission mode is not supported. |
| const auto kInboundConfigReqWithRetransmissionMode = |
| MakeConfigReqWithMtuAndRfc(kLocalCId, kMaxMTU, ChannelMode::kRetransmission, 0, 0, 0, 0, 0); |
| RETURN_IF_FATAL(sig()->ReceiveExpect(kConfigurationRequest, |
| kInboundConfigReqWithRetransmissionMode, |
| kOutboundUnacceptableParamsWithRfcERTMConfigRsp)); |
| } |
| |
| // 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(L2CAP_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()}); |
| 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) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(ChannelMode::kBasic, chan->info().mode); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| // Request ERTM. |
| RunLoopUntilIdle(); |
| |
| // Peer requests basic mode. |
| RETURN_IF_FATAL( |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp)); |
| |
| // Disconnect |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| // Same as above, but the peer sends its positive response after sending its Basic Mode request. |
| TEST_F(L2CAP_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()}); |
| 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) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(ChannelMode::kBasic, chan->info().mode); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, std::move(open_cb)); |
| |
| RETURN_IF_FATAL(RunLoopUntilIdle()); |
| |
| // 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 |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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()}); |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kPreferredMtu, chan->info().max_rx_sdu_size); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, {ChannelMode::kBasic, kPreferredMtu}, open_cb); |
| RunLoopUntilIdle(); |
| |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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()}); |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| int open_cb_count = 0; |
| auto open_cb = [&](const DynamicChannel* chan) { |
| if (open_cb_count == 0) { |
| ASSERT_TRUE(chan); |
| EXPECT_EQ(kMinACLMTU, chan->info().max_rx_sdu_size); |
| } |
| open_cb_count++; |
| }; |
| |
| registry()->OpenOutbound(kPsm, {ChannelMode::kBasic, kMtu}, open_cb); |
| RunLoopUntilIdle(); |
| |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kOutboundOkConfigRsp); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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()}); |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.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(ChannelMode::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, {ChannelMode::kBasic, kPreferredMtu}, open_cb); |
| RunLoopUntilIdle(); |
| |
| const ByteBuffer& kExpectedOutboundOkConfigRsp = MakeConfigRspWithMtu(kRemoteCId, kPeerMtu); |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq, kExpectedOutboundOkConfigRsp); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1, open_cb_count); |
| } |
| |
| TEST_F(L2CAP_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()}); |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| const auto kInboundConfigReq0 = |
| MakeConfigReqWithMtu(kLocalCId, kTxMtu, kConfigurationContinuation); |
| const auto kOutboundConfigRsp1 = MakeConfigRspWithMtuAndRfc( |
| kRemoteCId, ConfigurationResult::kSuccess, ChannelMode::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(kTxMtu, chan->info().max_tx_sdu_size); |
| EXPECT_EQ(ChannelMode::kEnhancedRetransmission, chan->info().mode); |
| } |
| open_cb_count++; |
| }; |
| |
| sig()->ReceiveResponses(ext_info_transaction_id(), {{SignalingChannel::Status::kSuccess, |
| kExtendedFeaturesInfoRspWithERTM.view()}}); |
| registry()->OpenOutbound(kPsm, kERTMChannelParams, open_cb); |
| RunLoopUntilIdle(); |
| |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq0, |
| kOutboundEmptyContinuationConfigRsp); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, open_cb_count); |
| |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReqWithERTM, kOutboundConfigRsp1); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1u, open_cb_count); |
| } |
| |
| // The unknown options from both configuration requests should be included when responding |
| // with the "unkown options" result. |
| TEST_F(L2CAP_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()}); |
| EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(), |
| {SignalingChannel::Status::kSuccess, kDisconRsp.view()}); |
| |
| constexpr uint8_t kUnknownOption0Type = 0x70; |
| constexpr uint8_t kUnknownOption1Type = 0x71; |
| const auto kInboundConfigReq0 = StaticByteBuffer( |
| // Destination CID |
| LowerBits(kLocalCId), UpperBits(kLocalCId), |
| // Flags (C = 1) |
| 0x01, 0x00, |
| // Unknown Option |
| kUnknownOption0Type, 0x01, 0x00); |
| const auto kInboundConfigReq1 = StaticByteBuffer( |
| // Destination CID |
| LowerBits(kLocalCId), UpperBits(kLocalCId), |
| // Flags (C = 0) |
| 0x00, 0x00, |
| // Unknown Option |
| kUnknownOption1Type, 0x01, 0x00); |
| |
| const auto kOutboundUnknownOptionsConfigRsp = StaticByteBuffer( |
| // 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) { open_cb_count++; }; |
| |
| registry()->OpenOutbound(kPsm, kChannelParams, open_cb); |
| RunLoopUntilIdle(); |
| |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq0, |
| kOutboundEmptyContinuationConfigRsp); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, open_cb_count); |
| |
| sig()->ReceiveExpect(kConfigurationRequest, kInboundConfigReq1, kOutboundUnknownOptionsConfigRsp); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, open_cb_count); |
| } |
| |
| } // namespace |
| } // namespace internal |
| } // namespace l2cap |
| } // namespace bt |