| // Copyright 2020 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 "enhanced_retransmission_mode_engines.h" |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "lib/gtest/test_loop_fixture.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/fragmenter.h" |
| |
| namespace bt::l2cap::internal { |
| namespace { |
| |
| class L2CAP_EnhancedRetransmissionModeEnginesTest : public ::gtest::TestLoopFixture {}; |
| |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 63; |
| |
| constexpr hci::ConnectionHandle kTestHandle = 0x0001; |
| constexpr ChannelId kTestChannelId = 0x0001; |
| constexpr uint8_t kExtendedControlFBitMask = 0b1000'0000; |
| constexpr uint8_t kExtendedControlPBitMask = 0b0001'0000; |
| constexpr uint8_t kExtendedControlRejFunctionMask = 0b0000'0100; |
| constexpr uint8_t kExtendedControlReceiverNotReadyFunctionMask = 0b0000'1000; |
| constexpr uint8_t kExtendedControlSrejFunctionMask = 0b0000'1100; |
| |
| void NoOpTxCallback(ByteBufferPtr){}; |
| void NoOpFailureCallback(){}; |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, MakeLinkedERTMEngines) { |
| auto [rx_engine, tx_engine] = |
| MakeLinkedEnhancedRetransmissionModeEngines(kTestChannelId, kDefaultMTU, kMaxTransmissions, |
| kTxWindow, NoOpTxCallback, NoOpFailureCallback); |
| EXPECT_TRUE(rx_engine); |
| EXPECT_TRUE(tx_engine); |
| } |
| |
| // This test that TxEngine sends I-Frames whose acknowledgement sequence numbers match the receive |
| // sequence number in RxEngine. This also tests that peer acknowledgement restarts our outbound data |
| // that was paused by hitting TxWindow. This mirrors the L2CAP Test Specification L2CAP/ERM/BV-06-C. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, |
| OutboundInformationFramesAcknowledgeReceivedInformationFrames) { |
| int tx_count = 0; |
| auto tx_callback = [&tx_count](ByteBufferPtr pdu) { |
| ASSERT_TRUE(pdu); |
| // Unlike the Test Spec's sequence diagram, we respond to the peer's I-Frame with a Receiver |
| // Ready (which is always allowed), so our subsequent I-Frame is actually the third outbound. |
| if (tx_count == 0) { |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| EXPECT_EQ(0, header.tx_seq()); |
| EXPECT_EQ(0, header.receive_seq_num()); |
| } else if (tx_count == 2) { |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| EXPECT_EQ(1, header.tx_seq()); |
| |
| // Acknowledges the I-Frame from the peer. |
| EXPECT_EQ(1, header.receive_seq_num()); |
| } |
| tx_count++; |
| }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, /*n_frames_in_tx_window=*/1, tx_callback, |
| NoOpFailureCallback); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer{'p', 'i', 'n', 'g'})); |
| EXPECT_EQ(1, tx_count); |
| |
| // Receive an I-frame containing an acknowledgment including the frame that we transmitted. |
| // See Core Spec, v5, Vol 3, Part A, Section 3.3.2, I-Frame Enhanced Control Field. |
| StaticByteBuffer info_frame(0, 1, 'p', 'o', 'n', 'g'); |
| EXPECT_TRUE(rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, info_frame, FrameCheckSequenceOption::kIncludeFcs))); |
| EXPECT_EQ(2, tx_count); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer{'b', 'y', 'e', 'e'})); |
| EXPECT_EQ(3, tx_count); |
| } |
| |
| // This test simulates a peer's non-response to our S-Frame poll request which causes us to |
| // raise a channel error after the monitor timer expires. This mirrors L2CAP Test Specification |
| // v5.0.2 L2CAP/ERM/BV-11-C. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, SignalFailureAfterMonitorTimerExpiry) { |
| int tx_count = 0; |
| auto tx_callback = [&tx_count](ByteBufferPtr pdu) { |
| ASSERT_TRUE(pdu); |
| // Unlike the Test Spec's sequence diagram, we respond to the peer's I-Frame with a Receiver |
| // Ready (which is always allowed), so our subsequent I-Frame is actually the third outbound. |
| if (tx_count == 0) { |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| EXPECT_EQ(0, header.tx_seq()); |
| EXPECT_EQ(0, header.receive_seq_num()); |
| } else if (tx_count == 1) { |
| ASSERT_EQ(sizeof(SimpleSupervisoryFrame), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_supervisory_frame()); |
| ASSERT_TRUE(pdu->As<SimpleSupervisoryFrame>().is_poll_request()); |
| } |
| tx_count++; |
| }; |
| bool failure_cb_called = false; |
| auto failure_cb = [&failure_cb_called] { failure_cb_called = true; }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, /*max_transmissions=*/1, kTxWindow, tx_callback, failure_cb); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer{'p', 'i', 'n', 'g'})); |
| EXPECT_EQ(1, tx_count); |
| |
| // Send a poll request after timer expiry waiting for peer acknowledgment. |
| RETURN_IF_FATAL(RunLoopFor(kErtmReceiverReadyPollTimerDuration)); |
| EXPECT_EQ(2, tx_count); |
| |
| // Monitor Timer expires without a response from the peer, signaling a channel failure. |
| EXPECT_FALSE(failure_cb_called); |
| RETURN_IF_FATAL(RunLoopFor(kErtmMonitorTimerDuration)); |
| EXPECT_TRUE(failure_cb_called); |
| } |
| |
| // This test simulates non-acknowledgment of an I-Frame that the local host sends which causes us |
| // to meet the MaxTransmit that the peer specified, raising a local error for the channel. This |
| // mirrors L2CAP Test Specification v5.0.2 L2CAP/ERM/BV-12-C. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, SignalFailureAfterMaxTransmitExhausted) { |
| int tx_count = 0; |
| auto tx_callback = [&tx_count](ByteBufferPtr pdu) { |
| ASSERT_TRUE(pdu); |
| // Unlike the Test Spec's sequence diagram, we respond to the peer's I-Frame with a Receiver |
| // Ready (which is always allowed), so our subsequent I-Frame is actually the third outbound. |
| if (tx_count == 0) { |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| EXPECT_EQ(0, header.tx_seq()); |
| EXPECT_EQ(0, header.receive_seq_num()); |
| } else if (tx_count == 1) { |
| ASSERT_EQ(sizeof(SimpleSupervisoryFrame), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_supervisory_frame()); |
| ASSERT_TRUE(pdu->As<SimpleSupervisoryFrame>().is_poll_request()); |
| } |
| tx_count++; |
| }; |
| bool failure_cb_called = false; |
| auto failure_cb = [&failure_cb_called] { failure_cb_called = true; }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, /*max_transmissions=*/1, kTxWindow, tx_callback, failure_cb); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer{'p', 'i', 'n', 'g'})); |
| EXPECT_EQ(1, tx_count); |
| |
| // Send a poll request after timer expiry waiting for peer acknowledgment. |
| RETURN_IF_FATAL(RunLoopFor(kErtmReceiverReadyPollTimerDuration)); |
| EXPECT_EQ(2, tx_count); |
| |
| // Peer response doesn't acknowledge the I-Frame's TxSeq and we already used up MaxTransmit, |
| // signaling a channel failure. |
| EXPECT_FALSE(failure_cb_called); |
| |
| // Receive an S-frame containing an acknowledgment not including the frame that we transmitted. F |
| // is set. See Core Spec, v5, Vol 3, Part A, Section 3.3.2, S-Frame Enhanced Control Field. |
| StaticByteBuffer receiver_ready(0b1 | kExtendedControlFBitMask, 0); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_TRUE(failure_cb_called); |
| } |
| |
| // This tests the integration of receiving Reject frames with triggering retransmission of requested |
| // unacknowledged I-Frames. This mirrors the L2CAP Test Specification v5.0.2 L2CAP/ERM/BV-13-C. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, RetransmitAfterReceivingRejectSFrame) { |
| int tx_count = 0; |
| int iframe_0_tx_count = 0; |
| int iframe_1_tx_count = 0; |
| auto tx_callback = [&tx_count, &iframe_0_tx_count, &iframe_1_tx_count](ByteBufferPtr pdu) { |
| tx_count++; |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| if (header.tx_seq() == 0) { |
| iframe_0_tx_count++; |
| } else if (header.tx_seq() == 1) { |
| iframe_1_tx_count++; |
| } |
| }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, NoOpFailureCallback); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('a'))); |
| EXPECT_EQ(1, tx_count); |
| EXPECT_EQ(1, iframe_0_tx_count); |
| EXPECT_EQ(0, iframe_1_tx_count); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('b'))); |
| EXPECT_EQ(2, tx_count); |
| EXPECT_EQ(1, iframe_0_tx_count); |
| EXPECT_EQ(1, iframe_1_tx_count); |
| |
| // Receive an S-frame containing a retransmission request starting at seq = 0. See Core Spec v5.0, |
| // Vol 3, Part A, Section 3.3.2 S-Frame Enhanced Control Field. |
| StaticByteBuffer reject(0b1 | kExtendedControlRejFunctionMask, 0); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, reject, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Check that this caused a retransmission of the two I-Frames. |
| EXPECT_EQ(4, tx_count); |
| EXPECT_EQ(2, iframe_0_tx_count); |
| EXPECT_EQ(2, iframe_1_tx_count); |
| } |
| |
| // This tests the integration of receiving Selective Reject frames that are poll requests, |
| // triggering retransmission as well as acknowledgment that frees up transmit window. This mirrors |
| // the L2CAP Test Specification v5.0.2 L2CAP/ERM/BV-14-C. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, |
| RetransmitAfterReceivingSelectiveRejectPollRequestSFrame) { |
| int tx_count = 0; |
| std::array<int, 4> iframe_tx_counts{}; |
| auto tx_callback = [&tx_count, &iframe_tx_counts](ByteBufferPtr pdu) { |
| tx_count++; |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| ASSERT_GT(iframe_tx_counts.size(), header.tx_seq()); |
| iframe_tx_counts[header.tx_seq()]++; |
| }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, /*n_frames_in_tx_window=*/3, tx_callback, |
| NoOpFailureCallback); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| for (int i = 0; i < 4; i++) { |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('a' + i))); |
| } |
| |
| // TxWindow caps transmissions. |
| EXPECT_EQ(3, tx_count); |
| EXPECT_THAT(iframe_tx_counts, testing::ElementsAre(1, 1, 1, 0)); |
| |
| // Receive an S-frame containing a poll request retransmission request for seq = 1. See Core Spec |
| // v5.0, Vol 3, Part A, Section 3.3.2 S-Frame Enhanced Control Field. |
| auto selective_reject = |
| StaticByteBuffer(0b1 | kExtendedControlSrejFunctionMask | kExtendedControlPBitMask, 1); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, selective_reject, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Check that this caused a retransmission of I-Frames 1 and 3 because the poll request |
| // acknowledged I-Frame 0, freeing up space in the transmit window. |
| EXPECT_EQ(5, tx_count); |
| EXPECT_THAT(iframe_tx_counts, testing::ElementsAre(1, 2, 1, 1)); |
| } |
| |
| // This tests the integration of receiving Selective Reject frames that are not poll requests, |
| // triggering retransmission only. This mirrors the L2CAP Test Specification v5.0.2 |
| // L2CAP/ERM/BV-15-C. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, RetransmitAfterReceivingSelectiveRejectSFrame) { |
| int tx_count = 0; |
| std::array<int, 4> iframe_tx_counts{}; |
| auto tx_callback = [&tx_count, &iframe_tx_counts](ByteBufferPtr pdu) { |
| tx_count++; |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| ASSERT_GT(iframe_tx_counts.size(), header.tx_seq()); |
| iframe_tx_counts[header.tx_seq()]++; |
| }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, /*n_frames_in_tx_window=*/3, tx_callback, |
| NoOpFailureCallback); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| for (int i = 0; i < 4; i++) { |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('a' + i))); |
| } |
| |
| // TxWindow caps transmissions. |
| EXPECT_EQ(3, tx_count); |
| EXPECT_THAT(iframe_tx_counts, testing::ElementsAre(1, 1, 1, 0)); |
| |
| // Receive an S-frame containing a retransmission request for seq = 1. See Core Spec v5.0, Vol 3, |
| // Part A, Section 3.3.2 S-Frame Enhanced Control Field. |
| StaticByteBuffer selective_reject(0b1 | kExtendedControlSrejFunctionMask, 1); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, selective_reject, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Check that this caused a retransmission of I-Frame 1 only. |
| EXPECT_EQ(4, tx_count); |
| EXPECT_THAT(iframe_tx_counts, testing::ElementsAre(1, 2, 1, 0)); |
| |
| // Receive an S-frame containing an acknowledgment up to seq = 3. |
| StaticByteBuffer ack_3(0b1, 3); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, ack_3, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Initial transmission of I-Frame 3 after the acknowledgment freed up transmit window capacity. |
| EXPECT_EQ(5, tx_count); |
| EXPECT_THAT(iframe_tx_counts, testing::ElementsAre(1, 2, 1, 1)); |
| |
| // Receive an S-frame containing an acknowledgment up to seq = 3. |
| StaticByteBuffer ack_4(0b1, 4); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, ack_4, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| EXPECT_EQ(5, tx_count); |
| } |
| |
| // This tests the integration of receiving an acknowledgment sequence with triggering retransmission |
| // of unacknowledged I-Frames. This mirrors the L2CAP Test Specification L2CAP/ERM/BV-18-C. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, |
| RetransmitAfterPollResponseDoesNotAcknowledgeSentFrames) { |
| DynamicByteBuffer info_frame; |
| int tx_count = 0; |
| auto tx_callback = [&info_frame, &tx_count](ByteBufferPtr pdu) { |
| // The first packet is the I-Frame containing the data that we sent. |
| // The second packet is the S-Frame polling for the peer after the Retransmission Timer expires. |
| // It is not checked to keep the tests less fragile, as the first two packets are already |
| // covered by L2CAP_EnhancedRetransmissionModeTxEngineTest. |
| if (tx_count == 0) { |
| info_frame = DynamicByteBuffer(*pdu); |
| } else if (tx_count == 2) { |
| EXPECT_TRUE(ContainersEqual(info_frame, *pdu)); |
| } |
| tx_count++; |
| }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, NoOpFailureCallback); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer{'a'})); |
| EXPECT_EQ(1, tx_count); |
| |
| ASSERT_TRUE(RunLoopFor(kErtmReceiverReadyPollTimerDuration)); |
| EXPECT_EQ(2, tx_count); |
| |
| // Receive an S-frame containing an acknowledgment not including the frame that we transmitted. F |
| // is set. See Core Spec, v5, Vol 3, Part A, Section 3.3.2, S-Frame Enhanced Control Field. |
| StaticByteBuffer receiver_ready(0b1 | kExtendedControlFBitMask, 0); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Check that this caused a retransmission of the initial I-Frame. |
| EXPECT_EQ(3, tx_count); |
| } |
| |
| // This test simulates the peer declaring that it is busy by sending the ReceiverNotReady S-Frame, |
| // which prevents us from retransmitting an unacknowledged outbound I-Frame. This mirrors L2CAP Test |
| // Specification v5.0.2 L2CAP/ERM/BV-20-C. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, |
| DoNotRetransmitAfterReceivingReceiverNotReadyPollResponse) { |
| int tx_count = 0; |
| auto tx_callback = [&tx_count](ByteBufferPtr pdu) { |
| ASSERT_TRUE(pdu); |
| // Unlike the Test Spec's sequence diagram, we respond to the peer's I-Frame with a Receiver |
| // Ready (which is always allowed), so our subsequent I-Frame is actually the third outbound. |
| if (tx_count == 0) { |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| EXPECT_EQ(0, header.tx_seq()); |
| EXPECT_EQ(0, header.receive_seq_num()); |
| } else if (tx_count == 1) { |
| ASSERT_EQ(sizeof(SimpleSupervisoryFrame), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_supervisory_frame()); |
| ASSERT_TRUE(pdu->As<SimpleSupervisoryFrame>().is_poll_request()); |
| } |
| tx_count++; |
| }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, NoOpFailureCallback); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer{'p', 'i', 'n', 'g'})); |
| EXPECT_EQ(1, tx_count); |
| |
| // Send a poll request after timer expiry waiting for peer acknowledgment. |
| RETURN_IF_FATAL(RunLoopFor(kErtmReceiverReadyPollTimerDuration)); |
| EXPECT_EQ(2, tx_count); |
| |
| // Receive an Receiver Not Ready containing an acknowledgment not including the frame that we |
| // transmitted. F is set. See Core Spec, v5, Vol 3, Part A, Section 3.3.2, S-Frame Enhanced |
| // Control Field. |
| StaticByteBuffer receiver_not_ready( |
| 0b1 | kExtendedControlReceiverNotReadyFunctionMask | kExtendedControlFBitMask, 0); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_not_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // RNR sets our RemoteBusy flag, so we should not transmit anything. |
| EXPECT_EQ(2, tx_count); |
| } |
| // This tests that explicitly a requested selective retransmission and another for the same I-Frame |
| // that is a poll response causes only one retransmission. This mirrors the L2CAP Test Specification |
| // v5.0.2 L2CAP/ERM/BI-03-C. |
| TEST_F( |
| L2CAP_EnhancedRetransmissionModeEnginesTest, |
| RetransmitOnlyOnceAfterReceivingDuplicateSelectiveRejectSFramesForSameIFrameDuringReceiverReadyPoll) { |
| int tx_count = 0; |
| int iframe_0_tx_count = 0; |
| int iframe_1_tx_count = 0; |
| auto tx_callback = [&tx_count, &iframe_0_tx_count, &iframe_1_tx_count](ByteBufferPtr pdu) { |
| tx_count++; |
| |
| // After outbound I-Frames, expect an outbound poll request S-Frame. |
| if (tx_count == 3) { |
| ASSERT_EQ(sizeof(SimpleSupervisoryFrame), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_supervisory_frame()); |
| ASSERT_TRUE(pdu->As<SimpleSupervisoryFrame>().is_poll_request()); |
| return; |
| } |
| |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| if (header.tx_seq() == 0) { |
| iframe_0_tx_count++; |
| } else if (header.tx_seq() == 1) { |
| iframe_1_tx_count++; |
| } |
| }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, NoOpFailureCallback); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('a'))); |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('b'))); |
| EXPECT_EQ(2, tx_count); |
| EXPECT_EQ(1, iframe_0_tx_count); |
| EXPECT_EQ(1, iframe_1_tx_count); |
| |
| ASSERT_TRUE(RunLoopFor(kErtmReceiverReadyPollTimerDuration)); |
| EXPECT_EQ(3, tx_count); |
| |
| // Receive an S-frame containing a retransmission request for I-Frame 0. See Core Spec v5.0, Vol |
| // 3, Part A, Section 3.3.2 S-Frame Enhanced Control Field. |
| StaticByteBuffer selective_reject(0b1 | kExtendedControlSrejFunctionMask, 0); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, selective_reject, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Receive an S-frame containing a retransmission request for I-Frame 0 and a poll response. |
| auto selective_reject_f = |
| StaticByteBuffer(0b1 | kExtendedControlSrejFunctionMask | kExtendedControlFBitMask, 0); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, selective_reject_f, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Check that these two SREJs caused a retransmission of I-Frame 0 only once. |
| EXPECT_EQ(4, tx_count); |
| EXPECT_EQ(2, iframe_0_tx_count); |
| EXPECT_EQ(1, iframe_1_tx_count); |
| |
| // Acknowledge all of the outbound I-Frames per the specification's sequence diagram. |
| StaticByteBuffer receiver_ready_2(0b1, 2); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready_2, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_EQ(4, tx_count); |
| } |
| |
| // This tests that explicitly requested retransmission and receiving a poll response that doesn't |
| // acknowledge all I-Frames results in only a single set of retransmissions. This mirrors the L2CAP |
| // Test Specification v5.0.2 L2CAP/ERM/BI-04-C. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, |
| RetransmitAfterReceivingRejectSFrameAndReceiverReadyPollResponseNotAcknowledgingSentFrames) { |
| int tx_count = 0; |
| int iframe_0_tx_count = 0; |
| int iframe_1_tx_count = 0; |
| auto tx_callback = [&tx_count, &iframe_0_tx_count, &iframe_1_tx_count](ByteBufferPtr pdu) { |
| tx_count++; |
| |
| // After outbound I-Frames, expect an outbound poll request S-Frame. |
| if (tx_count == 3) { |
| ASSERT_EQ(sizeof(SimpleSupervisoryFrame), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_supervisory_frame()); |
| ASSERT_TRUE(pdu->As<SimpleSupervisoryFrame>().is_poll_request()); |
| return; |
| } |
| |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| if (header.tx_seq() == 0) { |
| iframe_0_tx_count++; |
| } else if (header.tx_seq() == 1) { |
| iframe_1_tx_count++; |
| } |
| }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, NoOpFailureCallback); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('a'))); |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('b'))); |
| EXPECT_EQ(2, tx_count); |
| EXPECT_EQ(1, iframe_0_tx_count); |
| EXPECT_EQ(1, iframe_1_tx_count); |
| |
| ASSERT_TRUE(RunLoopFor(kErtmReceiverReadyPollTimerDuration)); |
| EXPECT_EQ(3, tx_count); |
| |
| // Receive an S-frame containing a retransmission request starting at seq = 0. See Core Spec v5.0, |
| // Vol 3, Part A, Section 3.3.2 S-Frame Enhanced Control Field. |
| StaticByteBuffer reject(0b1 | kExtendedControlRejFunctionMask, 0); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, reject, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Receive an S-frame containing an acknowledgment not including the frames that we transmitted. F |
| // is set. See Core Spec v5.0, Vol 3, Part A, Section 3.3.2 S-Frame Enhanced Control Field. |
| StaticByteBuffer receiver_ready_0(0b1 | kExtendedControlFBitMask, 0); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready_0, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Check that this caused a retransmission of the two I-Frames only once each. |
| EXPECT_EQ(5, tx_count); |
| EXPECT_EQ(2, iframe_0_tx_count); |
| EXPECT_EQ(2, iframe_1_tx_count); |
| |
| // Acknowledge all of the outbound I-Frames per the specification's sequence diagram. |
| StaticByteBuffer receiver_ready_2(0b1, 2); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready_2, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_EQ(5, tx_count); |
| } |
| |
| // This tests that explicitly requested retransmission and receiving a I-Frame that doesn't |
| // acknowledge all I-Frames results in only a single set of retransmissions. This mirrors the L2CAP |
| // Test Specification v5.0.2 L2CAP/ERM/BI-05-C in which we—the IUT—perform the ALT1 path in the |
| // sequence diagram Figure 4.96. |
| TEST_F(L2CAP_EnhancedRetransmissionModeEnginesTest, |
| RetransmitAfterReceivingRejectSFrameAndIFramePollResponseNotAcknowledgingSentFrames) { |
| int tx_count = 0; |
| int iframe_0_tx_count = 0; |
| int iframe_1_tx_count = 0; |
| auto tx_callback = [&tx_count, &iframe_0_tx_count, &iframe_1_tx_count](ByteBufferPtr pdu) { |
| tx_count++; |
| SCOPED_TRACE(tx_count); |
| |
| if (tx_count == 3) { |
| // After outbound I-Frames (not retransmitted), expect an outbound poll request S-Frame. |
| ASSERT_EQ(sizeof(SimpleSupervisoryFrame), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_supervisory_frame()); |
| EXPECT_TRUE(pdu->As<SimpleSupervisoryFrame>().is_poll_request()); |
| return; |
| } else if (tx_count == 6) { |
| // After retransmitted I-Frames, expect an outbound ReceiverReady that acknowledges the peer's |
| // I-Frame. |
| ASSERT_EQ(sizeof(SimpleSupervisoryFrame), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_supervisory_frame()); |
| EXPECT_FALSE(pdu->As<SimpleSupervisoryFrame>().is_poll_request()); |
| EXPECT_EQ(1, pdu->As<SimpleSupervisoryFrame>().receive_seq_num()); |
| return; |
| } |
| |
| ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size()); |
| ASSERT_TRUE(pdu->As<EnhancedControlField>().designates_information_frame()); |
| const auto& header = pdu->As<SimpleInformationFrameHeader>(); |
| EXPECT_EQ(0, header.receive_seq_num()); |
| if (header.tx_seq() == 0) { |
| iframe_0_tx_count++; |
| } else if (header.tx_seq() == 1) { |
| iframe_1_tx_count++; |
| } |
| }; |
| auto [rx_engine, tx_engine] = MakeLinkedEnhancedRetransmissionModeEngines( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, NoOpFailureCallback); |
| ASSERT_TRUE(rx_engine); |
| ASSERT_TRUE(tx_engine); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('a'))); |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('b'))); |
| EXPECT_EQ(2, tx_count); |
| EXPECT_EQ(1, iframe_0_tx_count); |
| EXPECT_EQ(1, iframe_1_tx_count); |
| |
| ASSERT_TRUE(RunLoopFor(kErtmReceiverReadyPollTimerDuration)); |
| EXPECT_EQ(3, tx_count); |
| |
| // Receive an S-frame containing a retransmission request starting at seq = 0. See Core Spec v5.0, |
| // Vol 3, Part A, Section 3.3.2 S-Frame Enhanced Control Field. |
| StaticByteBuffer reject(0b1 | kExtendedControlRejFunctionMask, 0); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, reject, FrameCheckSequenceOption::kIncludeFcs)); |
| |
| // Receive an I-frame containing an acknowledgment not including the frames that we transmitted. F |
| // is set. See Core Spec v5.0, Vol 3, Part A, Section 3.3.2 I-Frame Enhanced Control Field. |
| StaticByteBuffer inbound_iframe(kExtendedControlFBitMask, 0, 'A'); |
| auto inbound_pdu = rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, inbound_iframe, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_TRUE(inbound_pdu); |
| |
| // Check that this caused a retransmission of the two I-Frames only once each, plus our |
| // acknowledgment of the peer I-Frame. |
| EXPECT_EQ(6, tx_count); |
| EXPECT_EQ(2, iframe_0_tx_count); |
| EXPECT_EQ(2, iframe_1_tx_count); |
| |
| // Acknowledge all of the outbound I-Frames per the specification's sequence diagram. |
| StaticByteBuffer receiver_ready(0b1, 2); |
| rx_engine->ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_EQ(6, tx_count); |
| } |
| |
| } // namespace |
| } // namespace bt::l2cap::internal |