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