blob: b9198a9e66af15dcc490540818d33fdfd9ff16db [file] [log] [blame]
// 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/enhanced_retransmission_mode_tx_engine.h"
#include <gtest/gtest.h>
#include <pw_async/fake_dispatcher_fixture.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_tx_channel.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/frame_headers.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
namespace bt::l2cap::internal {
namespace {
constexpr ChannelId kTestChannelId = 0x0001;
using TxEngine = EnhancedRetransmissionModeTxEngine;
class EnhancedRetransmissionModeTxEngineTest
: public pw::async::test::FakeDispatcherFixture {
public:
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->To<SimpleSupervisoryFrame>();
EXPECT_EQ(SupervisoryFunction::ReceiverReady, sframe.function());
EXPECT_TRUE(sframe.is_poll_request());
}
// Helper to queue an SDU to the channel and notify the engine.
void QueueSdu(TxEngine& engine, ByteBufferPtr sdu) {
channel_.QueueSdu(std::move(sdu));
engine.NotifySduQueued();
}
FakeTxChannel& channel() { return channel_; }
private:
FakeTxChannel channel_;
};
void NoOpFailureCallback() {}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
QueueSduTransmitsMinimalSizedSdu) {
ByteBufferPtr last_pdu;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) {
++n_pdus;
last_pdu = std::move(pdu);
});
constexpr size_t kMtu = 10;
const StaticByteBuffer payload(1);
TxEngine tx_engine(kTestChannelId,
kMtu,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, 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 StaticByteBuffer expected_pdu(0, // Final Bit, TxSeq, MustBeZeroBit
0, // SAR bits, ReqSeq
1); // Payload
EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu));
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
QueueSduTransmitsMaximalSizedSdu) {
ByteBufferPtr last_pdu;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) {
++n_pdus;
last_pdu = std::move(pdu);
});
constexpr size_t kMtu = 1;
const StaticByteBuffer payload(1);
TxEngine tx_engine(kTestChannelId,
kMtu,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, 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 StaticByteBuffer expected_pdu(0, // Final Bit, TxSeq, MustBeZeroBit
0, // SAR bits, ReqSeq
1); // Payload
EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu));
}
TEST_F(EnhancedRetransmissionModeTxEngineTest, QueueSduSurvivesOversizedSdu) {
// TODO(https://fxbug.dev/42054330): Update this test when we add support for
// segmentation.
constexpr size_t kMtu = 1;
TxEngine tx_engine(kTestChannelId,
kMtu,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine,
std::make_unique<DynamicByteBuffer>(StaticByteBuffer(1, 2)));
}
TEST_F(EnhancedRetransmissionModeTxEngineTest, QueueSduSurvivesZeroByteSdu) {
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest, QueueSduAdvancesSequenceNumber) {
const StaticByteBuffer payload(1);
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
{
// See Core Spec v5.0, Volume 3, Part A, Table 3.2.
const StaticByteBuffer expected_pdu(0, // Final Bit, TxSeq, MustBeZeroBit
0, // SAR bits, ReqSeq
1); // Payload
QueueSdu(tx_engine, 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 StaticByteBuffer expected_pdu(
1 << 1, // Final Bit, TxSeq=1, MustBeZeroBit
0, // SAR bits, ReqSeq
1); // Payload
QueueSdu(tx_engine, 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 StaticByteBuffer expected_pdu(
2 << 1, // Final Bit, TxSeq=2, MustBeZeroBit
0, // SAR bits, ReqSeq
1); // Payload
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(payload));
ASSERT_TRUE(last_pdu);
EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu));
}
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
QueueSduRollsOverSequenceNumber) {
constexpr size_t kTxWindow = 63; // Max possible value
const StaticByteBuffer payload(1);
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
constexpr size_t kMaxSeq = 64;
for (size_t i = 0; i < kMaxSeq; ++i) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(payload));
tx_engine.UpdateAckSeq((i + 1) % kMaxSeq, /*is_poll_response=*/false);
}
// See Core Spec v5.0, Volume 3, Part A, Table 3.2.
const StaticByteBuffer expected_pdu(
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, /*is_poll_response=*/false);
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(payload));
ASSERT_TRUE(last_pdu);
EXPECT_TRUE(ContainersEqual(expected_pdu, *last_pdu));
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
QueueSduDoesNotTransmitBeyondTxWindow) {
constexpr size_t kTxWindow = 1;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
ASSERT_EQ(1u, n_pdus);
n_pdus = 0;
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
EXPECT_EQ(0u, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
QueueSduDoesNotTransmitBeyondTxWindowEvenIfQueueWrapsSequenceNumbers) {
constexpr size_t kTxWindow = 1;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, 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) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
ASSERT_EQ(0u, n_pdus);
}
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineTransmitsReceiverReadyPollAfterTimeout) {
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(last_pdu);
last_pdu = nullptr;
ASSERT_TRUE(RunFor(std::chrono::seconds(2)));
SCOPED_TRACE("");
VerifyIsReceiverReadyPollFrame(last_pdu.get());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineTransmitsReceiverReadyPollOnlyOnceAfterTimeout) {
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(last_pdu);
last_pdu = nullptr;
ASSERT_TRUE(RunFor(std::chrono::seconds(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(RunFor(std::chrono::seconds(2))); // No tasks were run.
EXPECT_FALSE(last_pdu);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineAdvancesReceiverReadyPollTimeoutOnNewTransmission) {
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(last_pdu);
last_pdu = nullptr;
ASSERT_FALSE(RunFor(std::chrono::seconds(1))); // No events should fire.
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
last_pdu = nullptr;
ASSERT_FALSE(
RunFor(std::chrono::seconds(1))); // Original timeout should not fire.
ASSERT_TRUE(RunFor(std::chrono::seconds(1))); // New timeout should fire.
SCOPED_TRACE("");
VerifyIsReceiverReadyPollFrame(last_pdu.get());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
ReceiverReadyPollIncludesRequestSequenceNumber) {
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
tx_engine.UpdateReqSeq(1);
RunUntilIdle();
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
last_pdu = nullptr;
SCOPED_TRACE("");
EXPECT_TRUE(RunFor(std::chrono::seconds(2)));
ASSERT_NO_FATAL_FAILURE(VerifyIsReceiverReadyPollFrame(last_pdu.get()));
EXPECT_EQ(1u, last_pdu->To<SimpleSupervisoryFrame>().receive_seq_num());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
AckOfOnlyOutstandingFrameCancelsReceiverReadyPollTimeout) {
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(last_pdu);
last_pdu = nullptr;
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/false);
RunUntilIdle();
EXPECT_FALSE(RunFor(std::chrono::seconds(2))); // No tasks were run.
EXPECT_FALSE(last_pdu);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
AckOfAllOutstandingFramesCancelsReceiverReadyPollTimeout) {
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(last_pdu);
last_pdu = nullptr;
tx_engine.UpdateAckSeq(3, /*is_poll_response=*/false);
RunUntilIdle();
EXPECT_FALSE(RunFor(std::chrono::seconds(2))); // No tasks were run.
EXPECT_FALSE(last_pdu);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
PartialAckDoesNotCancelReceiverReadyPollTimeout) {
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(last_pdu);
last_pdu = nullptr;
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/false);
RunUntilIdle();
// 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(RunFor(std::chrono::seconds(2)));
VerifyIsReceiverReadyPollFrame(last_pdu.get());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
NewTransmissionAfterAckedFrameReArmsReceiverReadyPollTimeout) {
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
// Send a frame, and get the ACK.
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/false);
RunUntilIdle();
// Send a new frame.
QueueSdu(tx_engine, 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(RunFor(std::chrono::seconds(2)));
VerifyIsReceiverReadyPollFrame(last_pdu.get());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineRetransmitsReceiverReadyPollAfterMonitorTimeout) {
constexpr size_t kMaxTransmissions = 2; // Allow retransmission
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
// First the receiver_ready_poll_task_ fires.
ASSERT_TRUE(RunFor(std::chrono::seconds(2)));
ASSERT_TRUE(last_pdu);
last_pdu = nullptr;
// Then the monitor_task_ fires.
EXPECT_TRUE(RunFor(std::chrono::seconds(12)));
VerifyIsReceiverReadyPollFrame(last_pdu.get());
}
TEST_F(
EnhancedRetransmissionModeTxEngineTest,
EngineDoesNotRetransmitReceiverReadyPollAfterMonitorTimeoutWhenRetransmissionsAreDisabled) {
constexpr size_t kMaxTransmissions = 1;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
// First the receiver_ready_poll_task_ fires.
ASSERT_TRUE(RunFor(std::chrono::seconds(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.
RunFor(std::chrono::seconds(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(
EnhancedRetransmissionModeTxEngineTest,
EngineStopsPollingReceiverReadyFromMonitorTaskAfterReceivingFinalUpdateForAckSeq) {
constexpr size_t kMaxTransmissions = 3; // Allow multiple retransmissions
ByteBufferPtr last_pdu;
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(RunFor(std::chrono::seconds(2))); // receiver_ready_poll_task_
ASSERT_TRUE(RunFor(std::chrono::seconds(12))); // monitor_task_
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/true);
EXPECT_FALSE(RunFor(std::chrono::seconds(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(
EnhancedRetransmissionModeTxEngineTest,
EngineContinuesPollingReceiverReadyFromMonitorTaskAfterReceivingNonFinalUpdateForAckSeq) {
constexpr size_t kMaxTransmissions = 2; // Allow retransmissions
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(RunFor(std::chrono::seconds(2))); // receiver_ready_poll_task_
ASSERT_TRUE(RunFor(std::chrono::seconds(12))); // monitor_task_
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/false);
EXPECT_TRUE(RunFor(std::chrono::seconds(12))); // monitor_task_
VerifyIsReceiverReadyPollFrame(last_pdu.get());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineRetransmitsReceiverReadyPollAfterMultipleMonitorTimeouts) {
constexpr size_t kMaxTransmissions = 3; // Allow multiple retransmissions
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(RunFor(std::chrono::seconds(2))); // receiver_ready_poll_task_
ASSERT_TRUE(RunFor(std::chrono::seconds(12))); // monitor_task_
ASSERT_FALSE(
RunFor(std::chrono::seconds(2))); // RR-poll task does _not_ fire
last_pdu = nullptr;
EXPECT_TRUE(RunFor(std::chrono::seconds(10))); // monitor_task_ again
VerifyIsReceiverReadyPollFrame(last_pdu.get());
}
TEST_F(
EnhancedRetransmissionModeTxEngineTest,
EngineRetransmitsReceiverReadyPollIndefinitelyAfterMonitorTimeoutWhenMaxTransmitsIsZero) {
constexpr size_t kMaxTransmissions = 0;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
// First the receiver_ready_poll_task_ fires.
ASSERT_TRUE(RunFor(std::chrono::seconds(2)));
EXPECT_TRUE(last_pdu);
last_pdu = nullptr;
// Then the monitor_task_ fires.
EXPECT_TRUE(RunFor(std::chrono::seconds(12)));
EXPECT_TRUE(last_pdu);
last_pdu = nullptr;
// And the monitor_task_ fires again.
EXPECT_TRUE(RunFor(std::chrono::seconds(12)));
EXPECT_TRUE(last_pdu);
last_pdu = nullptr;
// And the monitor_task_ fires yet again.
EXPECT_TRUE(RunFor(std::chrono::seconds(12)));
EXPECT_TRUE(last_pdu);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineStopsTransmittingReceiverReadyPollAfterMaxTransmits) {
constexpr size_t kMaxTransmissions = 2;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(RunFor(std::chrono::seconds(2))); // receiver_ready_poll_task_
ASSERT_TRUE(RunFor(std::chrono::seconds(12))); // monitor_task_
ASSERT_TRUE(RunFor(std::chrono::seconds(12))); // monitor_task_
last_pdu = nullptr;
EXPECT_FALSE(RunFor(std::chrono::seconds(13)));
EXPECT_FALSE(last_pdu);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineClosesChannelAfterMaxTransmitsOfReceiverReadyPoll) {
constexpr size_t kMaxTransmissions = 2;
bool connection_failed = false;
TxEngine tx_engine(
kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
[&] { connection_failed = true; },
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(RunFor(std::chrono::seconds(2))); // receiver_ready_poll_task_
ASSERT_TRUE(RunFor(std::chrono::seconds(12))); // monitor_task_
ASSERT_TRUE(RunFor(std::chrono::seconds(12))); // monitor_task_
EXPECT_TRUE(connection_failed);
}
TEST_F(
EnhancedRetransmissionModeTxEngineTest,
EngineClosesChannelAfterMaxTransmitsOfReceiverReadyPollEvenIfRetransmissionsAreDisabled) {
constexpr size_t kMaxTransmissions = 1;
bool connection_failed = false;
TxEngine tx_engine(
kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
[&] { connection_failed = true; },
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_TRUE(RunFor(std::chrono::seconds(2))); // receiver_ready_poll_task_
ASSERT_TRUE(RunFor(std::chrono::seconds(12))); // monitor_task_
EXPECT_TRUE(connection_failed);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineClosesChannelAfterMaxTransmitsOfIFrame) {
constexpr size_t kMaxTransmissions = 2;
size_t num_info_frames_sent = 0;
bool connection_failed = false;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame()) {
++num_info_frames_sent;
}
});
TxEngine tx_engine(
kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
[&] { connection_failed = true; },
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
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(RunFor(std::chrono::seconds(2)));
// The peer indicates that it has not received any frames. This causes us to
// retransmit the frame.
tx_engine.UpdateAckSeq(0, /*is_poll_response=*/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(RunFor(std::chrono::seconds(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, /*is_poll_response=*/true);
// Because we've exhausted kMaxTransmissions, the connection will be closed.
EXPECT_TRUE(connection_failed);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineExhaustsAllRetransmissionsOfIFrameBeforeClosingChannel) {
constexpr size_t kMaxTransmissions = 255;
size_t num_info_frames_sent = 0;
bool connection_failed = false;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame()) {
++num_info_frames_sent;
}
});
TxEngine tx_engine(
kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
[&] { connection_failed = true; },
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
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(RunFor(std::chrono::seconds(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, /*is_poll_response=*/true);
}
// The connection is closed, and we've exhausted kMaxTransmissions.
EXPECT_TRUE(connection_failed);
EXPECT_EQ(255u, num_info_frames_sent);
}
TEST_F(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;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame()) {
++num_info_frames_sent;
last_tx_frame = std::move(pdu);
}
});
TxEngine tx_engine(
kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
[&] { connection_failed = true; },
dispatcher());
QueueSdu(tx_engine, 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(RunFor(std::chrono::seconds(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, /*is_poll_response=*/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."
QueueSdu(tx_engine,
std::make_unique<DynamicByteBuffer>(StaticByteBuffer('@')));
EXPECT_EQ(1u, num_info_frames_sent);
EXPECT_EQ('@', (*last_tx_frame)[sizeof(EnhancedControlField)]);
}
TEST_F(
EnhancedRetransmissionModeTxEngineTest,
EngineClosesChannelAfterMaxTransmitsOfIFrameEvenIfRetransmissionsAreDisabled) {
constexpr size_t kMaxTransmissions = 1;
bool connection_failed = false;
TxEngine tx_engine(
kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
[&] { connection_failed = true; },
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
// Not having received an acknowledgement after 2 seconds,
// receiver_ready_poll_task_ will fire, and cause us to send a
// ReceiverReadyPoll.
ASSERT_TRUE(RunFor(std::chrono::seconds(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, /*is_poll_response=*/true);
EXPECT_TRUE(connection_failed);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineRetransmitsMissingFrameOnPollResponse) {
constexpr size_t kMaxTransmissions = 2;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) { last_pdu = std::move(pdu); });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
last_pdu = nullptr;
ASSERT_TRUE(RunFor(std::chrono::seconds(2))); // receiver_ready_poll_task_
last_pdu = nullptr;
tx_engine.UpdateAckSeq(0, /*is_poll_response=*/true);
ASSERT_TRUE(last_pdu);
ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader));
ASSERT_TRUE(
last_pdu->To<EnhancedControlField>().designates_information_frame());
EXPECT_EQ(0u, last_pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineRetransmitsAllMissingFramesOnPollResponse) {
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 63;
size_t n_pdus = 0;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) {
++n_pdus;
last_pdu = std::move(pdu);
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
// Send a TxWindow's worth of frames.
for (size_t i = 0; i < kTxWindow; ++i) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
RunUntilIdle();
// Let receiver_ready_poll_task_ fire, and clear out accumulated callback
// state.
ASSERT_TRUE(RunFor(std::chrono::seconds(2)));
n_pdus = 0;
last_pdu = nullptr;
tx_engine.UpdateAckSeq(0, /*is_poll_response=*/true);
EXPECT_EQ(kTxWindow, n_pdus);
ASSERT_TRUE(last_pdu);
ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader));
ASSERT_TRUE(
last_pdu->To<EnhancedControlField>().designates_information_frame());
EXPECT_EQ(kTxWindow - 1,
last_pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
TEST_F(
EnhancedRetransmissionModeTxEngineTest,
EngineRetransmitsAllMissingFramesOnPollResponseWithWrappedSequenceNumber) {
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 63;
size_t n_pdus = 0;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) {
++n_pdus;
last_pdu = std::move(pdu);
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
// Send a TxWindow's worth of frames.
for (size_t i = 0; i < kTxWindow; ++i) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
// Acknowledge the first 32 of these frames (with sequence numbers 0...31).
tx_engine.UpdateAckSeq(32, /*is_poll_response=*/false);
// Queue 32 new frames (with sequence numbers 63, 0 ... 30).
for (size_t i = 0; i < 32; ++i) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
RunUntilIdle();
// Let receiver_ready_poll_task_ fire, and then clear out accumulated callback
// state.
ASSERT_TRUE(RunFor(std::chrono::seconds(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, /*is_poll_response=*/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->To<EnhancedControlField>().designates_information_frame());
EXPECT_EQ(30u, last_pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineProperlyHandlesPartialAckWithWrappedSequenceNumber) {
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 63;
size_t n_pdus = 0;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) {
++n_pdus;
last_pdu = std::move(pdu);
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
// Send a TxWindow's worth of frames.
for (size_t i = 0; i < kTxWindow; ++i) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
// Acknowledge the first 62 of these frames (with sequence numbers 0...61).
tx_engine.UpdateAckSeq(62, /*is_poll_response=*/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) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
RunUntilIdle();
// Let receiver_ready_poll_task_ fire, and then clear out accumulated callback
// state.
ASSERT_TRUE(RunFor(std::chrono::seconds(2)));
n_pdus = 0;
last_pdu = nullptr;
// Acknowledge an additional 5 frames (with sequence numbers 62, 63, 0, 1, 2).
tx_engine.UpdateAckSeq(3, /*is_poll_response=*/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->To<EnhancedControlField>().designates_information_frame());
EXPECT_EQ(60u, last_pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineDoesNotRetransmitFramesBeyondTxWindow) {
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 32;
size_t n_pdus = 0;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) {
++n_pdus;
last_pdu = std::move(pdu);
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
// Queue two TxWindow's worth of frames. These have sequence numbers 0...63.
for (size_t i = 0; i < 2 * kTxWindow; ++i) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
RunUntilIdle();
// Let receiver_ready_poll_task_ fire, and clear out accumulated callback
// state.
ASSERT_TRUE(RunFor(std::chrono::seconds(2)));
n_pdus = 0;
last_pdu = nullptr;
tx_engine.UpdateAckSeq(0, /*is_poll_response=*/true);
EXPECT_EQ(kTxWindow, n_pdus);
ASSERT_TRUE(last_pdu);
ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader));
EXPECT_EQ(kTxWindow - 1,
last_pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineDoesNotRetransmitFramesBeyondTxWindowWhenWindowWraps) {
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 48;
size_t n_pdus = 0;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) {
++n_pdus;
last_pdu = std::move(pdu);
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
// 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) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
tx_engine.UpdateAckSeq(48, /*is_poll_response=*/false);
RunUntilIdle();
// 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) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
RunUntilIdle();
// 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) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
RunUntilIdle();
// Let receiver_ready_poll_task_ fire, and clear out accumulated callback
// state.
ASSERT_TRUE(RunFor(std::chrono::seconds(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, /*is_poll_response=*/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->To<EnhancedControlField>().designates_information_frame());
EXPECT_EQ(31u, last_pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineDoesNotRetransmitPreviouslyAckedFramesOnPollResponse) {
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 2;
size_t n_pdus = 0;
ByteBufferPtr last_pdu;
channel().HandleSendFrame([&](auto pdu) {
++n_pdus;
last_pdu = std::move(pdu);
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
// Let receiver_ready_poll_task_ fire, and clear out accumulated callback
// state.
ASSERT_TRUE(RunFor(std::chrono::seconds(2)));
n_pdus = 0;
last_pdu = nullptr;
constexpr size_t kPollResponseReqSeq = 1;
tx_engine.UpdateAckSeq(kPollResponseReqSeq, /*is_poll_response=*/true);
EXPECT_EQ(kTxWindow - kPollResponseReqSeq, n_pdus);
ASSERT_TRUE(last_pdu);
ASSERT_GE(last_pdu->size(), sizeof(SimpleInformationFrameHeader));
ASSERT_TRUE(
last_pdu->To<EnhancedControlField>().designates_information_frame());
EXPECT_EQ(1, last_pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
AckOfFrameWithNoneOutstandingClosesChannel) {
bool connection_failed = false;
TxEngine tx_engine(
kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
[&] { connection_failed = true; },
dispatcher());
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/false);
EXPECT_TRUE(connection_failed);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
AckOfMoreFramesThanAreOutstandingClosesChannel) {
bool connection_failed = false;
TxEngine tx_engine(
kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
[&] { connection_failed = true; },
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
tx_engine.UpdateAckSeq(2, /*is_poll_response=*/true);
EXPECT_TRUE(connection_failed);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineDoesNotCrashOnSpuriousAckAfterValidAck) {
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/true);
tx_engine.UpdateAckSeq(2, /*is_poll_response=*/true);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
EngineDoesNotCrashOnSpuriousAckBeforeAnyDataHasBeenSent) {
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
for (size_t i = 0; i <= EnhancedControlField::kMaxSeqNum; ++i) {
tx_engine.UpdateAckSeq(i, /*is_poll_response=*/true);
}
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
QueueSduDoesNotTransmitFramesWhenRemoteIsBusy) {
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
tx_engine.SetRemoteBusy();
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
EXPECT_EQ(0u, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
UpdateAckSeqTransmitsQueuedDataWhenPossible) {
constexpr size_t kTxWindow = 1;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_EQ(1u, n_pdus);
n_pdus = 0;
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/false);
RunUntilIdle();
EXPECT_EQ(1u, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
UpdateAckSeqTransmissionOfQueuedDataRespectsTxWindow) {
constexpr size_t kTxWindow = 1;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_EQ(1u, n_pdus);
n_pdus = 0;
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/false);
RunUntilIdle();
EXPECT_EQ(1u, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
NonFinalUpdateAckSeqDoesNotTransmitQueuedFramesWhenRemoteIsBusy) {
constexpr size_t kTxWindow = 1;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_EQ(1u, n_pdus);
n_pdus = 0;
tx_engine.SetRemoteBusy();
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/false);
RunUntilIdle();
EXPECT_EQ(0u, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
FinalUpdateAckSeqDoesNotTransmitQueuedFramesWhenRemoteIsBusy) {
constexpr size_t kTxWindow = 1;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_EQ(1u, n_pdus);
n_pdus = 0;
tx_engine.SetRemoteBusy();
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/true);
RunUntilIdle();
EXPECT_EQ(0u, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
MaybeSendQueuedDataTransmitsAllQueuedFramesWithinTxWindow) {
constexpr size_t kTxWindow = 63;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
tx_engine.SetRemoteBusy();
for (size_t i = 0; i < kTxWindow; ++i) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
RunUntilIdle();
ASSERT_EQ(0u, n_pdus);
tx_engine.ClearRemoteBusy();
tx_engine.MaybeSendQueuedData();
RunUntilIdle();
EXPECT_EQ(kTxWindow, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
MaybeSendQueuedDataDoesNotTransmitBeyondTxWindow) {
constexpr size_t kTxWindow = 32;
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
tx_engine.SetRemoteBusy();
for (size_t i = 0; i < kTxWindow + 1; ++i) {
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
}
RunUntilIdle();
ASSERT_EQ(0u, n_pdus);
tx_engine.ClearRemoteBusy();
tx_engine.MaybeSendQueuedData();
RunUntilIdle();
EXPECT_EQ(kTxWindow, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
MaybeSendQueuedDataRespectsRemoteBusy) {
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
tx_engine.SetRemoteBusy();
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_EQ(0u, n_pdus);
tx_engine.MaybeSendQueuedData();
RunUntilIdle();
EXPECT_EQ(0u, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
MaybeSendQueuedDataDoesNotCrashWhenCalledWithoutPendingPdus) {
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
tx_engine.MaybeSendQueuedData();
RunUntilIdle();
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
QueueSduCanSendMoreFramesAfterClearingRemoteBusy) {
size_t n_pdus = 0;
channel().HandleSendFrame([&](auto pdu) { ++n_pdus; });
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
tx_engine.SetRemoteBusy();
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_EQ(0u, n_pdus);
tx_engine.ClearRemoteBusy();
tx_engine.MaybeSendQueuedData();
RunUntilIdle();
ASSERT_EQ(1u, n_pdus);
n_pdus = 0;
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
EXPECT_EQ(1u, n_pdus);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
QueueSduMaintainsSduOrderingAfterClearRemoteBusy) {
std::vector<uint8_t> pdu_seq_numbers;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
pdu_seq_numbers.push_back(
pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
tx_engine.SetRemoteBusy();
QueueSdu(tx_engine,
std::make_unique<DynamicByteBuffer>(kDefaultPayload)); // seq=0
RunUntilIdle();
ASSERT_TRUE(pdu_seq_numbers.empty());
tx_engine.ClearRemoteBusy();
QueueSdu(tx_engine,
std::make_unique<DynamicByteBuffer>(kDefaultPayload)); // seq=1
RunUntilIdle();
// 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(EnhancedRetransmissionModeTxEngineTest,
UpdateAckSeqRetransmitsUnackedFramesBeforeTransmittingQueuedFrames) {
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 63;
std::vector<uint8_t> pdu_seq_numbers;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
pdu_seq_numbers.push_back(
pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
// Send out two frames.
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
ASSERT_EQ(2u, pdu_seq_numbers.size());
pdu_seq_numbers.clear();
// Indicate the remote is busy, and queue a third frame.
tx_engine.SetRemoteBusy();
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
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, /*is_poll_response=*/true);
RunUntilIdle();
EXPECT_EQ((std::vector{uint8_t{1}, uint8_t{2}}), pdu_seq_numbers);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
QueueSduDoesNotTransmitNewFrameWhenEngineIsAwaitingPollResponse) {
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 3;
std::vector<uint8_t> pdu_seq_numbers;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
pdu_seq_numbers.push_back(
pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
// Let receiver_ready_poll_task_ fire. This moves the engine into the 'WAIT_F'
// state.
ASSERT_TRUE(RunFor(std::chrono::seconds(2)));
// Queue a new frame.
pdu_seq_numbers.clear();
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
EXPECT_EQ(std::vector<uint8_t>(), pdu_seq_numbers);
}
TEST_F(
EnhancedRetransmissionModeTxEngineTest,
NonFinalUpdateAckSeqDoesNotTransmitNewFrameWhenEngineIsAwaitingPollResponse) {
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 1;
std::vector<uint8_t> pdu_seq_numbers;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
pdu_seq_numbers.push_back(
pdu->To<SimpleInformationFrameHeader>().tx_seq());
}
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
// Let receiver_ready_poll_task_ fire. This moves the engine into the 'WAIT_F'
// state.
ASSERT_TRUE(RunFor(std::chrono::seconds(2)));
// Acknowledge the first frame, making room for the transmission of the second
// frame.
pdu_seq_numbers.clear();
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/false);
RunUntilIdle();
// 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(
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,
channel(),
[&] {
connection_failed = true;
tx_engine.reset();
},
dispatcher());
// Queue three SDUs, of which two should be transmitted immediately.
QueueSdu(*tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(*tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(*tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
// Let receiver_ready_poll_task_ fire. This moves the engine into the 'WAIT_F'
// state.
ASSERT_TRUE(RunFor(std::chrono::seconds(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, /*is_poll_response=*/true);
RunUntilIdle();
// Because we only allow one transmission, the connection should have failed.
EXPECT_TRUE(connection_failed);
}
TEST_F(
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,
channel(),
[&] {
connection_failed = true;
tx_engine.reset();
},
dispatcher());
QueueSdu(*tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
// Let receiver_ready_poll_task_ fire, to transmit the poll.
ASSERT_TRUE(RunFor(std::chrono::seconds(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(RunFor(std::chrono::seconds(12)));
EXPECT_TRUE(connection_failed);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
TransmissionOfPduIncludesRequestSeqNum) {
uint8_t outbound_req_seq = 0;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
outbound_req_seq =
pdu->To<SimpleInformationFrameHeader>().receive_seq_num();
}
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
tx_engine.UpdateReqSeq(5);
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
EXPECT_EQ(5u, outbound_req_seq);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
DeferredTransmissionOfPduIncludesCurrentRequestSeqNum) {
constexpr size_t kTxWindow = 1;
uint8_t outbound_req_seq = 0;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
outbound_req_seq =
pdu->To<SimpleInformationFrameHeader>().receive_seq_num();
}
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kDefaultMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
tx_engine.UpdateReqSeq(5);
tx_engine.UpdateAckSeq(1, /*is_poll_response=*/true); // Peer acks first PDU.
RunUntilIdle();
// The second PDU should have been transmitted with ReqSeq = 5.
EXPECT_EQ(5u, outbound_req_seq);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
RetransmissionOfPduIncludesCurrentSeqNum) {
constexpr size_t kMaxTransmissions = 2;
uint8_t outbound_req_seq = 0;
size_t n_info_frames = 0;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
++n_info_frames;
outbound_req_seq =
pdu->To<SimpleInformationFrameHeader>().receive_seq_num();
}
});
TxEngine tx_engine(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kDefaultTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
RunUntilIdle();
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(RunFor(std::chrono::seconds(2)));
// Our peer indicates that it has not received our data.
tx_engine.UpdateAckSeq(0, /*is_poll_response=*/true);
RunUntilIdle();
// Our retransmission should include our current request sequence number.
EXPECT_EQ(2u, n_info_frames);
EXPECT_EQ(10u, outbound_req_seq);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
PollTaskLoopsEvenWhenRemoteIsBusy) {
size_t n_pdus = 0;
channel().HandleSendFrame([&](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_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/connection_failure_callback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
ASSERT_EQ(1u, n_pdus);
// Let receiver_ready_poll_task_ fire, to transmit the poll.
ASSERT_TRUE(RunFor(std::chrono::seconds(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.
RunFor(std::chrono::seconds(12));
EXPECT_EQ(0u, n_pdus);
EXPECT_FALSE(failure);
}
TEST_F(EnhancedRetransmissionModeTxEngineTest,
SetRangeRetransmitCausesUpdateAckSeqToRetransmit) {
size_t n_info_frames = 0;
std::optional<uint8_t> tx_seq;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
++n_info_frames;
tx_seq = pdu->To<SimpleInformationFrameHeader>().tx_seq();
}
});
TxEngine tx_engine(/*channel_id=*/kTestChannelId,
/*max_tx_sdu_size=*/kDefaultMTU,
/*max_transmissions=*/4,
/*n_frames_in_tx_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, 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(EnhancedRetransmissionModeTxEngineTest,
SetRangeRetransmitWithPollRequestTriggersPollResponseOnFirstIFrame) {
size_t n_info_frames = 0;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<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->To<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_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, 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(
EnhancedRetransmissionModeTxEngineTest,
SetRangeRetransmitAfterPollTaskSuppressesSubsequentRetransmitByPollResponse) {
size_t n_info_frames = 0;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<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_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
ASSERT_EQ(1u, n_info_frames);
// Let receiver_ready_poll_task_ fire, to transmit the poll.
ASSERT_TRUE(RunFor(std::chrono::seconds(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(
EnhancedRetransmissionModeTxEngineTest,
SetRangeRetransmitWithPollResponseSetDoesNotSuppressSubsequentRetransmissions) {
size_t n_info_frames = 0;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<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_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
ASSERT_EQ(1u, n_info_frames);
// Let receiver_ready_poll_task_ fire, to transmit the poll.
ASSERT_TRUE(RunFor(std::chrono::seconds(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(RunFor(std::chrono::seconds(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(
EnhancedRetransmissionModeTxEngineTest,
SetRangeRetransmitSuppressesRetransmissionsByRetransmitRangeWithPollResponse) {
size_t n_info_frames = 0;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<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_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
ASSERT_EQ(1u, n_info_frames);
// Let receiver_ready_poll_task_ fire, to transmit the poll.
ASSERT_TRUE(RunFor(std::chrono::seconds(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(EnhancedRetransmissionModeTxEngineTest,
SetSingleRetransmitCausesUpdateAckSeqToRetransmit) {
size_t n_info_frames = 0;
std::optional<uint8_t> tx_seq;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
++n_info_frames;
tx_seq = pdu->To<SimpleInformationFrameHeader>().tx_seq();
}
});
TxEngine tx_engine(/*channel_id=*/kTestChannelId,
/*max_tx_sdu_size=*/kDefaultMTU,
/*max_transmissions=*/4,
/*n_frames_in_tx_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, 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(EnhancedRetransmissionModeTxEngineTest,
SetSingleRetransmitWithPollRequestTriggersPollResponseOnIFrame) {
size_t n_info_frames = 0;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<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->To<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_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, 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(EnhancedRetransmissionModeTxEngineTest,
SetSingleRetransmitOnlyAcksIFramesIfPollRequestIsSet) {
size_t n_info_frames = 0;
std::optional<uint8_t> tx_seq;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField) &&
pdu->To<EnhancedControlField>().designates_information_frame() &&
pdu->size() >= sizeof(SimpleInformationFrameHeader)) {
++n_info_frames;
tx_seq = pdu->To<SimpleInformationFrameHeader>().tx_seq();
}
});
TxEngine tx_engine(/*channel_id=*/kTestChannelId,
/*max_tx_sdu_size=*/kDefaultMTU,
/*max_transmissions=*/4,
/*n_frames_in_tx_window=*/2,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
// TxWindow prevents third I-Frame from going out.
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, 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(
EnhancedRetransmissionModeTxEngineTest,
SetSingleRetransmitDuringPollRequestThenAgainWithPollResponseAndDifferentAckSeqBothCauseRetransmit) {
size_t n_info_frames = 0;
size_t n_supervisory_frames = 0;
std::optional<uint8_t> tx_seq;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField)) {
if (pdu->To<EnhancedControlField>().designates_information_frame()) {
++n_info_frames;
tx_seq = pdu->To<SimpleInformationFrameHeader>().tx_seq();
} else if (pdu->To<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_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
ASSERT_EQ(2u, n_info_frames);
// Let receiver_ready_poll_task_ fire, to transmit the poll.
ASSERT_TRUE(RunFor(std::chrono::seconds(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(RunFor(std::chrono::seconds(2)));
EXPECT_EQ(2U, n_supervisory_frames);
}
TEST_F(
EnhancedRetransmissionModeTxEngineTest,
SetSingleRetransmitDuringPollRequestSuppressesSingleRetransmitPollResponseWithSameAckSeq) {
size_t n_info_frames = 0;
size_t n_supervisory_frames = 0;
std::optional<uint8_t> tx_seq;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField)) {
if (pdu->To<EnhancedControlField>().designates_information_frame()) {
++n_info_frames;
tx_seq = pdu->To<SimpleInformationFrameHeader>().tx_seq();
} else if (pdu->To<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_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
ASSERT_EQ(2u, n_info_frames);
// Let receiver_ready_poll_task_ fire, to transmit the poll.
ASSERT_TRUE(RunFor(std::chrono::seconds(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(RunFor(std::chrono::seconds(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(
EnhancedRetransmissionModeTxEngineTest,
SetSingleRetransmitDuringPollRequestSuppressesRetransmissionErroneously) {
size_t n_info_frames = 0;
size_t n_supervisory_frames = 0;
std::optional<uint8_t> tx_seq;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
if (pdu && pdu->size() >= sizeof(EnhancedControlField)) {
if (pdu->To<EnhancedControlField>().designates_information_frame()) {
++n_info_frames;
tx_seq = pdu->To<SimpleInformationFrameHeader>().tx_seq();
} else if (pdu->To<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_window=*/kDefaultTxWindow,
/*channel=*/channel(),
/*connection_failure_callback=*/NoOpFailureCallback,
dispatcher());
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
QueueSdu(tx_engine, std::make_unique<DynamicByteBuffer>(kDefaultPayload));
ASSERT_EQ(2u, n_info_frames);
// Let receiver_ready_poll_task_ fire, to transmit the poll.
ASSERT_TRUE(RunFor(std::chrono::seconds(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);
QueueSdu(tx_engine, 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(RunFor(std::chrono::seconds(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