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_tx_engine.h"
16
17 #include <pw_async/fake_dispatcher_fixture.h>
18
19 #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
20 #include "pw_bluetooth_sapphire/internal/host/l2cap/fake_tx_channel.h"
21 #include "pw_bluetooth_sapphire/internal/host/l2cap/frame_headers.h"
22 #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
23 #include "pw_unit_test/framework.h"
24
25 namespace bt::l2cap::internal {
26 namespace {
27
28 using Engine = CreditBasedFlowControlTxEngine;
29
30 class CreditBasedFlowControlTxEngineTest : public ::testing::Test {
31 protected:
32 static constexpr ChannelId kTestChannelId = 170u;
33 static constexpr auto kTestMtu = 256u;
34 static constexpr auto kTestMps = 64u;
35 static constexpr auto kInitialCredits = 1u;
36
SetUp()37 void SetUp() override {
38 channel_.HandleSendFrame(
39 [this](ByteBufferPtr pdu) { sent_frames().push_back(std::move(pdu)); });
40 }
41
engine()42 Engine& engine() { return *engine_; }
sent_frames()43 std::vector<ByteBufferPtr>& sent_frames() { return sent_frames_; }
channel()44 FakeTxChannel& channel() { return channel_; }
45
ProcessSdu(ByteBufferPtr sdu)46 void ProcessSdu(ByteBufferPtr sdu) {
47 channel().QueueSdu(std::move(sdu));
48 engine().NotifySduQueued();
49 }
50
51 private:
52 std::unique_ptr<Engine> engine_ = std::make_unique<Engine>(
53 kTestChannelId,
54 kTestMtu,
55 channel_,
56 CreditBasedFlowControlMode::kLeCreditBasedFlowControl,
57 kTestMps,
58 kInitialCredits);
59
60 std::vector<ByteBufferPtr> sent_frames_{};
61
62 FakeTxChannel channel_{};
63 };
64
TEST_F(CreditBasedFlowControlTxEngineTest,SendBasicPayload)65 TEST_F(CreditBasedFlowControlTxEngineTest, SendBasicPayload) {
66 StaticByteBuffer<4> basic{'t', 'e', 's', 't'};
67 ProcessSdu(std::make_unique<DynamicByteBuffer>(basic));
68
69 ASSERT_EQ(sent_frames().size(), 1u);
70 auto& sent = sent_frames()[0];
71
72 ASSERT_TRUE(sent);
73 EXPECT_EQ(sent->size(), 10u);
74 EXPECT_EQ(channel().queue_size(), 0u);
75
76 // clang-format off: Formatter wants each value on a separate line.
77 StaticByteBuffer<10> expected{
78 // PDU size field (LE u16)
79 6, 0,
80 // Channel field (LE u16)
81 kTestChannelId, 0,
82 // SDU size field (LE u16)
83 4, 0,
84 // Payload
85 't', 'e', 's', 't',
86 };
87 // clang-format on
88
89 EXPECT_TRUE(ContainersEqual(*sent, expected));
90 }
91
TEST_F(CreditBasedFlowControlTxEngineTest,SendSegmentedPayload)92 TEST_F(CreditBasedFlowControlTxEngineTest, SendSegmentedPayload) {
93 StaticByteBuffer<72> segmented{
94 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
95 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f',
96 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e',
97 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd',
98 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
99 };
100
101 // Make sure credits are available to send the entire payload.
102 EXPECT_TRUE(engine().AddCredits(5));
103 ProcessSdu(std::make_unique<DynamicByteBuffer>(segmented));
104 EXPECT_EQ(channel().queue_size(), 0u);
105
106 ASSERT_EQ(sent_frames().size(), 2u);
107 auto& sent_first = sent_frames()[0];
108 auto& sent_second = sent_frames()[1];
109
110 ASSERT_TRUE(sent_first);
111 EXPECT_EQ(sent_first->size(), kTestMps);
112
113 // clang-format off: Formatter wants each value on a separate line.
114 StaticByteBuffer<kTestMps> expected_first{
115 // PDU size field (LE u16)
116 (kTestMps - 4), 0,
117 // Channel field (LE u16)
118 kTestChannelId, 0,
119 // SDU size field (LE u16)
120 72, 0,
121 // Payload
122 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
123 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f',
124 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e',
125 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b',
126 };
127 // clang-format on
128
129 ASSERT_TRUE(sent_second);
130 EXPECT_EQ(sent_second->size(), 18u);
131
132 // clang-format off: Formatter wants each value on a separate line.
133 StaticByteBuffer<18> expected_second{
134 // PDU size field (LE u16)
135 14, 0,
136 // Channel field (LE u16)
137 kTestChannelId, 0,
138 // Payload
139 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
140 };
141 // clang-format on
142
143 EXPECT_TRUE(ContainersEqual(*sent_first, expected_first));
144 EXPECT_TRUE(ContainersEqual(*sent_second, expected_second));
145 }
146
TEST_F(CreditBasedFlowControlTxEngineTest,NoSendWithoutCreditsBasic)147 TEST_F(CreditBasedFlowControlTxEngineTest, NoSendWithoutCreditsBasic) {
148 StaticByteBuffer<5> first{'f', 'i', 'r', 's', 't'};
149 StaticByteBuffer<6> second{'s', 'e', 'c', 'o', 'n', 'd'};
150
151 ProcessSdu(std::make_unique<DynamicByteBuffer>(first));
152
153 ASSERT_EQ(sent_frames().size(), 1u);
154 auto& sent_first = sent_frames()[0];
155
156 ASSERT_TRUE(sent_first);
157 EXPECT_EQ(sent_first->size(), 11u);
158
159 // clang-format off: Formatter wants each value on a separate line.
160 StaticByteBuffer<11> expected_first{
161 // PDU size field (LE u16)
162 7, 0,
163 // Channel field (LE u16)
164 kTestChannelId, 0,
165 // SDU size field (LE u16)
166 5, 0,
167 // Payload
168 'f', 'i', 'r', 's', 't',
169 };
170 // clang-format on
171
172 EXPECT_TRUE(ContainersEqual(*sent_first, expected_first));
173 EXPECT_EQ(engine().credits(), 0);
174 EXPECT_EQ(engine().segments_count(), 0u);
175 EXPECT_EQ(channel().queue_size(), 0u);
176
177 ProcessSdu(std::make_unique<DynamicByteBuffer>(second));
178
179 // Ensure the second send did not occur yet, as credits are exhausted.
180 EXPECT_EQ(engine().credits(), 0);
181 EXPECT_EQ(engine().segments_count(), 0u);
182 EXPECT_EQ(sent_frames().size(), 1u);
183 EXPECT_EQ(channel().queue_size(), 1u);
184
185 engine().AddCredits(1);
186
187 // Now confirm the send did occur.
188 EXPECT_EQ(engine().credits(), 0);
189 EXPECT_EQ(engine().segments_count(), 0u);
190 EXPECT_EQ(channel().queue_size(), 0u);
191 ASSERT_EQ(sent_frames().size(), 2u);
192 auto& sent_second = sent_frames()[1];
193 ASSERT_TRUE(sent_second);
194 EXPECT_EQ(sent_second->size(), 12u);
195
196 // clang-format off: Formatter wants each value on a separate line.
197 StaticByteBuffer<12> expected_second{
198 // PDU size field (LE u16)
199 8, 0,
200 // Channel field (LE u16)
201 170, 0,
202 // SDU size field (LE u16)
203 6, 0,
204 // Payload
205 's', 'e', 'c', 'o', 'n', 'd',
206 };
207 // clang-format on
208
209 EXPECT_TRUE(ContainersEqual(*sent_second, expected_second));
210 }
211
TEST_F(CreditBasedFlowControlTxEngineTest,NoSendWithoutCreditsSegmented)212 TEST_F(CreditBasedFlowControlTxEngineTest, NoSendWithoutCreditsSegmented) {
213 StaticByteBuffer<150> segmented{
214 'L', 'o', 'r', 'e', 'm', ' ', 'i', 'p', 's', 'u', 'm', ' ', 'd', 'o',
215 'l', 'o', 'r', ' ', 's', 'i', 't', ' ', 'a', 'm', 'e', 't', ',', ' ',
216 'c', 'o', 'n', 's', 'e', 'c', 't', 'e', 't', 'u', 'r', ' ', 'a', 'd',
217 'i', 'p', 'i', 's', 'c', 'i', 'n', 'g', ' ', 'e', 'l', 'i', 't', '.',
218 ' ', 'S', 'e', 'd', ' ', 'e', 't', ' ', 'v', 'e', 'h', 'i', 'c', 'u',
219 'l', 'a', ' ', 'e', 'n', 'i', 'm', '.', ' ', 'U', 't', ' ', 's', 'i',
220 't', ' ', 'a', 'm', 'e', 't', ' ', 'm', 'a', 'g', 'n', 'a', ' ', 'm',
221 'a', 'u', 'r', 'i', 's', '.', ' ', 'U', 't', ' ', 's', 'e', 'd', ' ',
222 't', 'u', 'r', 'p', 'i', 's', ' ', 'n', 'i', 'b', 'h', '.', ' ', 'V',
223 'e', 's', 't', 'i', 'b', 'u', 'l', 'u', 'm', ' ', 's', 'e', 'd', ' ',
224 't', 'o', 'r', 't', 'o', 'r', ' ', 'i', 'd', '.'};
225
226 ProcessSdu(std::make_unique<DynamicByteBuffer>(segmented));
227 EXPECT_EQ(engine().credits(), 0);
228 EXPECT_EQ(engine().segments_count(), 2u);
229 EXPECT_EQ(channel().queue_size(), 0u);
230
231 ASSERT_EQ(sent_frames().size(), 1u);
232 auto& sent_first = sent_frames()[0];
233 ASSERT_TRUE(sent_first);
234 EXPECT_EQ(sent_first->size(), kTestMps);
235
236 // clang-format off: Formatter wants each value on a separate line.
237 StaticByteBuffer<kTestMps> expected_first{
238 // PDU size field (LE u16)
239 (kTestMps - 4), 0,
240 // Channel field (LE u16)
241 170, 0,
242 // SDU size field (LE u16)
243 150, 0,
244 // Payload
245 'L', 'o', 'r', 'e', 'm', ' ', 'i', 'p', 's', 'u', 'm', ' ', 'd', 'o', 'l',
246 'o', 'r', ' ', 's', 'i', 't', ' ', 'a', 'm', 'e', 't', ',', ' ', 'c', 'o',
247 'n', 's', 'e', 'c', 't', 'e', 't', 'u', 'r', ' ', 'a', 'd', 'i', 'p', 'i',
248 's', 'c', 'i', 'n', 'g', ' ', 'e', 'l', 'i', 't', '.', ' ', 'S',
249 };
250 // clang-format on
251
252 EXPECT_TRUE(ContainersEqual(*sent_first, expected_first));
253
254 engine().AddCredits(1);
255
256 ASSERT_EQ(sent_frames().size(), 2u);
257 auto& sent_second = sent_frames()[1];
258 ASSERT_TRUE(sent_second);
259 EXPECT_EQ(sent_second->size(), kTestMps);
260 EXPECT_EQ(engine().credits(), 0);
261 EXPECT_EQ(engine().segments_count(), 1u);
262
263 // clang-format off: Formatter wants each value on a separate line.
264 StaticByteBuffer<kTestMps> expected_second{
265 // PDU size field (LE u16)
266 (kTestMps - 4), 0,
267 // Channel field (LE u16)
268 170, 0,
269 // Payload
270 'e', 'd', ' ', 'e', 't', ' ', 'v', 'e', 'h', 'i', 'c', 'u', 'l', 'a', ' ',
271 'e', 'n', 'i', 'm', '.', ' ', 'U', 't', ' ', 's', 'i', 't', ' ', 'a', 'm',
272 'e', 't', ' ', 'm', 'a', 'g', 'n', 'a', ' ', 'm', 'a', 'u', 'r', 'i', 's',
273 '.', ' ', 'U', 't', ' ', 's', 'e', 'd', ' ', 't', 'u', 'r', 'p', 'i', 's',
274 };
275 // clang-format on
276
277 EXPECT_TRUE(ContainersEqual(*sent_second, expected_second));
278
279 engine().AddCredits(10);
280
281 ASSERT_EQ(sent_frames().size(), 3u);
282 auto& sent_third = sent_frames()[2];
283 ASSERT_TRUE(sent_third);
284 EXPECT_EQ(sent_third->size(), 36u);
285 EXPECT_EQ(engine().credits(), 9);
286 EXPECT_EQ(engine().segments_count(), 0u);
287
288 // clang-format off: Formatter wants each value on a separate line.
289 StaticByteBuffer<36> expected_third{
290 // PDU size field (LE u16)
291 32, 0,
292 // Channel field (LE u16)
293 170, 0,
294 // Payload
295 ' ', 'n', 'i', 'b', 'h', '.', ' ', 'V', 'e', 's', 't', 'i', 'b', 'u', 'l',
296 'u', 'm', ' ', 's', 'e', 'd', ' ', 't', 'o', 'r', 't', 'o', 'r', ' ', 'i',
297 'd', '.'
298 };
299 // clang-format on
300
301 EXPECT_TRUE(ContainersEqual(*sent_third, expected_third));
302 }
303
TEST_F(CreditBasedFlowControlTxEngineTest,DoesNotAcceptSduWhilePdusQueued)304 TEST_F(CreditBasedFlowControlTxEngineTest, DoesNotAcceptSduWhilePdusQueued) {
305 StaticByteBuffer<150> segmented{
306 'L', 'o', 'r', 'e', 'm', ' ', 'i', 'p', 's', 'u', 'm', ' ', 'd', 'o',
307 'l', 'o', 'r', ' ', 's', 'i', 't', ' ', 'a', 'm', 'e', 't', ',', ' ',
308 'c', 'o', 'n', 's', 'e', 'c', 't', 'e', 't', 'u', 'r', ' ', 'a', 'd',
309 'i', 'p', 'i', 's', 'c', 'i', 'n', 'g', ' ', 'e', 'l', 'i', 't', '.',
310 ' ', 'S', 'e', 'd', ' ', 'e', 't', ' ', 'v', 'e', 'h', 'i', 'c', 'u',
311 'l', 'a', ' ', 'e', 'n', 'i', 'm', '.', ' ', 'U', 't', ' ', 's', 'i',
312 't', ' ', 'a', 'm', 'e', 't', ' ', 'm', 'a', 'g', 'n', 'a', ' ', 'm',
313 'a', 'u', 'r', 'i', 's', '.', ' ', 'U', 't', ' ', 's', 'e', 'd', ' ',
314 't', 'u', 'r', 'p', 'i', 's', ' ', 'n', 'i', 'b', 'h', '.', ' ', 'V',
315 'e', 's', 't', 'i', 'b', 'u', 'l', 'u', 'm', ' ', 's', 'e', 'd', ' ',
316 't', 'o', 'r', 't', 'o', 'r', ' ', 'i', 'd', '.'};
317
318 ProcessSdu(std::make_unique<DynamicByteBuffer>(segmented));
319 EXPECT_EQ(engine().credits(), 0);
320 EXPECT_EQ(engine().segments_count(), 2u);
321 EXPECT_EQ(channel().queue_size(), 0u);
322
323 ASSERT_EQ(sent_frames().size(), 1u);
324 auto& sent_first = sent_frames()[0];
325 ASSERT_TRUE(sent_first);
326 EXPECT_EQ(sent_first->size(), kTestMps);
327
328 // clang-format off: Formatter wants each value on a separate line.
329 StaticByteBuffer<kTestMps> expected_first{
330 // PDU size field (LE u16)
331 (kTestMps - 4), 0,
332 // Channel field (LE u16)
333 170, 0,
334 // SDU size field (LE u16)
335 150, 0,
336 // Payload
337 'L', 'o', 'r', 'e', 'm', ' ', 'i', 'p', 's', 'u', 'm', ' ', 'd', 'o', 'l',
338 'o', 'r', ' ', 's', 'i', 't', ' ', 'a', 'm', 'e', 't', ',', ' ', 'c', 'o',
339 'n', 's', 'e', 'c', 't', 'e', 't', 'u', 'r', ' ', 'a', 'd', 'i', 'p', 'i',
340 's', 'c', 'i', 'n', 'g', ' ', 'e', 'l', 'i', 't', '.', ' ', 'S',
341 };
342 // clang-format on
343
344 EXPECT_TRUE(ContainersEqual(*sent_first, expected_first));
345
346 StaticByteBuffer<8> next_sdu{'n', 'e', 'x', 't', '_', 's', 'd', 'u'};
347 ProcessSdu(std::make_unique<DynamicByteBuffer>(next_sdu));
348
349 EXPECT_EQ(sent_frames().size(), 1u);
350 EXPECT_EQ(engine().credits(), 0);
351 EXPECT_EQ(engine().segments_count(), 2u);
352 EXPECT_EQ(channel().queue_size(), 1u);
353
354 engine().AddCredits(3);
355
356 ASSERT_EQ(sent_frames().size(), 4u);
357 auto& sent_second = sent_frames()[1];
358 auto& sent_third = sent_frames()[2];
359 auto& sent_fourth = sent_frames()[3];
360
361 ASSERT_TRUE(sent_second);
362 EXPECT_EQ(sent_second->size(), kTestMps);
363
364 ASSERT_TRUE(sent_third);
365 EXPECT_EQ(sent_third->size(), 36u);
366
367 ASSERT_TRUE(sent_fourth);
368 EXPECT_EQ(sent_fourth->size(), 14u);
369
370 EXPECT_EQ(engine().credits(), 0);
371 EXPECT_EQ(engine().segments_count(), 0u);
372 EXPECT_EQ(channel().queue_size(), 0u);
373
374 // clang-format off: Formatter wants each value on a separate line.
375 StaticByteBuffer<kTestMps> expected_second{
376 // PDU size field (LE u16)
377 (kTestMps - 4), 0,
378 // Channel field (LE u16)
379 170, 0,
380 // Payload
381 'e', 'd', ' ', 'e', 't', ' ', 'v', 'e', 'h', 'i', 'c', 'u', 'l', 'a', ' ',
382 'e', 'n', 'i', 'm', '.', ' ', 'U', 't', ' ', 's', 'i', 't', ' ', 'a', 'm',
383 'e', 't', ' ', 'm', 'a', 'g', 'n', 'a', ' ', 'm', 'a', 'u', 'r', 'i', 's',
384 '.', ' ', 'U', 't', ' ', 's', 'e', 'd', ' ', 't', 'u', 'r', 'p', 'i', 's',
385 };
386 // clang-format on
387
388 EXPECT_TRUE(ContainersEqual(*sent_second, expected_second));
389
390 // clang-format off: Formatter wants each value on a separate line.
391 StaticByteBuffer<36> expected_third{
392 // PDU size field (LE u16)
393 32, 0,
394 // Channel field (LE u16)
395 170, 0,
396 // Payload
397 ' ', 'n', 'i', 'b', 'h', '.', ' ', 'V', 'e', 's', 't', 'i', 'b', 'u', 'l',
398 'u', 'm', ' ', 's', 'e', 'd', ' ', 't', 'o', 'r', 't', 'o', 'r', ' ', 'i',
399 'd', '.'
400 };
401 // clang-format on
402
403 EXPECT_TRUE(ContainersEqual(*sent_third, expected_third));
404
405 // clang-format off: Formatter wants each value on a separate line.
406 StaticByteBuffer<14> expected_fourth{
407 // PDU size field (LE u16)
408 10, 0,
409 // Channel field (LE u16)
410 170, 0,
411 // SDU size field (LE u16)
412 8, 0,
413 // Payload
414 'n', 'e', 'x', 't', '_', 's', 'd', 'u',
415 };
416 // clang-format on
417
418 EXPECT_TRUE(ContainersEqual(*sent_fourth, expected_fourth));
419 }
420
TEST_F(CreditBasedFlowControlTxEngineTest,DoesNotAcceptOversizedSdu)421 TEST_F(CreditBasedFlowControlTxEngineTest, DoesNotAcceptOversizedSdu) {
422 StaticByteBuffer<kTestMtu + 1> oversized{};
423 ProcessSdu(std::make_unique<DynamicByteBuffer>(oversized));
424
425 EXPECT_EQ(engine().credits(), 1);
426 EXPECT_EQ(engine().segments_count(), 0u);
427 EXPECT_EQ(channel().queue_size(), 0u);
428 ASSERT_EQ(sent_frames().size(), 0u);
429 }
430
TEST_F(CreditBasedFlowControlTxEngineTest,AddCreditsOverCap)431 TEST_F(CreditBasedFlowControlTxEngineTest, AddCreditsOverCap) {
432 EXPECT_FALSE(engine().AddCredits(65535));
433 EXPECT_EQ(engine().credits(), 1u);
434 EXPECT_TRUE(engine().AddCredits(3000));
435 EXPECT_EQ(engine().credits(), 3001u);
436 EXPECT_TRUE(engine().AddCredits(50000));
437 EXPECT_EQ(engine().credits(), 53001u);
438 EXPECT_FALSE(engine().AddCredits(12535));
439 EXPECT_EQ(engine().credits(), 53001u);
440 EXPECT_FALSE(engine().AddCredits(65535));
441 EXPECT_EQ(engine().credits(), 53001u);
442 EXPECT_TRUE(engine().AddCredits(12534));
443 EXPECT_EQ(engine().credits(), 65535u);
444 EXPECT_FALSE(engine().AddCredits(1));
445 EXPECT_EQ(engine().credits(), 65535u);
446 EXPECT_FALSE(engine().AddCredits(42));
447 EXPECT_EQ(engine().credits(), 65535u);
448 EXPECT_FALSE(engine().AddCredits(99));
449 EXPECT_EQ(engine().credits(), 65535u);
450 EXPECT_FALSE(engine().AddCredits(32768));
451 EXPECT_EQ(engine().credits(), 65535u);
452 EXPECT_FALSE(engine().AddCredits(32767));
453 EXPECT_EQ(engine().credits(), 65535u);
454 EXPECT_FALSE(engine().AddCredits(65535));
455 EXPECT_EQ(engine().credits(), 65535u);
456 }
457
458 } // namespace
459 } // namespace bt::l2cap::internal
460