| // Copyright 2019 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_tx_engine.h" |
| |
| #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/l2cap/frame_headers.h" |
| |
| namespace bt::l2cap::internal { |
| namespace { |
| |
| constexpr ChannelId kTestChannelId = 0x0001; |
| |
| using TxEngine = EnhancedRetransmissionModeTxEngine; |
| |
| class L2CAP_EnhancedRetransmissionModeTxEngineTest : public ::gtest::TestLoopFixture { |
| public: |
| L2CAP_EnhancedRetransmissionModeTxEngineTest() : kDefaultPayload('h', 'e', 'l', 'l', 'o') {} |
| |
| protected: |
| // The default values are provided for use by tests which don't depend on the |
| // specific value a given parameter. This should make the tests easier to |
| // read, because the reader can focus on only the non-defaulted parameter |
| // values. |
| static constexpr auto kDefaultMTU = bt::l2cap::kDefaultMTU; |
| static constexpr size_t kDefaultMaxTransmissions = 1; |
| static constexpr size_t kDefaultTxWindow = 63; |
| |
| const StaticByteBuffer<5> kDefaultPayload; |
| |
| void VerifyIsReceiverReadyPollFrame(ByteBuffer* buf) { |
| ASSERT_TRUE(buf); |
| ASSERT_EQ(sizeof(SimpleSupervisoryFrame), buf->size()); |
| |
| const auto sframe = buf->As<SimpleSupervisoryFrame>(); |
| EXPECT_EQ(SupervisoryFunction::ReceiverReady, sframe.function()); |
| EXPECT_TRUE(sframe.is_poll_request()); |
| } |
| }; |
| |
| void NoOpTxCallback(ByteBufferPtr) {} |
| void NoOpFailureCallback() {} |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, QueueSduTransmitsMinimalSizedSdu) { |
| ByteBufferPtr last_pdu; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { |
| ++n_pdus; |
| last_pdu = std::move(pdu); |
| }; |
| |
| constexpr size_t kMtu = 10; |
| const auto payload = CreateStaticByteBuffer(1); |
| TxEngine(kTestChannelId, kMtu, kDefaultMaxTransmissions, kDefaultTxWindow, tx_callback, |
| NoOpFailureCallback) |
| .QueueSdu(std::make_unique<DynamicByteBuffer>(payload)); |
| EXPECT_EQ(1u, n_pdus); |
| ASSERT_TRUE(last_pdu); |
| |
| // See Core Spec v5.0, Volume 3, Part A, Table 3.2. |
| const auto expected_pdu = CreateStaticByteBuffer(0, // Final Bit, TxSeq, MustBeZeroBit |
| 0, // SAR bits, ReqSeq |
| 1); // Payload |
| EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu)); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, QueueSduTransmitsMaximalSizedSdu) { |
| ByteBufferPtr last_pdu; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { |
| ++n_pdus; |
| last_pdu = std::move(pdu); |
| }; |
| |
| constexpr size_t kMtu = 1; |
| const auto payload = CreateStaticByteBuffer(1); |
| TxEngine(kTestChannelId, kMtu, kDefaultMaxTransmissions, kDefaultTxWindow, tx_callback, |
| NoOpFailureCallback) |
| .QueueSdu(std::make_unique<DynamicByteBuffer>(payload)); |
| EXPECT_EQ(1u, n_pdus); |
| ASSERT_TRUE(last_pdu); |
| |
| // See Core Spec v5.0, Volume 3, Part A, Table 3.2. |
| const auto expected_pdu = CreateStaticByteBuffer(0, // Final Bit, TxSeq, MustBeZeroBit |
| 0, // SAR bits, ReqSeq |
| 1); // Payload |
| EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu)); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, QueueSduSurvivesOversizedSdu) { |
| // TODO(fxbug.dev/1033): Update this test when we add support for segmentation. |
| constexpr size_t kMtu = 1; |
| TxEngine(kTestChannelId, kMtu, kDefaultMaxTransmissions, kDefaultTxWindow, NoOpTxCallback, |
| NoOpFailureCallback) |
| .QueueSdu(std::make_unique<DynamicByteBuffer>(CreateStaticByteBuffer(1, 2))); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, QueueSduSurvivesZeroByteSdu) { |
| TxEngine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, NoOpTxCallback, |
| NoOpFailureCallback) |
| .QueueSdu(std::make_unique<DynamicByteBuffer>()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, QueueSduAdvancesSequenceNumber) { |
| const auto payload = CreateStaticByteBuffer(1); |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| { |
| // See Core Spec v5.0, Volume 3, Part A, Table 3.2. |
| const auto expected_pdu = CreateStaticByteBuffer(0, // Final Bit, TxSeq, MustBeZeroBit |
| 0, // SAR bits, ReqSeq |
| 1); // Payload |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(payload)); |
| ASSERT_TRUE(last_pdu); |
| EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu)); |
| } |
| |
| { |
| // See Core Spec v5.0, Volume 3, Part A, Table 3.2. |
| const auto expected_pdu = CreateStaticByteBuffer(1 << 1, // Final Bit, TxSeq=1, MustBeZeroBit |
| 0, // SAR bits, ReqSeq |
| 1); // Payload |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(payload)); |
| ASSERT_TRUE(last_pdu); |
| EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu)); |
| } |
| |
| { |
| // See Core Spec v5.0, Volume 3, Part A, Table 3.2. |
| const auto expected_pdu = CreateStaticByteBuffer(2 << 1, // Final Bit, TxSeq=2, MustBeZeroBit |
| 0, // SAR bits, ReqSeq |
| 1); // Payload |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(payload)); |
| ASSERT_TRUE(last_pdu); |
| EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu)); |
| } |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, QueueSduRollsOverSequenceNumber) { |
| constexpr size_t kTxWindow = 63; // Max possible value |
| const auto payload = CreateStaticByteBuffer(1); |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| constexpr size_t kMaxSeq = 64; |
| for (size_t i = 0; i < kMaxSeq; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(payload)); |
| tx_engine.UpdateAckSeq((i + 1) % kMaxSeq, false); |
| } |
| |
| // See Core Spec v5.0, Volume 3, Part A, Table 3.2. |
| const auto expected_pdu = |
| CreateStaticByteBuffer(0, // Final Bit, TxSeq (rolls over from 63 to 0), MustBeZeroBit |
| 0, // SAR bits, ReqSeq |
| 1); // Payload |
| last_pdu = nullptr; |
| // Free up space for more transmissions. We need room for the 64th frame from |
| // above (since the TxWindow is 63), and the new 0th frame. Hence we |
| // acknowledge original frames 0 and 1. |
| tx_engine.UpdateAckSeq(2, false); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(payload)); |
| ASSERT_TRUE(last_pdu); |
| EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu)); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, QueueSduDoesNotTransmitBeyondTxWindow) { |
| constexpr size_t kTxWindow = 1; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(1u, n_pdus); |
| |
| n_pdus = 0; |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| EXPECT_EQ(0u, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| QueueSduDoesNotTransmitBeyondTxWindowEvenIfQueueWrapsSequenceNumbers) { |
| constexpr size_t kTxWindow = 1; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(1u, n_pdus); |
| |
| constexpr size_t kMaxSeq = 64; |
| n_pdus = 0; |
| for (size_t i = 0; i < kMaxSeq; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(0u, n_pdus); |
| } |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, EngineTransmitsReceiverReadyPollAfterTimeout) { |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| SCOPED_TRACE(""); |
| VerifyIsReceiverReadyPollFrame(last_pdu.get()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineTransmitsReceiverReadyPollOnlyOnceAfterTimeout) { |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| SCOPED_TRACE(""); |
| RETURN_IF_FATAL(VerifyIsReceiverReadyPollFrame(last_pdu.get())); |
| last_pdu = nullptr; |
| |
| // Note: This value is chosen to be at least as long as |
| // kReceiverReadyPollTimerDuration, but shorter than kMonitorTimerDuration. |
| EXPECT_FALSE(RunLoopFor(zx::sec(2))); // No tasks were run. |
| EXPECT_FALSE(last_pdu); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineAdvancesReceiverReadyPollTimeoutOnNewTransmission) { |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| ASSERT_FALSE(RunLoopFor(zx::sec(1))); // No events should fire. |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| last_pdu = nullptr; |
| |
| ASSERT_FALSE(RunLoopFor(zx::sec(1))); // Original timeout should not fire. |
| ASSERT_TRUE(RunLoopFor(zx::sec(1))); // New timeout should fire. |
| SCOPED_TRACE(""); |
| VerifyIsReceiverReadyPollFrame(last_pdu.get()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| ReceiverReadyPollIncludesRequestSequenceNumber) { |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| tx_engine.UpdateReqSeq(1); |
| RunLoopUntilIdle(); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| last_pdu = nullptr; |
| |
| SCOPED_TRACE(""); |
| EXPECT_TRUE(RunLoopFor(zx::sec(2))); |
| ASSERT_NO_FATAL_FAILURE(VerifyIsReceiverReadyPollFrame(last_pdu.get())); |
| EXPECT_EQ(1u, last_pdu->As<SimpleSupervisoryFrame>().receive_seq_num()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| AckOfOnlyOutstandingFrameCancelsReceiverReadyPollTimeout) { |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| tx_engine.UpdateAckSeq(1, false); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(RunLoopFor(zx::sec(2))); // No tasks were run. |
| EXPECT_FALSE(last_pdu); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| AckOfAllOutstandingFramesCancelsReceiverReadyPollTimeout) { |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| tx_engine.UpdateAckSeq(3, false); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(RunLoopFor(zx::sec(2))); // No tasks were run. |
| EXPECT_FALSE(last_pdu); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| PartialAckDoesNotCancelReceiverReadyPollTimeout) { |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| tx_engine.UpdateAckSeq(1, false); |
| RunLoopUntilIdle(); |
| |
| // See Core Spec v5.0, Volume 3, Part A, Sec 8.6.5.6, under heading |
| // Process-ReqSeq. We should only Stop-RetransTimer if UnackedFrames is 0. |
| SCOPED_TRACE(""); |
| EXPECT_TRUE(RunLoopFor(zx::sec(2))); |
| VerifyIsReceiverReadyPollFrame(last_pdu.get()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| NewTransmissionAfterAckedFrameReArmsReceiverReadyPollTimeout) { |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| // Send a frame, and get the ACK. |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| tx_engine.UpdateAckSeq(1, false); |
| RunLoopUntilIdle(); |
| |
| // Send a new frame. |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| last_pdu = nullptr; |
| |
| // Having earlier received an ACK for the previous frame should not have left |
| // around any state that would prevent us from sending a receiver-ready poll |
| // for the second frame. |
| SCOPED_TRACE(""); |
| EXPECT_TRUE(RunLoopFor(zx::sec(2))); |
| VerifyIsReceiverReadyPollFrame(last_pdu.get()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineRetransmitsReceiverReadyPollAfterMonitorTimeout) { |
| constexpr size_t kMaxTransmissions = 2; // Allow retransmission |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| // First the receiver_ready_poll_task_ fires. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| ASSERT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| // Then the monitor_task_ fires. |
| EXPECT_TRUE(RunLoopFor(zx::sec(12))); |
| VerifyIsReceiverReadyPollFrame(last_pdu.get()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineDoesNotRetransmitReceiverReadyPollAfterMonitorTimeoutWhenRetransmissionsAreDisabled) { |
| constexpr size_t kMaxTransmissions = 1; |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| // First the receiver_ready_poll_task_ fires. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| ASSERT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| // Run the event loop long enough for the monitor task to fire again. Because |
| // kMaxTransmissions == 1, the ReceiverReadyPoll should not be retransmitted. |
| RunLoopFor(zx::sec(13)); |
| EXPECT_FALSE(last_pdu); |
| } |
| |
| // See Core Spec v5.0, Volume 3, Part A, Sec 5.4, Table 8.6.5.8, for the row |
| // with "Recv ReqSeqAndFbit" and "F = 1". |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineStopsPollingReceiverReadyFromMonitorTaskAfterReceivingFinalUpdateForAckSeq) { |
| constexpr size_t kMaxTransmissions = 3; // Allow multiple retransmissions |
| ByteBufferPtr last_pdu; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, |
| NoOpTxCallback, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); // receiver_ready_poll_task_ |
| ASSERT_TRUE(RunLoopFor(zx::sec(12))); // monitor_task_ |
| tx_engine.UpdateAckSeq(1, true); |
| EXPECT_FALSE(RunLoopFor(zx::sec(13))); // No other tasks. |
| } |
| |
| // See Core Spec v5.0, Volume 3, Part A, Sec 5.4, Table 8.6.5.8, for the row |
| // with "Recv ReqSeqAndFbit" and "F = 0". |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineContinuesPollingReceiverReadyFromMonitorTaskAfterReceivingNonFinalUpdateForAckSeq) { |
| constexpr size_t kMaxTransmissions = 2; // Allow retransmissions |
| ByteBufferPtr last_pdu; |
| TxEngine tx_engine( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, |
| [&](auto pdu) { last_pdu = std::move(pdu); }, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); // receiver_ready_poll_task_ |
| ASSERT_TRUE(RunLoopFor(zx::sec(12))); // monitor_task_ |
| tx_engine.UpdateAckSeq(1, false); |
| EXPECT_TRUE(RunLoopFor(zx::sec(12))); // monitor_task_ |
| VerifyIsReceiverReadyPollFrame(last_pdu.get()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineRetransmitsReceiverReadyPollAfterMultipleMonitorTimeouts) { |
| constexpr size_t kMaxTransmissions = 3; // Allow multiple retransmissions |
| ByteBufferPtr last_pdu; |
| TxEngine tx_engine( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, |
| [&](auto pdu) { last_pdu = std::move(pdu); }, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); // receiver_ready_poll_task_ |
| ASSERT_TRUE(RunLoopFor(zx::sec(12))); // monitor_task_ |
| ASSERT_FALSE(RunLoopFor(zx::sec(2))); // RR-poll task does _not_ fire |
| last_pdu = nullptr; |
| |
| EXPECT_TRUE(RunLoopFor(zx::sec(10))); // monitor_task_ again |
| VerifyIsReceiverReadyPollFrame(last_pdu.get()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineRetransmitsReceiverReadyPollIndefinitelyAfterMonitorTimeoutWhenMaxTransmitsIsZero) { |
| constexpr size_t kMaxTransmissions = 0; |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| // First the receiver_ready_poll_task_ fires. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| EXPECT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| // Then the monitor_task_ fires. |
| EXPECT_TRUE(RunLoopFor(zx::sec(12))); |
| EXPECT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| // And the monitor_task_ fires again. |
| EXPECT_TRUE(RunLoopFor(zx::sec(12))); |
| EXPECT_TRUE(last_pdu); |
| last_pdu = nullptr; |
| |
| // And the monitor_task_ fires yet again. |
| EXPECT_TRUE(RunLoopFor(zx::sec(12))); |
| EXPECT_TRUE(last_pdu); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineStopsTransmittingReceiverReadyPollAfterMaxTransmits) { |
| constexpr size_t kMaxTransmissions = 2; |
| ByteBufferPtr last_pdu; |
| TxEngine tx_engine( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, |
| [&](auto pdu) { last_pdu = std::move(pdu); }, NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); // receiver_ready_poll_task_ |
| ASSERT_TRUE(RunLoopFor(zx::sec(12))); // monitor_task_ |
| ASSERT_TRUE(RunLoopFor(zx::sec(12))); // monitor_task_ |
| last_pdu = nullptr; |
| |
| EXPECT_FALSE(RunLoopFor(zx::sec(13))); |
| EXPECT_FALSE(last_pdu); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineClosesChannelAfterMaxTransmitsOfReceiverReadyPoll) { |
| constexpr size_t kMaxTransmissions = 2; |
| bool connection_failed = false; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, |
| NoOpTxCallback, [&] { connection_failed = true; }); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); // receiver_ready_poll_task_ |
| ASSERT_TRUE(RunLoopFor(zx::sec(12))); // monitor_task_ |
| ASSERT_TRUE(RunLoopFor(zx::sec(12))); // monitor_task_ |
| EXPECT_TRUE(connection_failed); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineClosesChannelAfterMaxTransmitsOfReceiverReadyPollEvenIfRetransmissionsAreDisabled) { |
| constexpr size_t kMaxTransmissions = 1; |
| bool connection_failed = false; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, |
| NoOpTxCallback, [&] { connection_failed = true; }); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); // receiver_ready_poll_task_ |
| ASSERT_TRUE(RunLoopFor(zx::sec(12))); // monitor_task_ |
| EXPECT_TRUE(connection_failed); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, EngineClosesChannelAfterMaxTransmitsOfIFrame) { |
| constexpr size_t kMaxTransmissions = 2; |
| size_t num_info_frames_sent = 0; |
| bool connection_failed = false; |
| TxEngine tx_engine( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, |
| [&](ByteBufferPtr pdu) { |
| if (pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame()) { |
| ++num_info_frames_sent; |
| } |
| }, |
| [&] { connection_failed = true; }); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(1u, num_info_frames_sent); |
| |
| // Not having received an acknowledgement after 2 seconds, |
| // receiver_ready_poll_task_ will fire, and cause us to send a |
| // ReceiverReadyPoll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // The peer indicates that it has not received any frames. This causes us to |
| // retransmit the frame. |
| tx_engine.UpdateAckSeq(0, true); |
| EXPECT_EQ(2u, num_info_frames_sent); |
| |
| // Not having received an acknowledgement after 2 seconds, |
| // receiver_ready_poll_task_ will fire, and cause us to send another |
| // ReceiverReadyPoll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // The connection should remain open, to allow the peer time to respond to our |
| // poll, and acknowledge the outstanding frame. |
| EXPECT_FALSE(connection_failed); |
| |
| // The peer again indicates that it has not received any frames. |
| tx_engine.UpdateAckSeq(0, true); |
| |
| // Because we've exhausted kMaxTransmissions, the connection will be closed. |
| EXPECT_TRUE(connection_failed); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineExhaustsAllRetransmissionsOfIFrameBeforeClosingChannel) { |
| constexpr size_t kMaxTransmissions = 255; |
| size_t num_info_frames_sent = 0; |
| bool connection_failed = false; |
| TxEngine tx_engine( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, |
| [&](ByteBufferPtr pdu) { |
| if (pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame()) { |
| ++num_info_frames_sent; |
| } |
| }, |
| [&] { connection_failed = true; }); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| for (size_t i = 0; i < kMaxTransmissions; ++i) { |
| // Not having received an acknowledgement after 2 seconds, |
| // receiver_ready_poll_task_ will fire, and cause us to send a |
| // ReceiverReadyPoll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))) << "(i=" << i << ")"; |
| |
| // The connection should remain open, to allow the peer time to respond to |
| // our poll, and acknowledge the outstanding frame. |
| EXPECT_FALSE(connection_failed); |
| |
| // The peer indicates that it has not received any frames. |
| tx_engine.UpdateAckSeq(0, true); |
| } |
| |
| // The connection is closed, and we've exhausted kMaxTransmissions. |
| EXPECT_TRUE(connection_failed); |
| EXPECT_EQ(255u, num_info_frames_sent); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineRetransmitsIFrameIndefinitelyWhenMaxTransmitsIsZero) { |
| constexpr size_t kMaxTransmissions = 0; |
| constexpr size_t kTxWindow = 2; |
| size_t num_info_frames_sent = 0; |
| bool connection_failed = false; |
| ByteBufferPtr last_tx_frame = nullptr; |
| TxEngine tx_engine( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, |
| [&](ByteBufferPtr pdu) { |
| if (pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame()) { |
| ++num_info_frames_sent; |
| last_tx_frame = std::move(pdu); |
| } |
| }, |
| [&] { connection_failed = true; }); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| EXPECT_EQ(1u, num_info_frames_sent); |
| |
| // This test should trigger enough transmissions to overflow an 8-bit transmit counter. |
| const auto kNumTransmissions = size_t{2} * std::numeric_limits<uint8_t>::max(); |
| |
| // Retransmissions after the initial one triggered by QueueSdu |
| for (size_t i = 0; i < kNumTransmissions - 1; ++i) { |
| // Not having received an acknowledgement after 2 seconds, receiver_ready_poll_task_ will fire, |
| // and cause us to send a ReceiverReadyPoll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))) << "(i=" << i << ")"; |
| |
| // The connection should remain open, to allow the peer time to respond to our poll, and |
| // acknowledge the outstanding frame. |
| EXPECT_FALSE(connection_failed); |
| |
| // The peer indicates that it has not received any frames. |
| tx_engine.UpdateAckSeq(0, true); |
| } |
| |
| // The connection is still open and we have transmitted more times than the greatest possible |
| // finite MaxTransmit option (which is 8-bit). |
| EXPECT_FALSE(connection_failed); |
| EXPECT_EQ(kNumTransmissions, num_info_frames_sent); |
| |
| // Check that the outbound frame's transmit count hasn't overflowed to zero by transmitting a |
| // different payload. |
| ASSERT_TRUE(ContainersEqual(kDefaultPayload, last_tx_frame->view(sizeof(EnhancedControlField)))); |
| num_info_frames_sent = 0; |
| |
| // If the first frame's transmit count had overflowed to zero, it would get sent out together |
| // with this new SDU because it would be indistinguishable from "never transmitted." |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(StaticByteBuffer('@'))); |
| EXPECT_EQ(1u, num_info_frames_sent); |
| EXPECT_EQ('@', (*last_tx_frame)[sizeof(EnhancedControlField)]); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineClosesChannelAfterMaxTransmitsOfIFrameEvenIfRetransmissionsAreDisabled) { |
| constexpr size_t kMaxTransmissions = 1; |
| bool connection_failed = false; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, |
| NoOpTxCallback, [&] { connection_failed = true; }); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| // Not having received an acknowledgement after 2 seconds, |
| // receiver_ready_poll_task_ will fire, and cause us to send a |
| // ReceiverReadyPoll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // The connection should remain open, to allow the peer time to respond to our |
| // poll, and acknowledge the outstanding frame. |
| EXPECT_FALSE(connection_failed); |
| |
| // The peer indicates that it has not received any frames. |
| tx_engine.UpdateAckSeq(0, true); |
| EXPECT_TRUE(connection_failed); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, EngineRetransmitsMissingFrameOnPollResponse) { |
| constexpr size_t kMaxTransmissions = 2; |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { last_pdu = std::move(pdu); }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| last_pdu = nullptr; |
| |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); // receiver_ready_poll_task_ |
| last_pdu = nullptr; |
| |
| tx_engine.UpdateAckSeq(0, true); |
| ASSERT_TRUE(last_pdu); |
| ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader)); |
| ASSERT_TRUE(last_pdu->As<EnhancedControlField>().designates_information_frame()); |
| EXPECT_EQ(0u, last_pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineRetransmitsAllMissingFramesOnPollResponse) { |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 63; |
| size_t n_pdus = 0; |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { |
| ++n_pdus; |
| last_pdu = std::move(pdu); |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| // Send a TxWindow's worth of frames. |
| for (size_t i = 0; i < kTxWindow; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire, and clear out accumulated callback |
| // state. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| n_pdus = 0; |
| last_pdu = nullptr; |
| |
| tx_engine.UpdateAckSeq(0, true); |
| EXPECT_EQ(kTxWindow, n_pdus); |
| ASSERT_TRUE(last_pdu); |
| ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader)); |
| ASSERT_TRUE(last_pdu->As<EnhancedControlField>().designates_information_frame()); |
| EXPECT_EQ(kTxWindow - 1, last_pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineRetransmitsAllMissingFramesOnPollResponseWithWrappedSequenceNumber) { |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 63; |
| size_t n_pdus = 0; |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { |
| ++n_pdus; |
| last_pdu = std::move(pdu); |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| // Send a TxWindow's worth of frames. |
| for (size_t i = 0; i < kTxWindow; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| |
| // Acknowledge the first 32 of these frames (with sequence numbers 0...31). |
| tx_engine.UpdateAckSeq(32, false); |
| |
| // Queue 32 new frames (with sequence numbers 63, 0 ... 30). |
| for (size_t i = 0; i < 32; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire, and then clear out accumulated callback |
| // state. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| n_pdus = 0; |
| last_pdu = nullptr; |
| |
| // Repeat the earlier acknowledgement. This indicates that the peer has |
| // not received frame 32. |
| tx_engine.UpdateAckSeq(32, true); |
| |
| // We expect to retransmit frames 32...63, and then 0...30. That's 63 frames |
| // in total. |
| EXPECT_EQ(63u, n_pdus); |
| ASSERT_TRUE(last_pdu); |
| ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader)); |
| ASSERT_TRUE(last_pdu->As<EnhancedControlField>().designates_information_frame()); |
| EXPECT_EQ(30u, last_pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineProperlyHandlesPartialAckWithWrappedSequenceNumber) { |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 63; |
| size_t n_pdus = 0; |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { |
| ++n_pdus; |
| last_pdu = std::move(pdu); |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| // Send a TxWindow's worth of frames. |
| for (size_t i = 0; i < kTxWindow; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| |
| // Acknowledge the first 62 of these frames (with sequence numbers 0...61). |
| tx_engine.UpdateAckSeq(62, false); |
| |
| // Queue 62 new frames. These frames have sequence numebrs 63, 0...60. |
| // (Sequence number 62 was used when we queued the first batch of frames |
| // above.) |
| for (size_t i = 0; i < 62; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire, and then clear out accumulated callback |
| // state. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| n_pdus = 0; |
| last_pdu = nullptr; |
| |
| // Acknowledge an additional 5 frames (with sequence numbers 62, 63, 0, 1, 2). |
| tx_engine.UpdateAckSeq(3, true); |
| |
| // Verify that all unacknowledged frames are retransmitted. |
| EXPECT_EQ(58u, n_pdus); |
| ASSERT_TRUE(last_pdu); |
| ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader)); |
| ASSERT_TRUE(last_pdu->As<EnhancedControlField>().designates_information_frame()); |
| EXPECT_EQ(60u, last_pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, EngineDoesNotRetransmitFramesBeyondTxWindow) { |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 32; |
| size_t n_pdus = 0; |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { |
| ++n_pdus; |
| last_pdu = std::move(pdu); |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| // Queue two TxWindow's worth of frames. These have sequence numbers 0...63. |
| for (size_t i = 0; i < 2 * kTxWindow; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire, and clear out accumulated callback |
| // state. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| n_pdus = 0; |
| last_pdu = nullptr; |
| |
| tx_engine.UpdateAckSeq(0, true); |
| EXPECT_EQ(kTxWindow, n_pdus); |
| ASSERT_TRUE(last_pdu); |
| ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader)); |
| EXPECT_EQ(kTxWindow - 1, last_pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineDoesNotRetransmitFramesBeyondTxWindowWhenWindowWraps) { |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 48; |
| size_t n_pdus = 0; |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { |
| ++n_pdus; |
| last_pdu = std::move(pdu); |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| // Queue one TxWindow's worth of frames. This advances the sequence numbers, |
| // so that further transmissions can wrap. |
| for (size_t i = 0; i < 48; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| tx_engine.UpdateAckSeq(48, false); |
| RunLoopUntilIdle(); |
| |
| // Queue another TxWindow's worth of frames. These have sequence |
| // numbers 48..63, and 0..31. These _should_ be retransmitted at the next |
| // UpdateAckSeq() call. |
| for (size_t i = 0; i < 48; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| RunLoopUntilIdle(); |
| |
| // Queue a few more frames, with sequence numbers 32..39. These should _not_ |
| // be retransmitted at the next UpdateAckSeq() call. |
| for (size_t i = 0; i < 8; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire, and clear out accumulated callback |
| // state. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| n_pdus = 0; |
| last_pdu = nullptr; |
| |
| // Report that the peer has not received frame 48. This should trigger |
| // retranmissions of unacknowledged frames within the TxWindow. |
| tx_engine.UpdateAckSeq(48, true); |
| |
| // We expect to retransmit frames 48..63 and 0..31. The other frames are |
| // beyond the transmit window. |
| EXPECT_EQ(48u, n_pdus); |
| ASSERT_TRUE(last_pdu); |
| ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader)); |
| ASSERT_TRUE(last_pdu->As<EnhancedControlField>().designates_information_frame()); |
| EXPECT_EQ(31u, last_pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineDoesNotRetransmitPreviouslyAckedFramesOnPollResponse) { |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 2; |
| size_t n_pdus = 0; |
| ByteBufferPtr last_pdu; |
| auto tx_callback = [&](auto pdu) { |
| ++n_pdus; |
| last_pdu = std::move(pdu); |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire, and clear out accumulated callback |
| // state. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| n_pdus = 0; |
| last_pdu = nullptr; |
| |
| constexpr size_t kPollResponseReqSeq = 1; |
| tx_engine.UpdateAckSeq(kPollResponseReqSeq, true); |
| EXPECT_EQ(kTxWindow - kPollResponseReqSeq, n_pdus); |
| ASSERT_TRUE(last_pdu); |
| ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader)); |
| ASSERT_TRUE(last_pdu->As<EnhancedControlField>().designates_information_frame()); |
| EXPECT_EQ(1, last_pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, AckOfFrameWithNoneOutstandingClosesChannel) { |
| bool connection_failed = false; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| NoOpTxCallback, [&] { connection_failed = true; }); |
| |
| tx_engine.UpdateAckSeq(1, false); |
| EXPECT_TRUE(connection_failed); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| AckOfMoreFramesThanAreOutstandingClosesChannel) { |
| bool connection_failed = false; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| NoOpTxCallback, [&] { connection_failed = true; }); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.UpdateAckSeq(2, true); |
| EXPECT_TRUE(connection_failed); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, EngineDoesNotCrashOnSpuriousAckAfterValidAck) { |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| NoOpTxCallback, NoOpFailureCallback); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| tx_engine.UpdateAckSeq(1, true); |
| tx_engine.UpdateAckSeq(2, true); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineDoesNotCrashOnSpuriousAckBeforeAnyDataHasBeenSent) { |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| NoOpTxCallback, NoOpFailureCallback); |
| for (size_t i = 0; i <= EnhancedControlField::kMaxSeqNum; ++i) { |
| tx_engine.UpdateAckSeq(i, true); |
| } |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| QueueSduDoesNotTransmitFramesWhenRemoteIsBusy) { |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.SetRemoteBusy(); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, UpdateAckSeqTransmitsQueuedDataWhenPossible) { |
| constexpr size_t kTxWindow = 1; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(1u, n_pdus); |
| |
| n_pdus = 0; |
| tx_engine.UpdateAckSeq(1, false); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1u, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| UpdateAckSeqTransmissionOfQueuedDataRespectsTxWindow) { |
| constexpr size_t kTxWindow = 1; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(1u, n_pdus); |
| |
| n_pdus = 0; |
| tx_engine.UpdateAckSeq(1, false); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1u, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| NonFinalUpdateAckSeqDoesNotTransmitQueuedFramesWhenRemoteIsBusy) { |
| constexpr size_t kTxWindow = 1; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(1u, n_pdus); |
| |
| n_pdus = 0; |
| tx_engine.SetRemoteBusy(); |
| tx_engine.UpdateAckSeq(1, false); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| FinalUpdateAckSeqDoesNotTransmitQueuedFramesWhenRemoteIsBusy) { |
| constexpr size_t kTxWindow = 1; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(1u, n_pdus); |
| |
| n_pdus = 0; |
| tx_engine.SetRemoteBusy(); |
| tx_engine.UpdateAckSeq(1, true); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| MaybeSendQueuedDataTransmitsAllQueuedFramesWithinTxWindow) { |
| constexpr size_t kTxWindow = 63; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.SetRemoteBusy(); |
| for (size_t i = 0; i < kTxWindow; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| RunLoopUntilIdle(); |
| ASSERT_EQ(0u, n_pdus); |
| |
| tx_engine.ClearRemoteBusy(); |
| tx_engine.MaybeSendQueuedData(); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(kTxWindow, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| MaybeSendQueuedDataDoesNotTransmitBeyondTxWindow) { |
| constexpr size_t kTxWindow = 32; |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.SetRemoteBusy(); |
| for (size_t i = 0; i < kTxWindow + 1; ++i) { |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| RunLoopUntilIdle(); |
| ASSERT_EQ(0u, n_pdus); |
| |
| tx_engine.ClearRemoteBusy(); |
| tx_engine.MaybeSendQueuedData(); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(kTxWindow, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, MaybeSendQueuedDataRespectsRemoteBusy) { |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.SetRemoteBusy(); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(0u, n_pdus); |
| |
| tx_engine.MaybeSendQueuedData(); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| MaybeSendQueuedDataDoesNotCrashWhenCalledWithoutPendingPdus) { |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| NoOpTxCallback, NoOpFailureCallback); |
| tx_engine.MaybeSendQueuedData(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| QueueSduCanSendMoreFramesAfterClearingRemoteBusy) { |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.SetRemoteBusy(); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(0u, n_pdus); |
| |
| tx_engine.ClearRemoteBusy(); |
| tx_engine.MaybeSendQueuedData(); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(1u, n_pdus); |
| n_pdus = 0; |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(1u, n_pdus); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| QueueSduMaintainsSduOrderingAfterClearRemoteBusy) { |
| std::vector<uint8_t> pdu_seq_numbers; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| pdu_seq_numbers.push_back(pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.SetRemoteBusy(); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); // seq=0 |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(pdu_seq_numbers.empty()); |
| |
| tx_engine.ClearRemoteBusy(); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); // seq=1 |
| RunLoopUntilIdle(); |
| |
| // This requirement isn't in the specification directly. But it seems |
| // necessary given that we can sometimes exit the remote-busy condition |
| // without transmitting the queued data. See Core Spec v5.0, Volume 3, Part A, |
| // Table 8.7, row for "Recv RR (P=1)" for an example of such an operation. |
| EXPECT_EQ((std::vector<uint8_t>{0, 1}), pdu_seq_numbers); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| UpdateAckSeqRetransmitsUnackedFramesBeforeTransmittingQueuedFrames) { |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 63; |
| std::vector<uint8_t> pdu_seq_numbers; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| pdu_seq_numbers.push_back(pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| // Send out two frames. |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(2u, pdu_seq_numbers.size()); |
| pdu_seq_numbers.clear(); |
| |
| // Indicate the remote is busy, and queue a third frame. |
| tx_engine.SetRemoteBusy(); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(pdu_seq_numbers.empty()); |
| |
| // Clear the busy condition, and then report the new ack sequence number. |
| // Because the ack only acknowledges the first frame, the second frame should |
| // be retransmitted. And that retransmission should come before the (initial) |
| // transmission of the third frame. |
| tx_engine.ClearRemoteBusy(); |
| tx_engine.UpdateAckSeq(1, true); |
| RunLoopUntilIdle(); |
| EXPECT_EQ((std::vector{uint8_t{1}, uint8_t{2}}), pdu_seq_numbers); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| QueueSduDoesNotTransmitNewFrameWhenEngineIsAwaitingPollResponse) { |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 3; |
| std::vector<uint8_t> pdu_seq_numbers; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| pdu_seq_numbers.push_back(pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire. This moves the engine into the 'WAIT_F' |
| // state. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // Queue a new frame. |
| pdu_seq_numbers.clear(); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(std::vector<uint8_t>(), pdu_seq_numbers); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| NonFinalUpdateAckSeqDoesNotTransmitNewFrameWhenEngineIsAwaitingPollResponse) { |
| constexpr size_t kMaxTransmissions = 2; |
| constexpr size_t kTxWindow = 1; |
| std::vector<uint8_t> pdu_seq_numbers; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| pdu_seq_numbers.push_back(pdu->As<SimpleInformationFrameHeader>().tx_seq()); |
| } |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire. This moves the engine into the 'WAIT_F' |
| // state. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // Acknowledge the first frame, making room for the transmission of the second |
| // frame. |
| pdu_seq_numbers.clear(); |
| tx_engine.UpdateAckSeq(1, false); |
| RunLoopUntilIdle(); |
| |
| // Because we're still in the WAIT_F state, the second frame should _not_ be |
| // transmitted. |
| EXPECT_EQ(pdu_seq_numbers.end(), std::find(pdu_seq_numbers.begin(), pdu_seq_numbers.end(), 1)); |
| } |
| |
| // Note: to make the most of this test, the unit tests should be built with |
| // ASAN. |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineDoesNotCrashIfExhaustionOfMaxTransmitForIFrameCausesEngineDestruction) { |
| constexpr size_t kMaxTransmissions = 1; |
| constexpr size_t kTxWindow = 2; |
| bool connection_failed = false; |
| std::unique_ptr<TxEngine> tx_engine = std::make_unique<TxEngine>( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kTxWindow, NoOpTxCallback, [&] { |
| connection_failed = true; |
| tx_engine.reset(); |
| }); |
| |
| // Queue three SDUs, of which two should be transmitted immediately. |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire. This moves the engine into the 'WAIT_F' |
| // state. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // Acknowledge the first frame, making room for the transmission of the third |
| // frame. If the code is buggy, we may encounter use-after-free errors here. |
| tx_engine->UpdateAckSeq(1, true); |
| RunLoopUntilIdle(); |
| |
| // Because we only allow one transmission, the connection should have failed. |
| EXPECT_TRUE(connection_failed); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| EngineDoesNotCrashIfExhaustionOfMaxTransmitForReceiverReadyPollCausesEngineDestruction) { |
| constexpr size_t kMaxTransmissions = 1; |
| bool connection_failed = false; |
| std::unique_ptr<TxEngine> tx_engine = std::make_unique<TxEngine>( |
| kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, NoOpTxCallback, [&] { |
| connection_failed = true; |
| tx_engine.reset(); |
| }); |
| |
| tx_engine->QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // Let monitor_task_ fire, to attempt retransmission of the poll. The |
| // retransmission should fail, because we have exhausted kMaxTransmissions. If |
| // the code is buggy, we may encounter use-after-free errors here. |
| ASSERT_TRUE(RunLoopFor(zx::sec(12))); |
| |
| EXPECT_TRUE(connection_failed); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, TransmissionOfPduIncludesRequestSeqNum) { |
| uint8_t outbound_req_seq = 0; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| outbound_req_seq = pdu->As<SimpleInformationFrameHeader>().receive_seq_num(); |
| } |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kDefaultTxWindow, |
| tx_callback, NoOpFailureCallback); |
| |
| tx_engine.UpdateReqSeq(5); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(5u, outbound_req_seq); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| DeferredTransmissionOfPduIncludesCurrentRequestSeqNum) { |
| constexpr size_t kTxWindow = 1; |
| uint8_t outbound_req_seq = 0; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| outbound_req_seq = pdu->As<SimpleInformationFrameHeader>().receive_seq_num(); |
| } |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kDefaultMaxTransmissions, kTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| |
| tx_engine.UpdateReqSeq(5); |
| tx_engine.UpdateAckSeq(1, true); // Peer acks first PDU. |
| RunLoopUntilIdle(); |
| |
| // The second PDU should have been transmitted with ReqSeq = 5. |
| EXPECT_EQ(5u, outbound_req_seq); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, RetransmissionOfPduIncludesCurrentSeqNum) { |
| constexpr size_t kMaxTransmissions = 2; |
| uint8_t outbound_req_seq = 0; |
| size_t n_info_frames = 0; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| ++n_info_frames; |
| outbound_req_seq = pdu->As<SimpleInformationFrameHeader>().receive_seq_num(); |
| } |
| }; |
| TxEngine tx_engine(kTestChannelId, kDefaultMTU, kMaxTransmissions, kDefaultTxWindow, tx_callback, |
| NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, outbound_req_seq); |
| |
| // The receive engine reports that it has received new data from our peer. |
| tx_engine.UpdateReqSeq(10); |
| |
| // Let receiver_ready_poll_task_ fire. This triggers us to query if our peer |
| // has received our data. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // Our peer indicates that it has not received our data. |
| tx_engine.UpdateAckSeq(0, true); |
| RunLoopUntilIdle(); |
| |
| // Our retransmission should include our current request sequence number. |
| EXPECT_EQ(2u, n_info_frames); |
| EXPECT_EQ(10u, outbound_req_seq); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, PollTaskLoopsEvenWhenRemoteIsBusy) { |
| size_t n_pdus = 0; |
| auto tx_callback = [&](auto pdu) { ++n_pdus; }; |
| bool failure = false; |
| auto connection_failure_callback = [&] { failure = true; }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/2, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/connection_failure_callback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(1u, n_pdus); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| ASSERT_EQ(2u, n_pdus); |
| |
| // Receive a "set RemoteBusy" in the poll response (RNR with F=1). |
| n_pdus = 0; |
| tx_engine.SetRemoteBusy(); |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/true); |
| ASSERT_EQ(0u, n_pdus); |
| |
| // No polls should have been sent out and the monitor task should not have fired. |
| RunLoopFor(zx::sec(12)); |
| EXPECT_EQ(0u, n_pdus); |
| EXPECT_FALSE(failure); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetRangeRetransmitCausesUpdateAckSeqToRetransmit) { |
| size_t n_info_frames = 0; |
| std::optional<uint8_t> tx_seq; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| ++n_info_frames; |
| tx_seq = pdu->As<SimpleInformationFrameHeader>().tx_seq(); |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/4, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(3u, n_info_frames); |
| |
| // Request a retransmission of unacked data starting with TxSeq=1 (so the second and third |
| // frames). |
| n_info_frames = 0; |
| tx_seq.reset(); |
| tx_engine.SetRangeRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| EXPECT_EQ(2u, n_info_frames); |
| EXPECT_EQ(2u, tx_seq.value_or(0)); |
| |
| // Subsequent UpdateAckSeqs do not perform retransmission. |
| n_info_frames = 0; |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| EXPECT_EQ(0u, n_info_frames); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetRangeRetransmitWithPollRequestTriggersPollResponseOnFirstIFrame) { |
| size_t n_info_frames = 0; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| // We send two I-Frames to start, then retransmit both but the third frame is in response to a |
| // poll request so its and only its poll response bit should be set. |
| SCOPED_TRACE(n_info_frames); |
| EXPECT_EQ(n_info_frames == 2, pdu->As<EnhancedControlField>().is_poll_response()); |
| ++n_info_frames; |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/3, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(2u, n_info_frames); |
| |
| // Request a retransmission of unacked data starting with TxSeq=0 (all frames). |
| tx_engine.SetRangeRetransmit(/*is_poll_request=*/true); |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/false); |
| EXPECT_EQ(4u, n_info_frames); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetRangeRetransmitAfterPollTaskSuppressesSubsequentRetransmitByPollResponse) { |
| size_t n_info_frames = 0; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| ++n_info_frames; |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/4, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(1u, n_info_frames); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // Request a retransmission of unacked data starting with TxSeq=0. |
| tx_engine.SetRangeRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/false); |
| ASSERT_EQ(2u, n_info_frames); |
| |
| // Don't acknowledge data at TxSeq=0 again in the poll response, which _would_ retransmit them if |
| // they hadn't already been retransmitted. |
| n_info_frames = 0; |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/true); |
| EXPECT_EQ(0u, n_info_frames); |
| |
| // And further non-acknowledgments that don't call for retransmission shouldn't cause |
| // retransmission. |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/false); |
| EXPECT_EQ(0u, n_info_frames); |
| |
| // Or further acknowledgments, for that matter. |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| EXPECT_EQ(0u, n_info_frames); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetRangeRetransmitWithPollResponseSetDoesNotSuppressSubsequentRetransmissions) { |
| size_t n_info_frames = 0; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| ++n_info_frames; |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/4, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(1u, n_info_frames); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // Request a retransmission of unacked data starting with TxSeq=0 that also acknowledges the poll. |
| tx_engine.SetRangeRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/true); |
| ASSERT_EQ(2u, n_info_frames); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // Don't acknowledge data at TxSeq=0 again in the next poll response, which causes another |
| // retransmission. |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/true); |
| EXPECT_EQ(3u, n_info_frames); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetRangeRetransmitSuppressesRetransmissionsByRetransmitRangeWithPollResponse) { |
| size_t n_info_frames = 0; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| ++n_info_frames; |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/4, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(1u, n_info_frames); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| // Request a retransmission of unacked data starting with TxSeq=0. |
| tx_engine.SetRangeRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/false); |
| ASSERT_EQ(2u, n_info_frames); |
| |
| // Request retransmission _and_ don't acknowledge data at TxSeq=0, which _would_ retransmit them |
| // if they hadn't already been retransmitted. |
| n_info_frames = 0; |
| tx_engine.SetRangeRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/true); |
| EXPECT_EQ(0u, n_info_frames); |
| |
| // And further non-acknowledgments that don't call for retransmission shouldn't cause |
| // retransmission. |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/false); |
| EXPECT_EQ(0u, n_info_frames); |
| |
| // Or further acknowledgments, for that matter. |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| EXPECT_EQ(0u, n_info_frames); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetSingleRetransmitCausesUpdateAckSeqToRetransmit) { |
| size_t n_info_frames = 0; |
| std::optional<uint8_t> tx_seq; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| ++n_info_frames; |
| tx_seq = pdu->As<SimpleInformationFrameHeader>().tx_seq(); |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/4, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(3u, n_info_frames); |
| |
| // Request a retransmission of unacked data with TxSeq=1 (so only the second frame). |
| n_info_frames = 0; |
| tx_seq.reset(); |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| EXPECT_EQ(1u, n_info_frames); |
| EXPECT_EQ(1u, tx_seq.value_or(0)); |
| |
| // Subsequent UpdateAckSeqs do not perform retransmission. |
| n_info_frames = 0; |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| EXPECT_EQ(0u, n_info_frames); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetSingleRetransmitWithPollRequestTriggersPollResponseOnIFrame) { |
| size_t n_info_frames = 0; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| // We send two I-Frames to start, then retransmit both but the third frame is in response to a |
| // poll request so its and only its poll response bit should be set. |
| SCOPED_TRACE(n_info_frames); |
| EXPECT_EQ(n_info_frames == 2, pdu->As<EnhancedControlField>().is_poll_response()); |
| ++n_info_frames; |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/3, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(2u, n_info_frames); |
| |
| // Request a retransmission of unacked data with TxSeq=0 (first frame). |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/true); |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/false); |
| EXPECT_EQ(3u, n_info_frames); |
| |
| // Request a retransmission of unacked data with TxSeq=1 (second frame). |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/false); |
| EXPECT_EQ(4u, n_info_frames); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetSingleRetransmitOnlyAcksIFramesIfPollRequestIsSet) { |
| size_t n_info_frames = 0; |
| std::optional<uint8_t> tx_seq; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField) && |
| pdu->As<EnhancedControlField>().designates_information_frame() && |
| pdu->size() >= sizeof(SimpleInformationFrameHeader)) { |
| ++n_info_frames; |
| tx_seq = pdu->As<SimpleInformationFrameHeader>().tx_seq(); |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/4, /*n_frames_in_tx_windows=*/2, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| // TxWindow prevents third I-Frame from going out. |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(2u, n_info_frames); |
| |
| // Request a retransmission of unacked data with TxSeq=1, not a poll request. |
| n_info_frames = 0; |
| tx_seq.reset(); |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| |
| // Only the second I-Frame is retransmitted. |
| EXPECT_EQ(1u, n_info_frames); |
| EXPECT_EQ(1u, tx_seq.value_or(0)); |
| |
| // Request a retransmission of unacked data with TxSeq=1, this time a poll request. |
| n_info_frames = 0; |
| tx_seq.reset(); |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/true); |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| |
| // The first frame is acked, the second I-Frame is retransmitted, and the third frame (which was |
| // pending because of TxWindow) is transmitted for the first time. |
| EXPECT_EQ(2u, n_info_frames); |
| EXPECT_EQ(2u, tx_seq.value_or(0)); |
| } |
| |
| TEST_F( |
| L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetSingleRetransmitDuringPollRequestThenAgainWithPollResponseAndDifferentAckSeqBothCauseRetransmit) { |
| size_t n_info_frames = 0; |
| size_t n_supervisory_frames = 0; |
| std::optional<uint8_t> tx_seq; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField)) { |
| if (pdu->As<EnhancedControlField>().designates_information_frame()) { |
| ++n_info_frames; |
| tx_seq = pdu->As<SimpleInformationFrameHeader>().tx_seq(); |
| } else if (pdu->As<EnhancedControlField>().designates_supervisory_frame()) { |
| ++n_supervisory_frames; |
| } |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/4, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(2u, n_info_frames); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| ASSERT_EQ(1U, n_supervisory_frames); |
| |
| // Request a retransmission of unacked data with TxSeq=1, not a poll response. |
| n_info_frames = 0; |
| tx_seq.reset(); |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| |
| // The second I-Frame is retransmitted. |
| EXPECT_EQ(1u, n_info_frames); |
| EXPECT_EQ(1u, tx_seq.value_or(0)); |
| |
| // Request a retransmission of unacked data with TxSeq=0, this time a poll response. |
| n_info_frames = 0; |
| tx_seq.reset(); |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(0u, /*is_poll_response=*/true); |
| |
| // The first I-Frame is retransmitted. |
| EXPECT_EQ(1u, n_info_frames); |
| EXPECT_EQ(0u, tx_seq.value_or(1)); |
| |
| // Test that the second poll request stopped the MonitorTimer by checking that the ReceiverReady |
| // poll was sent. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| EXPECT_EQ(2U, n_supervisory_frames); |
| } |
| |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetSingleRetransmitDuringPollRequestSuppressesSingleRetransmitPollResponseWithSameAckSeq) { |
| size_t n_info_frames = 0; |
| size_t n_supervisory_frames = 0; |
| std::optional<uint8_t> tx_seq; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField)) { |
| if (pdu->As<EnhancedControlField>().designates_information_frame()) { |
| ++n_info_frames; |
| tx_seq = pdu->As<SimpleInformationFrameHeader>().tx_seq(); |
| } else if (pdu->As<EnhancedControlField>().designates_supervisory_frame()) { |
| ++n_supervisory_frames; |
| } |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/4, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(2u, n_info_frames); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| ASSERT_EQ(1U, n_supervisory_frames); |
| |
| // Request a retransmission of unacked data with TxSeq=1, not a poll response. |
| n_info_frames = 0; |
| tx_seq.reset(); |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| |
| // The second I-Frame is retransmitted. |
| EXPECT_EQ(1u, n_info_frames); |
| EXPECT_EQ(1u, tx_seq.value_or(0)); |
| |
| // Request a retransmission of unacked data with TxSeq=1, this time a poll response. |
| n_info_frames = 0; |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/true); |
| EXPECT_EQ(0u, n_info_frames); // No duplicate retransmission occurs. |
| |
| // Test that the second poll request stopped the MonitorTimer by checking that the ReceiverReady |
| // poll was sent. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| EXPECT_EQ(2U, n_supervisory_frames); |
| } |
| |
| // This is a possible bug introduced by implementing SrejActioned to the letter per Core Spec |
| // v5.{0–2} Vol 3, Part A, Sec 8.6.5.9–11. |
| TEST_F(L2CAP_EnhancedRetransmissionModeTxEngineTest, |
| SetSingleRetransmitDuringPollRequestSuppressesRetransmissionErroneously) { |
| size_t n_info_frames = 0; |
| size_t n_supervisory_frames = 0; |
| std::optional<uint8_t> tx_seq; |
| auto tx_callback = [&](ByteBufferPtr pdu) { |
| if (pdu && pdu->size() >= sizeof(EnhancedControlField)) { |
| if (pdu->As<EnhancedControlField>().designates_information_frame()) { |
| ++n_info_frames; |
| tx_seq = pdu->As<SimpleInformationFrameHeader>().tx_seq(); |
| } else if (pdu->As<EnhancedControlField>().designates_supervisory_frame()) { |
| ++n_supervisory_frames; |
| } |
| } |
| }; |
| TxEngine tx_engine(/*channel_id=*/kTestChannelId, /*max_tx_sdu_size=*/kDefaultMTU, |
| /*max_transmissions=*/4, /*n_frames_in_tx_windows=*/kDefaultTxWindow, |
| /*send_frame_callback=*/tx_callback, |
| /*connection_failure_callback=*/NoOpFailureCallback); |
| |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| ASSERT_EQ(2u, n_info_frames); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| ASSERT_EQ(1U, n_supervisory_frames); |
| |
| // Request a retransmission of unacked data with TxSeq=1, not a poll response. |
| n_info_frames = 0; |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/false); |
| ASSERT_EQ(1u, n_info_frames); |
| |
| // Ack the first frame and complete the poll. |
| n_info_frames = 0; |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/true); |
| ASSERT_EQ(1u, n_info_frames); // Retransmission is not suppressed. |
| |
| // Send and acknowledge enough frames to roll over AckSeq. |
| n_info_frames = 0; |
| for (size_t i = 0; i < EnhancedControlField::kMaxSeqNum + 1; i++) { |
| const auto ack_seq = (tx_seq.value() + 1) % (EnhancedControlField::kMaxSeqNum + 1); |
| tx_engine.UpdateAckSeq(ack_seq, /*is_poll_response=*/false); |
| tx_engine.QueueSdu(std::make_unique<DynamicByteBuffer>(kDefaultPayload)); |
| } |
| |
| // Now there's a new frame with TxSeq=1. |
| ASSERT_EQ(uintmax_t{EnhancedControlField::kMaxSeqNum} + 1, n_info_frames); |
| ASSERT_EQ(1u, tx_seq.value()); |
| |
| // Let receiver_ready_poll_task_ fire, to transmit the poll. |
| ASSERT_TRUE(RunLoopFor(zx::sec(2))); |
| |
| n_info_frames = 0; |
| tx_engine.SetSingleRetransmit(/*is_poll_request=*/false); |
| tx_engine.UpdateAckSeq(1u, /*is_poll_response=*/true); |
| |
| // Even though this is a different frame than the first retransmission in this test, the latter's |
| // SrejActioned state suppresses this retransmission. |
| EXPECT_EQ(0u, n_info_frames); |
| } |
| |
| } // namespace |
| } // namespace bt::l2cap::internal |