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