| // Copyright 2018 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_rx_engine.h" |
| |
| #include <gtest/gtest.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 { |
| |
| constexpr hci_spec::ConnectionHandle kTestHandle = 0x0001; |
| constexpr ChannelId kTestChannelId = 0x0001; |
| constexpr uint8_t kExtendedControlPBitMask = 0b0001'0000; |
| constexpr uint8_t kExtendedControlFBitMask = 0b1000'0000; |
| constexpr uint8_t kExtendedControlReceiverReadyBits = 0b0000'0000; |
| constexpr uint8_t kExtendedControlReceiverNotReadyBits = 0b0000'1000; |
| constexpr uint8_t kExtendedControlRejectBits = 0b0000'0100; |
| constexpr uint8_t kExtendedControlSelectiveRejectBits = 0b0000'1100; |
| |
| using Engine = EnhancedRetransmissionModeRxEngine; |
| |
| void NoOpTxCallback(ByteBufferPtr) {} |
| void NoOpFailureCallback() {} |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduImmediatelyReturnsDataForUnsegmentedSdu) { |
| // See Core Spec, v5, Vol 3, Part A, Table 3.2 for the first two bytes. |
| const StaticByteBuffer payload(0, 0, 'h', 'e', 'l', 'l', 'o'); |
| const ByteBufferPtr sdu = Engine(NoOpTxCallback, NoOpFailureCallback) |
| .ProcessPdu(Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, |
| FrameCheckSequenceOption::kIncludeFcs)); |
| ASSERT_TRUE(sdu); |
| EXPECT_TRUE(ContainersEqual(StaticByteBuffer('h', 'e', 'l', 'l', 'o'), *sdu)); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduCanHandleZeroBytePayload) { |
| // See Core Spec, v5, Vol 3, Part A, Table 3.2 for the first two bytes. |
| const StaticByteBuffer payload(0, 0); |
| const ByteBufferPtr sdu = Engine(NoOpTxCallback, NoOpFailureCallback) |
| .ProcessPdu(Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, |
| FrameCheckSequenceOption::kIncludeFcs)); |
| ASSERT_TRUE(sdu); |
| EXPECT_EQ(0u, sdu->size()); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduCanHandleZeroBytePdu) { |
| // See Core Spec, v5, Vol 3, Part A, Table 3.2 for the first two bytes. |
| const StaticByteBuffer payload{0, 0}; |
| |
| // FCS footer is entirely omitted. |
| const ByteBufferPtr sdu = |
| Engine(NoOpTxCallback, NoOpFailureCallback) |
| .ProcessPdu(Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs)); |
| EXPECT_FALSE(sdu); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduCanHandleIncompleteFcsFooter) { |
| // See Core Spec, v5, Vol 3, Part A, Table 3.2 for the first two bytes. |
| // No payload is present and only one byte of the FCS footer is present. |
| const StaticByteBuffer payload{0, 0, 0}; |
| const ByteBufferPtr sdu = |
| Engine(NoOpTxCallback, NoOpFailureCallback) |
| .ProcessPdu(Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs)); |
| EXPECT_FALSE(sdu); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduDoesNotGenerateSduForOutOfSequencePdu) { |
| // See Core Spec, v5, Vol 3, Part A, Table 3.2 for the first two bytes. |
| const StaticByteBuffer payload( // |
| 1 << 1, // TxSeq = 1, R=0 |
| 0, // SAR and ReqSeq |
| 'h', 'e', 'l', 'l', 'o'); |
| EXPECT_FALSE(Engine(NoOpTxCallback, NoOpFailureCallback) |
| .ProcessPdu(Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, |
| FrameCheckSequenceOption::kIncludeFcs))); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduAdvancesSequenceNumberOnInSequenceFrame) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| |
| // Send with sequence 0. |
| { |
| const StaticByteBuffer payload( // |
| 0 << 1, // TxSeq=0, R=0 |
| 0, // SAR and ReqSeq |
| 'h', 'e', 'l', 'l', 'o'); |
| ASSERT_TRUE(rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs))); |
| } |
| |
| // Send with sequence 1. |
| { |
| const StaticByteBuffer payload( // |
| 1 << 1, // TxSeq=1, R=0 |
| 0, // SAR and ReqSeq |
| 'h', 'e', 'l', 'l', 'o'); |
| ASSERT_TRUE(rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs))); |
| } |
| |
| // Send with sequence 2. |
| { |
| const StaticByteBuffer payload( // |
| 2 << 1, // TxSeq=2, R=0 |
| 0, // SAR and ReqSeq |
| 'h', 'e', 'l', 'l', 'o'); |
| EXPECT_TRUE(rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs))); |
| } |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduRollsOverSequenceNumber) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| StaticByteBuffer payload( // |
| 0 << 1, // TxSeq=0, R=0 |
| 0, // SAR and ReqSeq |
| 'h', 'e', 'l', 'l', 'o'); |
| for (size_t i = 0; i < 64; ++i) { |
| payload[0] = i << 1; // Set TxSeq |
| ASSERT_TRUE(rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs))) |
| << " (i=" << i << ")"; |
| } |
| |
| // Per Core Spec v5, Vol 3, Part A, Sec 8.3, the sequence number should now |
| // roll over to 0. |
| payload[0] = 0 << 1; |
| EXPECT_TRUE(rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs))); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, |
| ProcessPduDoesNotAdvanceSequenceNumberForOutOfSequencePdu) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| const StaticByteBuffer out_of_seq( // |
| 1 << 1, // TxSeq=1, R=0 |
| 0, // SAR and ReqSeq |
| 'h', 'e', 'l', 'l', 'o'); |
| ASSERT_FALSE(rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, out_of_seq, FrameCheckSequenceOption::kIncludeFcs))); |
| |
| const StaticByteBuffer in_seq( // |
| 0 << 1, // TxSeq=0, R=0 |
| 0, // SAR and ReqSeq |
| 'h', 'e', 'l', 'l', 'o'); |
| EXPECT_TRUE(rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, in_seq, FrameCheckSequenceOption::kIncludeFcs))); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduImmediatelyAcksUnsegmentedSdu) { |
| size_t n_acks = 0; |
| ByteBufferPtr outbound_ack; |
| auto tx_callback = [&](auto pdu) { |
| outbound_ack = std::move(pdu); |
| ++n_acks; |
| }; |
| |
| // See Core Spec, v5, Vol 3, Part A, Table 3.2 for the first two bytes. |
| const StaticByteBuffer payload(0, 0, 'h', 'e', 'l', 'l', 'o'); |
| ASSERT_TRUE(Engine(tx_callback, NoOpFailureCallback) |
| .ProcessPdu(Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, |
| FrameCheckSequenceOption::kIncludeFcs))); |
| EXPECT_EQ(1u, n_acks); |
| ASSERT_TRUE(outbound_ack); |
| ASSERT_EQ(sizeof(SimpleReceiverReadyFrame), outbound_ack->size()); |
| |
| auto ack_frame = *reinterpret_cast<const SimpleReceiverReadyFrame *>(outbound_ack->data()); |
| EXPECT_EQ(SupervisoryFunction::ReceiverReady, ack_frame.function()); |
| EXPECT_EQ(1u, ack_frame.receive_seq_num()); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduSendsCorrectReqSeqOnRollover) { |
| size_t n_acks = 0; |
| ByteBufferPtr last_ack; |
| auto tx_callback = [&](auto pdu) { |
| last_ack = std::move(pdu); |
| ++n_acks; |
| }; |
| |
| Engine rx_engine(tx_callback, NoOpFailureCallback); |
| // See Core Spec, v5, Vol 3, Part A, Table 3.2 for the first two bytes. |
| for (size_t i = 0; i < 64; ++i) { |
| const StaticByteBuffer payload(i << 1, 0, 'h', 'e', 'l', 'l', 'o'); |
| ASSERT_TRUE(rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs))) |
| << " (i=" << i << ")"; |
| } |
| EXPECT_EQ(64u, n_acks); |
| ASSERT_TRUE(last_ack); |
| ASSERT_EQ(sizeof(SimpleReceiverReadyFrame), last_ack->size()); |
| |
| auto ack_frame = *reinterpret_cast<const SimpleReceiverReadyFrame *>(last_ack->data()); |
| EXPECT_EQ(SupervisoryFunction::ReceiverReady, ack_frame.function()); |
| EXPECT_EQ(0u, ack_frame.receive_seq_num()); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduDoesNotAckOutOfSequenceFrame) { |
| size_t n_acks = 0; |
| ByteBufferPtr outbound_ack; |
| auto tx_callback = [&](auto pdu) { |
| outbound_ack = std::move(pdu); |
| ++n_acks; |
| }; |
| |
| // See Core Spec, v5, Vol 3, Part A, Table 3.2 for the first two bytes. |
| const StaticByteBuffer payload(1, 0, 'h', 'e', 'l', 'l', 'o'); |
| |
| // Per Core Spec, v5, Vol 3, Part A, Sec 8.4.7.1, receipt of an |
| // out-of-sequence frame should cause us to transmit a Reject frame. We assume |
| // that we should _not_ also transmit a ReceiverReady frame. |
| // |
| // TODO(fxbug.dev/1041): Revise this test when we start sending Reject frames. |
| ASSERT_FALSE(Engine(tx_callback, NoOpFailureCallback) |
| .ProcessPdu(Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, payload, |
| FrameCheckSequenceOption::kIncludeFcs))); |
| EXPECT_EQ(0u, n_acks); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduRespondsToReceiverReadyPollRequest) { |
| size_t n_outbound_frames = 0; |
| ByteBufferPtr last_outbound_frame; |
| auto tx_callback = [&](auto pdu) { |
| last_outbound_frame = std::move(pdu); |
| ++n_outbound_frames; |
| }; |
| |
| Engine rx_engine(tx_callback, NoOpFailureCallback); |
| // Send an I-frame to advance the receiver's sequence number. |
| // See Core Spec, v5, Vol 3, Part A, Table 3.2 for the first two bytes. |
| const StaticByteBuffer info_frame(0, 0, 'h', 'e', 'l', 'l', 'o'); |
| rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, info_frame, FrameCheckSequenceOption::kIncludeFcs)); |
| ASSERT_EQ(1u, n_outbound_frames); |
| |
| // Now send a ReceiverReady poll request. See Core Spec, v5, Vol 3, Part A, |
| // Table 3.2 and Table 3.5 for frame format. |
| const StaticByteBuffer receiver_ready_poll_request(0b1 | kExtendedControlPBitMask, 0); |
| auto local_sdu = rx_engine.ProcessPdu(Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready_poll_request, |
| FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_FALSE(local_sdu); // No payload in a ReceiverReady frame. |
| EXPECT_EQ(2u, n_outbound_frames); |
| ASSERT_TRUE(last_outbound_frame); |
| ASSERT_EQ(sizeof(SimpleSupervisoryFrame), last_outbound_frame->size()); |
| |
| auto sframe = *reinterpret_cast<const SimpleSupervisoryFrame *>(last_outbound_frame->data()); |
| EXPECT_EQ(SupervisoryFunction::ReceiverReady, sframe.function()); |
| EXPECT_EQ(1u, sframe.receive_seq_num()); |
| EXPECT_TRUE(sframe.is_poll_response()); |
| EXPECT_FALSE(sframe.is_poll_request()); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduCallsReceiveSeqNumCallback) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| |
| std::optional<uint8_t> receive_seq_num; |
| std::optional<bool> receive_is_poll_response; |
| auto receive_seq_num_callback = [&receive_seq_num, &receive_is_poll_response]( |
| uint8_t seq_num, bool is_poll_response) { |
| receive_seq_num = seq_num; |
| receive_is_poll_response = is_poll_response; |
| }; |
| rx_engine.set_receive_seq_num_callback(receive_seq_num_callback); |
| |
| // Send an I-frame containing an acknowledgment up to the 3rd frame that we transmitted. |
| // See Core Spec, v5, Vol 3, Part A, Section 3.3.2, Table 3.2 for the first two bytes. |
| StaticByteBuffer info_frame(0, 3, 'h', 'e', 'l', 'l', 'o'); |
| rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, info_frame, FrameCheckSequenceOption::kIncludeFcs)); |
| ASSERT_TRUE(receive_seq_num.has_value()); |
| EXPECT_EQ(3, receive_seq_num.value()); |
| ASSERT_TRUE(receive_is_poll_response.has_value()); |
| EXPECT_FALSE(receive_is_poll_response.value()); |
| |
| receive_is_poll_response.reset(); |
| |
| // Same as above but the 'F' bit is set. |
| info_frame[0] |= kExtendedControlFBitMask; |
| rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, info_frame, FrameCheckSequenceOption::kIncludeFcs)); |
| ASSERT_TRUE(receive_is_poll_response.has_value()); |
| EXPECT_TRUE(receive_is_poll_response.value()); |
| |
| receive_seq_num.reset(); |
| receive_is_poll_response.reset(); |
| |
| // Send an S-frame containing an acknowledgment up to the 4th frame that we transmitted. F is set. |
| // See Core Spec, v5, Vol 3, Part A, Section 3.3.2, Table 3.2 for the frame format. |
| StaticByteBuffer receiver_ready(0b1 | kExtendedControlFBitMask, 4); |
| auto local_sdu = rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| ASSERT_TRUE(receive_seq_num.has_value()); |
| EXPECT_EQ(4, receive_seq_num.value()); |
| ASSERT_TRUE(receive_is_poll_response.has_value()); |
| EXPECT_TRUE(receive_is_poll_response.value()); |
| |
| receive_is_poll_response.reset(); |
| |
| // Same as above but the 'F' bit is clear. |
| receiver_ready[0] &= ~kExtendedControlFBitMask; |
| rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| ASSERT_TRUE(receive_is_poll_response.has_value()); |
| EXPECT_FALSE(receive_is_poll_response.value()); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, ProcessPduCallsAckSeqNumCallback) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| |
| std::optional<uint8_t> ack_seq_num; |
| auto ack_seq_num_callback = [&ack_seq_num](uint8_t seq_num) { ack_seq_num = seq_num; }; |
| rx_engine.set_ack_seq_num_callback(ack_seq_num_callback); |
| |
| // Send an I-frame containing a sequence number for the first frame the receiver has sent. |
| // See Core Spec, v5, Vol 3, Part A, Section 3.3.2, Table 3.2 for the first two bytes. |
| StaticByteBuffer info_frame(0, 0, 'h', 'e', 'l', 'l', 'o'); |
| rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, info_frame, FrameCheckSequenceOption::kIncludeFcs)); |
| ASSERT_TRUE(ack_seq_num.has_value()); |
| |
| // We should now expect the next (second) frame to have a sequence number of 1. |
| EXPECT_EQ(1, ack_seq_num.value()); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, |
| ProcessPduCallsRemoteBusySetCallbackOnReceiverNotReady) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| |
| bool receive_seq_num_called = false; |
| rx_engine.set_receive_seq_num_callback([&](uint8_t, bool) { receive_seq_num_called = true; }); |
| |
| bool remote_busy_set_called = false; |
| auto remote_busy_set_callback = [&receive_seq_num_called, &remote_busy_set_called] { |
| // RemoteBusy state should be updated before ReceiveSeqNum to immediately suppress |
| // retransmissions. |
| EXPECT_FALSE(receive_seq_num_called); |
| remote_busy_set_called = true; |
| }; |
| rx_engine.set_remote_busy_set_callback(remote_busy_set_callback); |
| |
| const StaticByteBuffer receiver_not_ready(0b1 | kExtendedControlReceiverNotReadyBits, 0); |
| auto local_sdu = rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_not_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_FALSE(local_sdu); // No payload in a ReceiverNotReady frame. |
| EXPECT_TRUE(remote_busy_set_called); |
| EXPECT_TRUE(receive_seq_num_called); |
| |
| remote_busy_set_called = false; |
| local_sdu = rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_not_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_FALSE(local_sdu); // No payload in a ReceiverNotReady frame. |
| |
| // Second RNR should not invoke the callback. |
| EXPECT_FALSE(remote_busy_set_called); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, |
| ProcessPduCallsRemoteBusySetCallbackOnReceiverNotReadyAfterReceiverReadyClearedBusy) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| |
| const StaticByteBuffer receiver_not_ready(0b1 | kExtendedControlReceiverNotReadyBits, 0); |
| auto local_sdu = rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_not_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_FALSE(local_sdu); // No payload in a ReceiverNotReady frame. |
| |
| // This RR should clear RemoteBusy. |
| const StaticByteBuffer receiver_ready(0b1, 0); |
| local_sdu = rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_FALSE(local_sdu); // No payload in a ReceiverReady frame. |
| |
| bool remote_busy_set_called = false; |
| auto remote_busy_set_callback = [&remote_busy_set_called] { remote_busy_set_called = true; }; |
| rx_engine.set_remote_busy_set_callback(remote_busy_set_callback); |
| |
| // Receive a second RNR. |
| local_sdu = rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_not_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_FALSE(local_sdu); // No payload in a ReceiverNotReady frame. |
| |
| // Second RNR should invoke the callback because it's setting RemoteBusy once again. |
| EXPECT_TRUE(remote_busy_set_called); |
| } |
| |
| // Test parameter is a bitmask to the Extended Control Field. |
| class ExtendedControlFieldBitsTest : public testing::TestWithParam<uint16_t> {}; |
| |
| TEST_P(ExtendedControlFieldBitsTest, |
| ProcessPduCallsRemoteBusyClearedCallbackOnNonRnrSFrameAfterReceiverNotReady) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| |
| bool receive_seq_num_called = false; |
| int remote_busy_set_calls = 0; |
| rx_engine.set_remote_busy_set_callback([&] { remote_busy_set_calls++; }); |
| |
| int remote_busy_cleared_calls = 0; |
| auto remote_busy_cleared_callback = [&receive_seq_num_called, &remote_busy_cleared_calls] { |
| // RemoteBusy state should be updated before ReceiveSeqNum to immediately resume |
| // retransmissions. |
| EXPECT_FALSE(receive_seq_num_called); |
| remote_busy_cleared_calls++; |
| }; |
| rx_engine.set_remote_busy_cleared_callback(remote_busy_cleared_callback); |
| |
| const StaticByteBuffer receiver_not_ready(0b1 | kExtendedControlReceiverNotReadyBits, 0); |
| auto local_sdu = rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, receiver_not_ready, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_FALSE(local_sdu); // No payload in a ReceiverNotReady frame. |
| |
| EXPECT_EQ(1, remote_busy_set_calls); |
| EXPECT_EQ(0, remote_busy_cleared_calls); |
| |
| // The RNR invokes this callback but we only care about the callback ordering of the next frame. |
| rx_engine.set_receive_seq_num_callback([&](uint8_t, bool) { receive_seq_num_called = true; }); |
| |
| // This non-RNR S-Frame should clear RemoteBusy. |
| const uint16_t control_bits_to_set = GetParam(); |
| const StaticByteBuffer non_rnr_s_frame(0b1 | LowerBits(control_bits_to_set), 0); |
| local_sdu = rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, non_rnr_s_frame, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_FALSE(local_sdu); // No payload in an S-Frame. |
| |
| EXPECT_EQ(1, remote_busy_set_calls); |
| EXPECT_EQ(1, remote_busy_cleared_calls); |
| EXPECT_TRUE(receive_seq_num_called); |
| |
| // Receive a second non-RNR. |
| local_sdu = rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, non_rnr_s_frame, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_FALSE(local_sdu); // No payload in an S-Frame. |
| |
| EXPECT_EQ(1, remote_busy_set_calls); |
| EXPECT_EQ(1, remote_busy_cleared_calls); |
| |
| // Second S-Frame shouldn't invoke either callback because RemoteBusy remains cleared. |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(EnhancedRetransmissionModeRxEngineTestNonRnrSFrames, |
| ExtendedControlFieldBitsTest, |
| testing::Values(kExtendedControlReceiverReadyBits, |
| kExtendedControlRejectBits, |
| kExtendedControlSelectiveRejectBits)); |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, |
| ProcessPduCallsRangeRetransmitSetCallbackThenReceiveSeqNumCallbackOnReject) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| |
| std::optional<bool> receive_is_poll_request; |
| rx_engine.set_range_retransmit_set_callback( |
| [&](bool is_poll_request) { receive_is_poll_request = is_poll_request; }); |
| |
| std::optional<uint8_t> receive_seq_num; |
| std::optional<bool> receive_is_poll_response; |
| auto receive_seq_num_callback = [&](uint8_t seq_num, bool is_poll_response) { |
| // RangeRetransmitCallback should be called before ReceiveSeqNumCallback. |
| EXPECT_TRUE(receive_is_poll_request.has_value()); |
| receive_seq_num = seq_num; |
| receive_is_poll_response = is_poll_response; |
| }; |
| rx_engine.set_receive_seq_num_callback(receive_seq_num_callback); |
| |
| // Send a REJ S-frame containing acknowledgment up to the 3rd frame that we transmitted. |
| // See Core Spec, v5, Vol 3, Part A, Section 3.3.2, Table 3.2 for the first two bytes. |
| auto rej_frame = StaticByteBuffer(0b1 | kExtendedControlRejectBits, 3); |
| rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, rej_frame, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_TRUE(receive_seq_num.has_value()); |
| EXPECT_TRUE(receive_is_poll_response.has_value()); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, |
| ProcessPduCallsSingleRetransmitSetCallbackThenReceiveSeqNumCallbackOnSelectiveReject) { |
| Engine rx_engine(NoOpTxCallback, NoOpFailureCallback); |
| |
| std::optional<bool> receive_is_poll_request; |
| rx_engine.set_single_retransmit_set_callback( |
| [&](bool is_poll_request) { receive_is_poll_request = is_poll_request; }); |
| |
| std::optional<uint8_t> receive_seq_num; |
| std::optional<bool> receive_is_poll_response; |
| auto receive_seq_num_callback = [&](uint8_t seq_num, bool is_poll_response) { |
| // SingleRetransmitCallback should be called before ReceiveSeqNumCallback. |
| EXPECT_TRUE(receive_is_poll_request.has_value()); |
| receive_seq_num = seq_num; |
| receive_is_poll_response = is_poll_response; |
| }; |
| rx_engine.set_receive_seq_num_callback(receive_seq_num_callback); |
| |
| // Send an SREJ S-frame containing acknowledgment up to the 3rd frame that we transmitted. |
| // See Core Spec, v5, Vol 3, Part A, Section 3.3.2, Table 3.2 for the first two bytes. |
| auto srej_frame = StaticByteBuffer(0b1 | kExtendedControlSelectiveRejectBits, 3); |
| rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, srej_frame, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_TRUE(receive_seq_num.has_value()); |
| EXPECT_TRUE(receive_is_poll_response.has_value()); |
| } |
| |
| TEST(EnhancedRetransmissionModeRxEngineTest, |
| ProcessPduWithPollResponseAndPollRequestClosesChannel) { |
| bool connection_failed = false; |
| Engine rx_engine(NoOpTxCallback, [&] { connection_failed = true; }); |
| |
| // Send an RR S-frame with both poll request and response bits set. |
| // See Core Spec, v5, Vol 3, Part A, Section 3.3.2, Table 3.2 for field definitions. |
| auto rr_frame = StaticByteBuffer(0b1 | kExtendedControlFBitMask | kExtendedControlPBitMask, 0); |
| rx_engine.ProcessPdu( |
| Fragmenter(kTestHandle) |
| .BuildFrame(kTestChannelId, rr_frame, FrameCheckSequenceOption::kIncludeFcs)); |
| EXPECT_TRUE(connection_failed); |
| } |
| |
| } // namespace |
| } // namespace bt::l2cap::internal |