1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_bluetooth_sapphire/internal/host/l2cap/credit_based_flow_control_rx_engine.h"
16 
17 #include "pw_bluetooth_sapphire/internal/host/l2cap/fragmenter.h"
18 #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
19 #include "pw_unit_test/framework.h"
20 
21 namespace bt::l2cap::internal {
22 namespace {
23 
24 using Engine = CreditBasedFlowControlRxEngine;
25 
26 class CreditBasedFlowControlRxEngineTest : public ::testing::Test {
27  protected:
28   static constexpr hci_spec::ConnectionHandle kTestHandle = 0x0001;
29   static constexpr ChannelId kTestChannelId = 0x0001;
30 
engine()31   Engine& engine() { return *engine_; }
failure_callback_count()32   size_t failure_callback_count() { return failure_callback_count_; }
ToPdu(const ByteBuffer & buffer)33   PDU ToPdu(const ByteBuffer& buffer) {
34     return Fragmenter(kTestHandle)
35         .BuildFrame(kTestChannelId, buffer, FrameCheckSequenceOption::kNoFcs);
36   }
37 
ProcessPayload(const ByteBuffer & buffer)38   ByteBufferPtr ProcessPayload(const ByteBuffer& buffer) {
39     return engine().ProcessPdu(ToPdu(buffer));
40   }
41 
42  private:
43   size_t failure_callback_count_ = 0;
44   std::unique_ptr<Engine> engine_ =
__anon923920900202null45       std::make_unique<Engine>([this] { ++failure_callback_count_; });
46 };
47 
TEST_F(CreditBasedFlowControlRxEngineTest,SmallUnsegmentedSdu)48 TEST_F(CreditBasedFlowControlRxEngineTest, SmallUnsegmentedSdu) {
49   // clang-format off
50   const StaticByteBuffer payload(
51       // SDU size field (LE u16)
52       4, 0,
53       // Payload
54       't', 'e', 's', 't'
55   );
56   // clang-format on
57 
58   const ByteBufferPtr sdu = ProcessPayload(payload);
59 
60   ASSERT_TRUE(sdu);
61   EXPECT_TRUE(ContainersEqual(StaticByteBuffer('t', 'e', 's', 't'), *sdu));
62   EXPECT_EQ(0u, failure_callback_count());
63 }
64 
TEST_F(CreditBasedFlowControlRxEngineTest,LargeUnsegmentedSdu)65 TEST_F(CreditBasedFlowControlRxEngineTest, LargeUnsegmentedSdu) {
66   // clang-format off
67   const StaticByteBuffer payload(
68       // SDU size field (LE u16)
69       58, 0,
70       // Payload
71       'L', 'o', 'r', 'e', 'm', ' ', 'i', 'p', 's', 'u', 'm', ' ', 'd', 'o', 'l',
72       'o', 'r', ' ', 's', 'i', 't', ' ', 'a', 'm', 'e', 't', ',', ' ', 'c', 'o',
73       'n', 's', 'e', 'c', 't', 'e', 't', 'u', 'r', ' ', 'a', 'd', 'i', 'p', 'i',
74       's', 'c', 'i', 'n', 'g', ' ', 'e', 'l', 'i', 't', '.', ' ', 'S'
75   );
76   // clang-format on
77 
78   const ByteBufferPtr sdu = ProcessPayload(payload);
79   ASSERT_TRUE(sdu);
80   EXPECT_TRUE(ContainersEqual(payload.view(2), *sdu));
81   EXPECT_EQ(0u, failure_callback_count());
82 }
83 
TEST_F(CreditBasedFlowControlRxEngineTest,SduSegmentedIntoManySmallPdus)84 TEST_F(CreditBasedFlowControlRxEngineTest, SduSegmentedIntoManySmallPdus) {
85   // clang-format off
86   EXPECT_FALSE(ProcessPayload(StaticByteBuffer(
87       // SDU size field (LE u16)
88       16, 0,
89       // First four bytes of payload
90       't', 'e', 's', 't'
91   )));
92   // clang-format on
93 
94   EXPECT_FALSE(ProcessPayload(StaticByteBuffer('i', 'n', 'g', ' ')));
95   EXPECT_FALSE(ProcessPayload(StaticByteBuffer('f', 'o', 'r', ' ')));
96   const ByteBufferPtr sdu =
97       ProcessPayload(StaticByteBuffer('b', 'u', 'g', 's'));
98 
99   // clang-format off
100   StaticByteBuffer expected(
101       't', 'e', 's', 't', 'i', 'n', 'g', ' ',
102       'f', 'o', 'r', ' ', 'b', 'u', 'g', 's'
103   );
104   // clang-format on
105 
106   ASSERT_TRUE(sdu);
107   EXPECT_TRUE(ContainersEqual(expected, *sdu));
108   EXPECT_EQ(0u, failure_callback_count());
109 }
110 
TEST_F(CreditBasedFlowControlRxEngineTest,FailSduSmallerThanPayload)111 TEST_F(CreditBasedFlowControlRxEngineTest, FailSduSmallerThanPayload) {
112   // clang-format off
113   const StaticByteBuffer payload(
114       // SDU size field (LE u16)
115       4, 0,
116       // Payload
117       'f', 'a', 'i', 'l', 'i', 'u', 'r', 'e'
118   );
119   // clang-format on
120 
121   EXPECT_FALSE(ProcessPayload(payload));
122   EXPECT_EQ(1u, failure_callback_count());
123 }
124 
TEST_F(CreditBasedFlowControlRxEngineTest,FailSduSmallerThanPayloadSegmented)125 TEST_F(CreditBasedFlowControlRxEngineTest, FailSduSmallerThanPayloadSegmented) {
126   // clang-format off
127   const StaticByteBuffer payload(
128       // SDU size field (LE u16)
129       5, 0,
130       // Payload
131       'f', 'a', 'i', 'l'
132   );
133   // clang-format on
134 
135   EXPECT_FALSE(ProcessPayload(payload));
136   EXPECT_EQ(0u, failure_callback_count());
137   EXPECT_FALSE(ProcessPayload(StaticByteBuffer('i', 'u', 'r', 'e')));
138   EXPECT_EQ(1u, failure_callback_count());
139 }
140 
TEST_F(CreditBasedFlowControlRxEngineTest,InitialFrameMissingSduSize)141 TEST_F(CreditBasedFlowControlRxEngineTest, InitialFrameMissingSduSize) {
142   const ByteBufferPtr sdu = ProcessPayload(StaticByteBuffer(0));
143   EXPECT_FALSE(sdu);
144   EXPECT_EQ(1u, failure_callback_count());
145 }
146 
147 }  // namespace
148 }  // namespace bt::l2cap::internal
149