xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/l2cap/recombiner_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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/recombiner.h"
16 
17 #include <pw_bytes/endian.h>
18 
19 #include <cstdint>
20 
21 #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
22 #include "pw_bluetooth_sapphire/internal/host/l2cap/pdu.h"
23 #include "pw_bluetooth_sapphire/internal/host/transport/packet.h"
24 #include "pw_unit_test/framework.h"
25 
26 namespace bt::l2cap {
27 namespace {
28 
29 constexpr hci_spec::ConnectionHandle kTestHandle = 0x0001;
30 constexpr ChannelId kTestChannelId = 0xFFFF;
31 
32 template <typename... T>
PacketFromBytes(T...data)33 hci::ACLDataPacketPtr PacketFromBytes(T... data) {
34   StaticByteBuffer bytes(std::forward<T>(data)...);
35   PW_DCHECK(bytes.size() >= sizeof(hci_spec::ACLDataHeader));
36 
37   auto packet = hci::ACLDataPacket::New(
38       static_cast<uint16_t>(bytes.size() - sizeof(hci_spec::ACLDataHeader)));
39   packet->mutable_view()->mutable_data().Write(bytes);
40   packet->InitializeFromBuffer();
41 
42   return packet;
43 }
44 
FirstFragment(std::string payload,std::optional<uint16_t> payload_size=std::nullopt,hci_spec::ACLPacketBoundaryFlag pbf=hci_spec::ACLPacketBoundaryFlag::kFirstFlushable)45 hci::ACLDataPacketPtr FirstFragment(
46     std::string payload,
47     std::optional<uint16_t> payload_size = std::nullopt,
48     hci_spec::ACLPacketBoundaryFlag pbf =
49         hci_spec::ACLPacketBoundaryFlag::kFirstFlushable) {
50   uint16_t header_payload_size = payload_size.has_value()
51                                      ? *payload_size
52                                      : static_cast<uint16_t>(payload.size());
53   auto packet = hci::ACLDataPacket::New(
54       kTestHandle,
55       pbf,
56       hci_spec::ACLBroadcastFlag::kPointToPoint,
57       static_cast<uint16_t>(sizeof(BasicHeader) + payload.size()));
58 
59   // L2CAP Header
60   auto* header = packet->mutable_view()->mutable_payload<BasicHeader>();
61   header->length =
62       pw::bytes::ConvertOrderTo(cpp20::endian::little, header_payload_size);
63   header->channel_id =
64       pw::bytes::ConvertOrderTo(cpp20::endian::little, kTestChannelId);
65 
66   // L2CAP payload
67   packet->mutable_view()->mutable_payload_data().Write(BufferView(payload),
68                                                        sizeof(BasicHeader));
69   return packet;
70 }
71 
ContinuingFragment(std::string payload)72 hci::ACLDataPacketPtr ContinuingFragment(std::string payload) {
73   auto packet = hci::ACLDataPacket::New(
74       kTestHandle,
75       hci_spec::ACLPacketBoundaryFlag::kContinuingFragment,
76       hci_spec::ACLBroadcastFlag::kPointToPoint,
77       static_cast<uint16_t>(payload.size()));
78   packet->mutable_view()->mutable_payload_data().Write(BufferView(payload));
79   return packet;
80 }
81 
FirstFragmentWithShortL2capHeader()82 hci::ACLDataPacketPtr FirstFragmentWithShortL2capHeader() {
83   return PacketFromBytes(
84       // ACL data header (handle: 0x0001)
85       0x01,
86       0x00,
87       0x03,
88       0x00,
89 
90       // Incomplete basic L2CAP header (one byte short)
91       0x00,
92       0x00,
93       0x03);
94 }
95 
FirstFragmentWithTooLargePayload()96 hci::ACLDataPacketPtr FirstFragmentWithTooLargePayload() {
97   // Payload length (4 bytes) is larger than reported length (3 bytes).
98   return FirstFragment("hello", {3});
99 }
100 
ValidatePdu(PDU pdu,std::string expected_payload,ChannelId expected_cid=kTestChannelId)101 void ValidatePdu(PDU pdu,
102                  std::string expected_payload,
103                  ChannelId expected_cid = kTestChannelId) {
104   EXPECT_TRUE(pdu.is_valid());
105   EXPECT_EQ(expected_payload.size(), pdu.length());
106   EXPECT_EQ(expected_cid, pdu.channel_id());
107 
108   // Test that the contents of the PDU match the expected payload.
109   auto sdu = std::make_unique<DynamicByteBuffer>(pdu.length());
110   pdu.Copy(sdu.get());
111   EXPECT_EQ(sdu->AsString(), expected_payload);
112 
113   // Validate that all individual fragments perfectly sum up to the expected
114   // size.
115   auto fragments = pdu.ReleaseFragments();
116   size_t sum = 0;
117   for (const auto& f : fragments) {
118     sum += f->view().payload_size();
119   }
120   EXPECT_EQ(expected_payload.length() + sizeof(BasicHeader), sum);
121 }
122 
123 #define VALIDATE_PDU(...)     \
124   do {                        \
125     SCOPED_TRACE("");         \
126     ValidatePdu(__VA_ARGS__); \
127   } while (false)
128 
129 // The following test exercises a BT_DEBUG_ASSERT and thus only works in DEBUG
130 // builds.
131 #ifdef DEBUG
TEST(RecombinerTest,WrongHandle)132 TEST(RecombinerTest, WrongHandle) {
133   Recombiner recombiner(kTestHandle);
134   auto packet = PacketFromBytes(0x02,
135                                 0x00,  // handle: 0x0002
136                                 0x00,
137                                 0x00  // length: 0
138   );
139   ASSERT_DEATH_IF_SUPPORTED(recombiner.ConsumeFragment(std::move(packet)),
140                             ".*connection_handle.*");
141 }
142 #endif  // DEBUG
143 
TEST(RecombinerTest,FirstFragmentTooShort)144 TEST(RecombinerTest, FirstFragmentTooShort) {
145   Recombiner recombiner(kTestHandle);
146   auto result = recombiner.ConsumeFragment(FirstFragmentWithShortL2capHeader());
147   EXPECT_FALSE(result.pdu);
148   EXPECT_TRUE(result.frames_dropped);
149 }
150 
TEST(RecombinerTest,FirstFragmentTooLong)151 TEST(RecombinerTest, FirstFragmentTooLong) {
152   Recombiner recombiner(kTestHandle);
153   auto result = recombiner.ConsumeFragment(FirstFragmentWithTooLargePayload());
154   EXPECT_FALSE(result.pdu);
155   EXPECT_TRUE(result.frames_dropped);
156 }
157 
TEST(RecombinerTest,ContinuingFragmentWhenNotRecombining)158 TEST(RecombinerTest, ContinuingFragmentWhenNotRecombining) {
159   Recombiner recombiner(kTestHandle);
160   auto result = recombiner.ConsumeFragment(ContinuingFragment(""));
161   EXPECT_FALSE(result.pdu);
162   EXPECT_TRUE(result.frames_dropped);
163 }
164 
TEST(RecombinerTest,CompleteEmptyFirstFragment)165 TEST(RecombinerTest, CompleteEmptyFirstFragment) {
166   Recombiner recombiner(kTestHandle);
167   auto result = recombiner.ConsumeFragment(FirstFragment(""));
168   EXPECT_FALSE(result.frames_dropped);
169   ASSERT_TRUE(result.pdu);
170   VALIDATE_PDU(std::move(*result.pdu), "");
171 }
172 
TEST(RecombinerTest,CompleteNonEmptyFirstFragment)173 TEST(RecombinerTest, CompleteNonEmptyFirstFragment) {
174   Recombiner recombiner(kTestHandle);
175   auto result = recombiner.ConsumeFragment(FirstFragment("Test"));
176   EXPECT_FALSE(result.frames_dropped);
177   ASSERT_TRUE(result.pdu);
178   VALIDATE_PDU(std::move(*result.pdu), "Test");
179 }
180 
TEST(RecombinerTest,TwoPartRecombination)181 TEST(RecombinerTest, TwoPartRecombination) {
182   Recombiner recombiner(kTestHandle);
183   auto result = recombiner.ConsumeFragment(FirstFragment("der", {4}));
184   EXPECT_FALSE(result.frames_dropped);
185   EXPECT_FALSE(result.pdu);
186 
187   result = recombiner.ConsumeFragment(ContinuingFragment("p"));
188   EXPECT_FALSE(result.frames_dropped);
189   ASSERT_TRUE(result.pdu);
190   VALIDATE_PDU(std::move(*result.pdu), "derp");
191 }
192 
TEST(RecombinerTest,ThreePartRecombination)193 TEST(RecombinerTest, ThreePartRecombination) {
194   Recombiner recombiner(kTestHandle);
195   auto result = recombiner.ConsumeFragment(FirstFragment("d", {4}));
196   EXPECT_FALSE(result.frames_dropped);
197   EXPECT_FALSE(result.pdu);
198 
199   result = recombiner.ConsumeFragment(ContinuingFragment("er"));
200   EXPECT_FALSE(result.frames_dropped);
201   EXPECT_FALSE(result.pdu);
202 
203   result = recombiner.ConsumeFragment(ContinuingFragment("p"));
204   EXPECT_FALSE(result.frames_dropped);
205   ASSERT_TRUE(result.pdu);
206   VALIDATE_PDU(std::move(*result.pdu), "derp");
207 }
208 
TEST(RecombinerTest,RecombinationDroppedDueToCompleteFirstPacket)209 TEST(RecombinerTest, RecombinationDroppedDueToCompleteFirstPacket) {
210   Recombiner recombiner(kTestHandle);
211 
212   // Write a partial first fragment that initiates a recombination (complete
213   // frame length is 2 but payload contains 1 byte).
214   auto result = recombiner.ConsumeFragment(FirstFragment("a", {2}));
215   EXPECT_FALSE(result.frames_dropped);
216   EXPECT_FALSE(result.pdu);  // No complete PDU yet.
217 
218   // Write a new complete first fragment. The previous (still recombining) frame
219   // should get dropped and the new frame should get delivered. This should
220   // report an error for the dropped PDU even though it also returns a valid
221   // PDU.
222   result = recombiner.ConsumeFragment(FirstFragment("derp"));
223   EXPECT_TRUE(result.frames_dropped);
224 
225   // We should have a complete PDU that doesn't contain the dropped segment
226   // ("a").
227   ASSERT_TRUE(result.pdu);
228   VALIDATE_PDU(std::move(*result.pdu), "derp");
229 }
230 
TEST(RecombinerTest,RecombinationDroppedDueToPartialFirstPacket)231 TEST(RecombinerTest, RecombinationDroppedDueToPartialFirstPacket) {
232   Recombiner recombiner(kTestHandle);
233 
234   // Write a partial first fragment that initiates a recombination (complete
235   // frame length is 2 but payload contains 1 byte).
236   auto result = recombiner.ConsumeFragment(FirstFragment("a", {2}));
237   EXPECT_FALSE(result.frames_dropped);
238   EXPECT_FALSE(result.pdu);  // No complete PDU yet.
239 
240   // Write a new partial first fragment. The previous (still recombining) frame
241   // should get dropped and the new frame should be buffered for recombination.
242   result = recombiner.ConsumeFragment(FirstFragment("de", {4}));
243   EXPECT_TRUE(result.frames_dropped);
244   EXPECT_FALSE(result.pdu);  // No complete PDU yet.
245 
246   // Complete the new sequence. This should not contain the dropped segment
247   // ("a")
248   result = recombiner.ConsumeFragment(ContinuingFragment("rp"));
249   EXPECT_FALSE(result.frames_dropped);
250   ASSERT_TRUE(result.pdu);
251   VALIDATE_PDU(std::move(*result.pdu), "derp");
252 }
253 
TEST(RecombinerTest,RecombinationDroppedDueToMalformedFirstPacket)254 TEST(RecombinerTest, RecombinationDroppedDueToMalformedFirstPacket) {
255   Recombiner recombiner(kTestHandle);
256 
257   // Write a partial first fragment that initiates a recombination (complete
258   // frame length is 2 but payload contains 1 byte).
259   auto result = recombiner.ConsumeFragment(FirstFragment("a", {2}));
260   EXPECT_FALSE(result.frames_dropped);
261   EXPECT_FALSE(result.pdu);  // No complete PDU yet.
262 
263   // Write a new partial first fragment. The previous (still recombining) frame
264   // should get dropped. The new fragment should also get dropped since it's
265   // malformed.
266   result = recombiner.ConsumeFragment(FirstFragmentWithShortL2capHeader());
267   EXPECT_TRUE(result.frames_dropped);
268   EXPECT_FALSE(result.pdu);  // No complete PDU yet.
269 
270   // Complete a new sequence. This should not contain the dropped segments.
271   result = recombiner.ConsumeFragment(FirstFragment("derp"));
272   EXPECT_FALSE(result.frames_dropped);
273   ASSERT_TRUE(result.pdu);
274   VALIDATE_PDU(std::move(*result.pdu), "derp");
275 }
276 
TEST(RecombinerTest,RecombinationDroppedDueToTooLargeContinuingFrame)277 TEST(RecombinerTest, RecombinationDroppedDueToTooLargeContinuingFrame) {
278   Recombiner recombiner(kTestHandle);
279 
280   // Write a partial first fragment that initiates a recombination (complete
281   // frame length is 2 but payload contains 1 byte).
282   auto result = recombiner.ConsumeFragment(FirstFragment("a", {2}));
283   EXPECT_FALSE(result.frames_dropped);
284   EXPECT_FALSE(result.pdu);  // No complete PDU yet.
285 
286   // Write a continuing fragment that makes the complete frame larger than 2
287   // bytes. The previous (still recombining) frame should get dropped alongside
288   // the new fragment.
289   result = recombiner.ConsumeFragment(ContinuingFragment("bc"));
290   EXPECT_TRUE(result.frames_dropped);
291   EXPECT_FALSE(result.pdu);  // No complete PDU.
292 
293   // The next frame should not include the two dropped fragments.
294   result = recombiner.ConsumeFragment(FirstFragment("derp"));
295   EXPECT_FALSE(result.frames_dropped);
296   ASSERT_TRUE(result.pdu);
297   VALIDATE_PDU(std::move(*result.pdu), "derp");
298 }
299 
TEST(RecombinerTest,RecombinationDroppedForFrameWithMaxSize)300 TEST(RecombinerTest, RecombinationDroppedForFrameWithMaxSize) {
301   constexpr size_t kFrameSize = std::numeric_limits<uint16_t>::max();
302   constexpr size_t kRxSize = kFrameSize + 1;
303 
304   Recombiner recombiner(kTestHandle);
305   {
306     const auto result =
307         recombiner.ConsumeFragment(FirstFragment("", {kFrameSize}));
308     EXPECT_FALSE(result.frames_dropped);
309     EXPECT_FALSE(result.pdu);
310   }
311 
312   // Split the rest of the frame into multiple fragments (this is because
313   // Fuchsia's bt-hci layer currently requires ACL data payloads to be no larger
314   // than 1024 bytes).
315   //
316   // Loop until the frame is 1 byte larger than expected.
317   bool completed = false;
318   for (size_t acc = 0; acc < kRxSize;) {
319     const size_t remainder = kRxSize - acc;
320     const size_t size = std::min(hci_spec::kMaxACLPayloadSize, remainder);
321     acc += size;
322 
323     const auto result =
324         recombiner.ConsumeFragment(ContinuingFragment(std::string(size, 'd')));
325     if (acc == kRxSize) {
326       completed = true;
327       EXPECT_TRUE(result.frames_dropped) << "last fragment should get dropped!";
328       EXPECT_FALSE(result.pdu);
329     } else {
330       EXPECT_FALSE(result.frames_dropped);
331       EXPECT_FALSE(result.pdu);
332     }
333   }
334   EXPECT_TRUE(completed);
335 }
336 
TEST(RecombinerTest,RecombinationSucceedsForFrameWithMaxSize)337 TEST(RecombinerTest, RecombinationSucceedsForFrameWithMaxSize) {
338   constexpr size_t kFrameSize = std::numeric_limits<uint16_t>::max();
339 
340   Recombiner recombiner(kTestHandle);
341   {
342     const auto result =
343         recombiner.ConsumeFragment(FirstFragment("", {kFrameSize}));
344     EXPECT_FALSE(result.frames_dropped);
345     EXPECT_FALSE(result.pdu);
346   }
347 
348   // Split the rest of the frame into multiple fragments (this is because
349   // Fuchsia's bt-hci layer currently requires ACL data payloads to be no larger
350   // than 1024 bytes).
351   //
352   // Loop until the frame is 1 byte larger than expected.
353   bool completed = false;
354   for (size_t acc = 0; acc < kFrameSize;) {
355     const size_t remainder = kFrameSize - acc;
356     const size_t size = std::min(hci_spec::kMaxACLPayloadSize, remainder);
357     acc += size;
358 
359     auto result =
360         recombiner.ConsumeFragment(ContinuingFragment(std::string(size, 'd')));
361     if (acc == kFrameSize) {
362       completed = true;
363       EXPECT_FALSE(result.frames_dropped)
364           << "last fragment should not cause a drop!";
365       ASSERT_TRUE(result.pdu) << "last fragment should result in PDU!";
366       VALIDATE_PDU(std::move(*result.pdu), std::string(kFrameSize, 'd'));
367     } else {
368       EXPECT_FALSE(result.frames_dropped);
369       EXPECT_FALSE(result.pdu);
370     }
371   }
372   EXPECT_TRUE(completed);
373 }
374 
375 }  // namespace
376 }  // namespace bt::l2cap
377