1 // Copyright 2023 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/fragmenter.h"
16
17 #include <pw_assert/check.h>
18
19 #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
20 #include "pw_bluetooth_sapphire/internal/host/l2cap/pdu.h"
21 #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
22 #include "pw_unit_test/framework.h"
23
24 namespace bt::l2cap {
25 namespace {
26
27 constexpr hci_spec::ConnectionHandle kTestHandle = 0x0001;
28 constexpr ChannelId kTestChannelId = 0x0001;
29
TEST(FragmenterTest,OutboundFrameEmptyPayload)30 TEST(FragmenterTest, OutboundFrameEmptyPayload) {
31 StaticByteBuffer kExpectedFrame(
32 // Basic L2CAP header (0-length Information Payload)
33 0x00,
34 0x00,
35 0x01,
36 0x00);
37
38 OutboundFrame frame(
39 kTestChannelId, BufferView(), FrameCheckSequenceOption::kNoFcs);
40 EXPECT_EQ(kExpectedFrame.size(), frame.size());
41 decltype(kExpectedFrame) out_buffer;
42 frame.WriteToFragment(out_buffer.mutable_view(), 0);
43
44 EXPECT_TRUE(ContainersEqual(kExpectedFrame, out_buffer));
45 }
46
TEST(FragmenterTest,OutboundFrameEmptyPayloadWithFcs)47 TEST(FragmenterTest, OutboundFrameEmptyPayloadWithFcs) {
48 StaticByteBuffer kExpectedFrame(
49 // Basic L2CAP header (2-byte Information Payload)
50 0x02,
51 0x00,
52 0x01,
53 0x00,
54
55 // FCS over preceding header (no other informational data)
56 0x00,
57 0x28);
58
59 OutboundFrame frame(
60 kTestChannelId, BufferView(), FrameCheckSequenceOption::kIncludeFcs);
61 EXPECT_EQ(kExpectedFrame.size(), frame.size());
62 decltype(kExpectedFrame) out_buffer;
63 frame.WriteToFragment(out_buffer.mutable_view(), 0);
64
65 EXPECT_TRUE(ContainersEqual(kExpectedFrame, out_buffer));
66
67 // Reset
68 out_buffer.Fill(0xaa);
69
70 // Copy just FCS footer
71 frame.WriteToFragment(out_buffer.mutable_view(4), 4);
72 EXPECT_EQ(0x00, out_buffer[4]);
73 EXPECT_EQ(0x28, out_buffer[5]);
74 }
75
TEST(FragmenterTest,OutboundFrameExactFit)76 TEST(FragmenterTest, OutboundFrameExactFit) {
77 StaticByteBuffer payload('T', 'e', 's', 't');
78
79 StaticByteBuffer kExpectedFrame(
80 // Basic L2CAP header
81 0x04,
82 0x00,
83 0x01,
84 0x00,
85
86 // Payload
87 'T',
88 'e',
89 's',
90 't');
91
92 OutboundFrame frame(
93 kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
94 EXPECT_EQ(kExpectedFrame.size(), frame.size());
95 decltype(kExpectedFrame) out_buffer;
96 frame.WriteToFragment(out_buffer.mutable_view(), 0);
97
98 EXPECT_TRUE(ContainersEqual(kExpectedFrame, out_buffer));
99 }
100
TEST(FragmenterTest,OutboundFrameExactFitWithFcs)101 TEST(FragmenterTest, OutboundFrameExactFitWithFcs) {
102 // Test data from v5.0, Vol 3, Part A, Section 3.3.5, Example 1
103 StaticByteBuffer payload(
104 0x02, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09);
105
106 StaticByteBuffer kExpectedFrame(
107 // Basic L2CAP header
108 0x0e,
109 0x00,
110 0x40,
111 0x00,
112
113 // Payload
114 0x02,
115 0x00,
116 0x00,
117 0x01,
118 0x02,
119 0x03,
120 0x04,
121 0x05,
122 0x06,
123 0x07,
124 0x08,
125 0x09,
126
127 // FCS
128 0x38,
129 0x61);
130
131 OutboundFrame frame(
132 ChannelId(0x0040), payload, FrameCheckSequenceOption::kIncludeFcs);
133 EXPECT_EQ(kExpectedFrame.size(), frame.size());
134 decltype(kExpectedFrame) out_buffer;
135 frame.WriteToFragment(out_buffer.mutable_view(), 0);
136
137 EXPECT_TRUE(ContainersEqual(kExpectedFrame, out_buffer));
138 }
139
TEST(FragmenterTest,OutboundFrameOffsetInHeader)140 TEST(FragmenterTest, OutboundFrameOffsetInHeader) {
141 StaticByteBuffer payload('T', 'e', 's', 't');
142
143 StaticByteBuffer kExpectedFrameChunk(
144 // Basic L2CAP header (minus first byte)
145 0x00,
146 0x01,
147 0x00,
148
149 // Payload (first byte only, limited by size of output buffer)
150 'T',
151 'e',
152 's',
153 't',
154
155 // FCS
156 0xa4,
157 0xc3);
158
159 OutboundFrame frame(
160 kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs);
161 EXPECT_EQ(sizeof(BasicHeader) + payload.size() + sizeof(FrameCheckSequence),
162 frame.size());
163 decltype(kExpectedFrameChunk) out_buffer;
164 frame.WriteToFragment(out_buffer.mutable_view(), 1);
165
166 EXPECT_TRUE(ContainersEqual(kExpectedFrameChunk, out_buffer));
167 }
168
TEST(FragmenterTest,OutboundFrameOffsetInPayload)169 TEST(FragmenterTest, OutboundFrameOffsetInPayload) {
170 StaticByteBuffer payload('T', 'e', 's', 't');
171
172 StaticByteBuffer kExpectedFrameChunk(
173 // Payload
174 'e',
175 's',
176 't',
177
178 // First byte of FCS
179 0xa4);
180
181 OutboundFrame frame(
182 kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs);
183 EXPECT_EQ(sizeof(BasicHeader) + payload.size() + sizeof(FrameCheckSequence),
184 frame.size());
185 decltype(kExpectedFrameChunk) out_buffer;
186 frame.WriteToFragment(out_buffer.mutable_view(), sizeof(BasicHeader) + 1);
187
188 EXPECT_TRUE(ContainersEqual(kExpectedFrameChunk, out_buffer));
189 }
190
TEST(FragmenterTest,OutboundFrameOffsetInFcs)191 TEST(FragmenterTest, OutboundFrameOffsetInFcs) {
192 StaticByteBuffer payload('T', 'e', 's', 't');
193
194 // Second and last byte of FCS
195 StaticByteBuffer kExpectedFrameChunk(0xc3);
196
197 OutboundFrame frame(
198 kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs);
199 EXPECT_EQ(sizeof(BasicHeader) + payload.size() + sizeof(FrameCheckSequence),
200 frame.size());
201 decltype(kExpectedFrameChunk) out_buffer;
202 frame.WriteToFragment(out_buffer.mutable_view(),
203 sizeof(BasicHeader) + payload.size() + 1);
204
205 EXPECT_TRUE(ContainersEqual(kExpectedFrameChunk, out_buffer));
206 }
207
208 // This isn't expected to happen from Fragmenter.
TEST(FragmenterTest,OutboundFrameOutBufferBigger)209 TEST(FragmenterTest, OutboundFrameOutBufferBigger) {
210 StaticByteBuffer payload('T', 'e', 's', 't');
211
212 StaticByteBuffer kExpectedFrameChunk(
213 // Basic L2CAP header (minus first two bytes)
214 0x01,
215 0x00,
216
217 // Payload
218 'T',
219 'e',
220 's',
221 't',
222
223 // Extraneous unused bytes
224 0,
225 0,
226 0);
227
228 OutboundFrame frame(
229 kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
230 EXPECT_EQ(sizeof(BasicHeader) + payload.size(), frame.size());
231 decltype(kExpectedFrameChunk) out_buffer;
232 out_buffer.SetToZeros();
233 frame.WriteToFragment(out_buffer.mutable_view(), 2);
234
235 EXPECT_TRUE(ContainersEqual(kExpectedFrameChunk, out_buffer));
236 }
237
TEST(FragmenterTest,EmptyPayload)238 TEST(FragmenterTest, EmptyPayload) {
239 BufferView payload;
240
241 StaticByteBuffer expected_fragment(
242 // ACL data header
243 0x01,
244 0x00,
245 0x04,
246 0x00,
247
248 // Basic L2CAP header (0-length Information Payload)
249 0x00,
250 0x00,
251 0x01,
252 0x00);
253
254 // Make the fragment limit a lot larger than the test frame size.
255 Fragmenter fragmenter(kTestHandle, 1024);
256 PDU pdu = fragmenter.BuildFrame(
257 kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
258 ASSERT_TRUE(pdu.is_valid());
259 EXPECT_EQ(1u, pdu.fragment_count());
260
261 auto fragments = pdu.ReleaseFragments();
262
263 EXPECT_TRUE(
264 ContainersEqual(expected_fragment, (*fragments.begin())->view().data()));
265 }
266
TEST(FragmenterTest,SingleFragment)267 TEST(FragmenterTest, SingleFragment) {
268 StaticByteBuffer payload('T', 'e', 's', 't');
269
270 StaticByteBuffer expected_fragment(
271 // ACL data header
272 0x01,
273 0x00,
274 0x08,
275 0x00,
276
277 // Basic L2CAP header
278 0x04,
279 0x00,
280 0x01,
281 0x00,
282 'T',
283 'e',
284 's',
285 't');
286
287 // Make the fragment limit a lot larger than the test frame size.
288 Fragmenter fragmenter(kTestHandle, 1024);
289 PDU pdu = fragmenter.BuildFrame(
290 kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
291 ASSERT_TRUE(pdu.is_valid());
292 EXPECT_EQ(1u, pdu.fragment_count());
293
294 auto fragments = pdu.ReleaseFragments();
295
296 EXPECT_TRUE(
297 ContainersEqual(expected_fragment, (*fragments.begin())->view().data()));
298 }
299
TEST(FragmenterTest,SingleFragmentExactFit)300 TEST(FragmenterTest, SingleFragmentExactFit) {
301 StaticByteBuffer payload('T', 'e', 's', 't');
302
303 StaticByteBuffer expected_fragment(
304 // ACL data header
305 0x01,
306 0x00,
307 0x08,
308 0x00,
309
310 // Basic L2CAP header
311 0x04,
312 0x00,
313 0x01,
314 0x00,
315 'T',
316 'e',
317 's',
318 't');
319
320 // Make the fragment limit large enough to fit exactly one B-frame containing
321 // |payload|.
322 Fragmenter fragmenter(
323 kTestHandle, static_cast<uint16_t>(payload.size() + sizeof(BasicHeader)));
324 PDU pdu = fragmenter.BuildFrame(
325 kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
326 ASSERT_TRUE(pdu.is_valid());
327 EXPECT_EQ(1u, pdu.fragment_count());
328
329 auto fragments = pdu.ReleaseFragments();
330
331 EXPECT_TRUE(
332 ContainersEqual(expected_fragment, (*fragments.begin())->view().data()));
333 }
334
TEST(FragmenterTest,TwoFragmentsOffByOne)335 TEST(FragmenterTest, TwoFragmentsOffByOne) {
336 StaticByteBuffer payload('T', 'e', 's', 't', '!');
337
338 StaticByteBuffer expected_fragment0(
339 // ACL data header
340 0x01,
341 0x00,
342 0x08,
343 0x00,
344
345 // Basic L2CAP header, contains the complete length but a partial payload
346 0x05,
347 0x00,
348 0x01,
349 0x00,
350 'T',
351 'e',
352 's',
353 't');
354
355 StaticByteBuffer expected_fragment1(
356 // ACL data header
357 0x01,
358 0x10,
359 0x01,
360 0x00,
361
362 // Continuing payload
363 '!');
364
365 // Make the fragment limit large enough to fit exactly one B-frame containing
366 // 1 octet less than |payload|. The last octet should be placed in a second
367 // fragment.
368 Fragmenter fragmenter(
369 kTestHandle,
370 static_cast<uint16_t>(payload.size() + sizeof(BasicHeader) - 1));
371 PDU pdu = fragmenter.BuildFrame(
372 kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
373 ASSERT_TRUE(pdu.is_valid());
374 EXPECT_EQ(2u, pdu.fragment_count());
375
376 auto fragments = pdu.ReleaseFragments();
377
378 EXPECT_TRUE(
379 ContainersEqual(expected_fragment0, (*fragments.begin())->view().data()));
380 EXPECT_TRUE(ContainersEqual(expected_fragment1,
381 (*++fragments.begin())->view().data()));
382 }
383
TEST(FragmenterTest,TwoFragmentsExact)384 TEST(FragmenterTest, TwoFragmentsExact) {
385 StaticByteBuffer payload('T', 'e', 's', 't');
386 const bool payload_size_is_even = payload.size() % 2 == 0;
387 PW_DCHECK(payload_size_is_even, "test payload size should be even");
388
389 StaticByteBuffer expected_fragment0(
390 // ACL data header
391 0x01,
392 0x00,
393 0x04,
394 0x00,
395
396 // Basic L2CAP header, contains the complete length but a partial payload
397 0x04,
398 0x00,
399 0x01,
400 0x00);
401
402 StaticByteBuffer expected_fragment1(
403 // ACL data header
404 0x01,
405 0x10,
406 0x04,
407 0x00,
408
409 // Continuing payload
410 'T',
411 'e',
412 's',
413 't');
414
415 // Make the fragment limit large enough to fit exactly half a B-frame
416 // containing |payload|. The frame should be evenly divided across two
417 // fragments.
418 Fragmenter fragmenter(
419 kTestHandle,
420 static_cast<uint16_t>((payload.size() + sizeof(BasicHeader)) / 2));
421 PDU pdu = fragmenter.BuildFrame(
422 kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
423 ASSERT_TRUE(pdu.is_valid());
424 EXPECT_EQ(2u, pdu.fragment_count());
425
426 auto fragments = pdu.ReleaseFragments();
427
428 EXPECT_TRUE(
429 ContainersEqual(expected_fragment0, (*fragments.begin())->view().data()));
430 EXPECT_TRUE(ContainersEqual(expected_fragment1,
431 (*++fragments.begin())->view().data()));
432 }
433
TEST(FragmenterTest,ManyFragmentsOffByOne)434 TEST(FragmenterTest, ManyFragmentsOffByOne) {
435 constexpr size_t kMaxFragmentPayloadSize = 5;
436 constexpr size_t kExpectedFragmentCount = 4;
437 constexpr size_t kFrameSize =
438 (kExpectedFragmentCount - 1) * kMaxFragmentPayloadSize + 1;
439
440 StaticByteBuffer<kFrameSize - sizeof(BasicHeader)> payload;
441 payload.Fill('X');
442
443 StaticByteBuffer expected_fragment0(
444 // ACL data header
445 0x01,
446 0x00,
447 0x05,
448 0x00,
449
450 // Basic L2CAP header contains the complete length but partial payload
451 0x0C,
452 0x00,
453 0x01,
454 0x00,
455 'X');
456
457 StaticByteBuffer expected_fragment1(
458 // ACL data header
459 0x01,
460 0x10,
461 0x05,
462 0x00,
463
464 // Continuing payload
465 'X',
466 'X',
467 'X',
468 'X',
469 'X');
470
471 StaticByteBuffer expected_fragment2(
472 // ACL data header
473 0x01,
474 0x10,
475 0x05,
476 0x00,
477
478 // Continuing payload
479 'X',
480 'X',
481 'X',
482 'X',
483 'X');
484
485 StaticByteBuffer expected_fragment3(
486 // ACL data header
487 0x01,
488 0x10,
489 0x01,
490 0x00,
491
492 // Continuing payload
493 'X');
494
495 Fragmenter fragmenter(kTestHandle, kMaxFragmentPayloadSize);
496 PDU pdu = fragmenter.BuildFrame(
497 kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
498 ASSERT_TRUE(pdu.is_valid());
499 EXPECT_EQ(kExpectedFragmentCount, pdu.fragment_count());
500
501 auto fragments = pdu.ReleaseFragments();
502 auto iter = fragments.begin();
503 EXPECT_TRUE(ContainersEqual(expected_fragment0, (*iter++)->view().data()));
504 EXPECT_TRUE(ContainersEqual(expected_fragment1, (*iter++)->view().data()));
505 EXPECT_TRUE(ContainersEqual(expected_fragment2, (*iter++)->view().data()));
506 EXPECT_TRUE(ContainersEqual(expected_fragment3, (*iter)->view().data()));
507 }
508
TEST(FragmenterTest,MaximalSizedPayload)509 TEST(FragmenterTest, MaximalSizedPayload) {
510 DynamicByteBuffer payload(65535);
511 Fragmenter fragmenter(kTestHandle, 1024);
512 PDU pdu = fragmenter.BuildFrame(
513 kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
514 ASSERT_TRUE(pdu.is_valid());
515 EXPECT_LT(64u, pdu.fragment_count());
516 }
517
TEST(FragmenterTest,FragmentsFrameCheckSequence)518 TEST(FragmenterTest, FragmentsFrameCheckSequence) {
519 constexpr size_t kMaxFragmentPayloadSize = 5;
520 constexpr size_t kExpectedFragmentCount = 3;
521 constexpr size_t kFrameSize =
522 (kExpectedFragmentCount - 1) * kMaxFragmentPayloadSize + 1;
523
524 StaticByteBuffer payload('0', '1', '2', '3', '4');
525 EXPECT_EQ(kFrameSize - sizeof(BasicHeader) - sizeof(FrameCheckSequence),
526 payload.size());
527
528 StaticByteBuffer expected_fragment0(
529 // ACL data header
530 0x01,
531 0x00,
532 kMaxFragmentPayloadSize,
533 0x00,
534
535 // Basic L2CAP header contains the length of PDU (including FCS), partial
536 // payload
537 0x07,
538 0x00,
539 0x01,
540 0x00,
541 '0');
542
543 StaticByteBuffer expected_fragment1(
544 // ACL data header
545 0x01,
546 0x10,
547 kMaxFragmentPayloadSize,
548 0x00,
549
550 // Remaining bytes of payload and first byte of FCS
551 '1',
552 '2',
553 '3',
554 '4',
555 0xcc);
556
557 StaticByteBuffer expected_fragment2(
558 // ACL data header
559 0x01,
560 0x10,
561 0x01,
562 0x00,
563
564 // Last byte of FCS
565 0xe0);
566
567 Fragmenter fragmenter(kTestHandle, kMaxFragmentPayloadSize);
568 PDU pdu = fragmenter.BuildFrame(
569 kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs);
570 ASSERT_TRUE(pdu.is_valid());
571 EXPECT_EQ(kExpectedFragmentCount, pdu.fragment_count());
572
573 auto fragments = pdu.ReleaseFragments();
574 auto iter = fragments.begin();
575 EXPECT_TRUE(ContainersEqual(expected_fragment0, (*iter++)->view().data()));
576 EXPECT_TRUE(ContainersEqual(expected_fragment1, (*iter++)->view().data()));
577 EXPECT_TRUE(ContainersEqual(expected_fragment2, (*iter++)->view().data()));
578 }
579
580 } // namespace
581 } // namespace bt::l2cap
582