blob: 3af3e36490bacce4810a3b4c5929981fb8b4cbf9 [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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/enhanced_retransmission_mode_engines.h"
#include <gmock/gmock.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/l2cap/fake_tx_channel.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fragmenter.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
namespace bt::l2cap::internal {
namespace {
class EnhancedRetransmissionModeEnginesTest
: public pw::async::test::FakeDispatcherFixture {
protected:
void TxProcessSdu(TxEngine& engine, ByteBufferPtr sdu) {
channel_.QueueSdu(std::move(sdu));
engine.NotifySduQueued();
}
FakeTxChannel& channel() { return channel_; }
private:
FakeTxChannel channel_;
};
constexpr size_t kMaxTransmissions = 2;
constexpr size_t kTxWindow = 63;
constexpr hci_spec::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 NoOpFailureCallback() {}
TEST_F(EnhancedRetransmissionModeEnginesTest, MakeLinkedERTMEngines) {
auto [rx_engine, tx_engine] =
MakeLinkedEnhancedRetransmissionModeEngines(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
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(EnhancedRetransmissionModeEnginesTest,
OutboundInformationFramesAcknowledgeReceivedInformationFrames) {
int tx_count = 0;
channel().HandleSendFrame([&](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->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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,
channel(),
NoOpFailureCallback,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
TxProcessSdu(*tx_engine,
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);
TxProcessSdu(*tx_engine,
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(EnhancedRetransmissionModeEnginesTest,
SignalFailureAfterMonitorTimerExpiry) {
int tx_count = 0;
channel().HandleSendFrame([&](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->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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->To<EnhancedControlField>().designates_supervisory_frame());
ASSERT_TRUE(pdu->To<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,
channel(),
failure_cb,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
TxProcessSdu(*tx_engine,
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(RunFor(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(RunFor(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(EnhancedRetransmissionModeEnginesTest,
SignalFailureAfterMaxTransmitExhausted) {
int tx_count = 0;
channel().HandleSendFrame([&](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->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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->To<EnhancedControlField>().designates_supervisory_frame());
ASSERT_TRUE(pdu->To<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,
channel(),
failure_cb,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
TxProcessSdu(*tx_engine,
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(RunFor(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(EnhancedRetransmissionModeEnginesTest,
RetransmitAfterReceivingRejectSFrame) {
int tx_count = 0;
int iframe_0_tx_count = 0;
int iframe_1_tx_count = 0;
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
tx_count++;
ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size());
ASSERT_TRUE(pdu->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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,
channel(),
NoOpFailureCallback,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
TxProcessSdu(*tx_engine,
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);
TxProcessSdu(*tx_engine,
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(EnhancedRetransmissionModeEnginesTest,
RetransmitAfterReceivingSelectiveRejectPollRequestSFrame) {
int tx_count = 0;
std::array<int, 4> iframe_tx_counts{};
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
tx_count++;
ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size());
ASSERT_TRUE(pdu->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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,
channel(),
NoOpFailureCallback,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
for (int i = 0; i < 4; i++) {
TxProcessSdu(
*tx_engine,
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.
StaticByteBuffer selective_reject(
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(EnhancedRetransmissionModeEnginesTest,
RetransmitAfterReceivingSelectiveRejectSFrame) {
int tx_count = 0;
std::array<int, 4> iframe_tx_counts{};
channel().HandleSendFrame([&](ByteBufferPtr pdu) {
tx_count++;
ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size());
ASSERT_TRUE(pdu->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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,
channel(),
NoOpFailureCallback,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
for (int i = 0; i < 4; i++) {
TxProcessSdu(
*tx_engine,
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(EnhancedRetransmissionModeEnginesTest,
RetransmitAfterPollResponseDoesNotAcknowledgeSentFrames) {
DynamicByteBuffer info_frame;
int tx_count = 0;
channel().HandleSendFrame([&](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
// 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,
channel(),
NoOpFailureCallback,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
TxProcessSdu(*tx_engine,
std::make_unique<DynamicByteBuffer>(StaticByteBuffer{'a'}));
EXPECT_EQ(1, tx_count);
RunFor(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(EnhancedRetransmissionModeEnginesTest,
DoNotRetransmitAfterReceivingReceiverNotReadyPollResponse) {
int tx_count = 0;
channel().HandleSendFrame([&](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->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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->To<EnhancedControlField>().designates_supervisory_frame());
ASSERT_TRUE(pdu->To<SimpleSupervisoryFrame>().is_poll_request());
}
tx_count++;
});
auto [rx_engine, tx_engine] =
MakeLinkedEnhancedRetransmissionModeEngines(kTestChannelId,
kDefaultMTU,
kMaxTransmissions,
kTxWindow,
channel(),
NoOpFailureCallback,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
TxProcessSdu(*tx_engine,
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(RunFor(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(
EnhancedRetransmissionModeEnginesTest,
RetransmitOnlyOnceAfterReceivingDuplicateSelectiveRejectSFramesForSameIFrameDuringReceiverReadyPoll) {
int tx_count = 0;
int iframe_0_tx_count = 0;
int iframe_1_tx_count = 0;
channel().HandleSendFrame([&](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->To<EnhancedControlField>().designates_supervisory_frame());
ASSERT_TRUE(pdu->To<SimpleSupervisoryFrame>().is_poll_request());
return;
}
ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size());
ASSERT_TRUE(pdu->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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,
channel(),
NoOpFailureCallback,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
TxProcessSdu(*tx_engine,
std::make_unique<DynamicByteBuffer>(StaticByteBuffer('a')));
TxProcessSdu(*tx_engine,
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);
RunFor(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.
StaticByteBuffer selective_reject_f(
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(
EnhancedRetransmissionModeEnginesTest,
RetransmitAfterReceivingRejectSFrameAndReceiverReadyPollResponseNotAcknowledgingSentFrames) {
int tx_count = 0;
int iframe_0_tx_count = 0;
int iframe_1_tx_count = 0;
channel().HandleSendFrame([&](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->To<EnhancedControlField>().designates_supervisory_frame());
ASSERT_TRUE(pdu->To<SimpleSupervisoryFrame>().is_poll_request());
return;
}
ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size());
ASSERT_TRUE(pdu->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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,
channel(),
NoOpFailureCallback,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
TxProcessSdu(*tx_engine,
std::make_unique<DynamicByteBuffer>(StaticByteBuffer('a')));
TxProcessSdu(*tx_engine,
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);
RunFor(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(
EnhancedRetransmissionModeEnginesTest,
RetransmitAfterReceivingRejectSFrameAndIFramePollResponseNotAcknowledgingSentFrames) {
int tx_count = 0;
int iframe_0_tx_count = 0;
int iframe_1_tx_count = 0;
channel().HandleSendFrame([&](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->To<EnhancedControlField>().designates_supervisory_frame());
EXPECT_TRUE(pdu->To<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->To<EnhancedControlField>().designates_supervisory_frame());
EXPECT_FALSE(pdu->To<SimpleSupervisoryFrame>().is_poll_request());
EXPECT_EQ(1, pdu->To<SimpleSupervisoryFrame>().receive_seq_num());
return;
}
ASSERT_LE(sizeof(SimpleInformationFrameHeader), pdu->size());
ASSERT_TRUE(pdu->To<EnhancedControlField>().designates_information_frame());
const auto& header = pdu->To<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,
channel(),
NoOpFailureCallback,
dispatcher());
ASSERT_TRUE(rx_engine);
ASSERT_TRUE(tx_engine);
TxProcessSdu(*tx_engine,
std::make_unique<DynamicByteBuffer>(StaticByteBuffer('a')));
TxProcessSdu(*tx_engine,
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);
RunFor(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