blob: 32e97bd1caa6ed881d37a2def3b8504167865707 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "recombiner.h"
#include <gtest/gtest.h>
#include "pdu.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/packet.h"
namespace bt::l2cap {
namespace {
constexpr hci::ConnectionHandle kTestHandle = 0x0001;
constexpr ChannelId kTestChannelId = 0xFFFF;
template <typename... T>
hci::ACLDataPacketPtr PacketFromBytes(T... data) {
auto bytes = CreateStaticByteBuffer(std::forward<T>(data)...);
ZX_DEBUG_ASSERT(bytes.size() >= sizeof(hci::ACLDataHeader));
auto packet = hci::ACLDataPacket::New(bytes.size() - sizeof(hci::ACLDataHeader));
packet->mutable_view()->mutable_data().Write(bytes);
packet->InitializeFromBuffer();
return packet;
}
hci::ACLDataPacketPtr FirstFragment(
std::string payload, std::optional<uint16_t> payload_size = std::nullopt,
hci::ACLPacketBoundaryFlag pbf = hci::ACLPacketBoundaryFlag::kFirstFlushable) {
uint16_t header_payload_size = payload_size.has_value() ? *payload_size : payload.size();
auto packet = hci::ACLDataPacket::New(kTestHandle, pbf, hci::ACLBroadcastFlag::kPointToPoint,
sizeof(BasicHeader) + payload.size());
// L2CAP Header
auto* header = packet->mutable_view()->mutable_payload<BasicHeader>();
header->length = htole16(header_payload_size);
header->channel_id = htole16(kTestChannelId);
// L2CAP payload
packet->mutable_view()->mutable_payload_data().Write(BufferView(payload), sizeof(BasicHeader));
return packet;
}
hci::ACLDataPacketPtr ContinuingFragment(std::string payload) {
auto packet =
hci::ACLDataPacket::New(kTestHandle, hci::ACLPacketBoundaryFlag::kContinuingFragment,
hci::ACLBroadcastFlag::kPointToPoint, payload.size());
packet->mutable_view()->mutable_payload_data().Write(BufferView(payload));
return packet;
}
hci::ACLDataPacketPtr FirstFragmentWithShortL2capHeader() {
return PacketFromBytes(
// ACL data header (handle: 0x0001)
0x01, 0x00, 0x03, 0x00,
// Incomplete basic L2CAP header (one byte short)
0x00, 0x00, 0x03);
}
hci::ACLDataPacketPtr FirstFragmentWithTooLargePayload() {
// Payload length (4 bytes) is larger than reported length (3 bytes).
return FirstFragment("hello", {3});
}
void ValidatePdu(PDU pdu, std::string expected_payload, ChannelId expected_cid = kTestChannelId) {
EXPECT_TRUE(pdu.is_valid());
EXPECT_EQ(expected_payload.size(), pdu.length());
EXPECT_EQ(expected_cid, pdu.channel_id());
// Test that the contents of the PDU match the expected payload.
auto sdu = std::make_unique<DynamicByteBuffer>(pdu.length());
pdu.Copy(sdu.get());
EXPECT_EQ(sdu->AsString(), expected_payload);
// Validate that all individual fragments perfectly sum up to the expected size.
auto fragments = pdu.ReleaseFragments();
size_t sum = 0;
for (const auto& f : fragments) {
sum += f.view().payload_size();
}
EXPECT_EQ(expected_payload.length() + sizeof(BasicHeader), sum);
}
#define VALIDATE_PDU(...) \
do { \
SCOPED_TRACE(""); \
ValidatePdu(__VA_ARGS__); \
} while (false)
// The following test exercises a ZX_DEBUG_ASSERT and thus only works in DEBUG builds.
#ifdef DEBUG
TEST(L2CAP_RecombinerTest, WrongHandle) {
Recombiner recombiner(kTestHandle);
auto packet = PacketFromBytes(0x02, 0x00, // handle: 0x0002
0x00, 0x00 // length: 0
);
ASSERT_DEATH_IF_SUPPORTED(recombiner.ConsumeFragment(std::move(packet)), ".*connection_handle.*");
}
#endif // DEBUG
TEST(L2CAP_RecombinerTest, FirstFragmentTooShort) {
Recombiner recombiner(kTestHandle);
auto result = recombiner.ConsumeFragment(FirstFragmentWithShortL2capHeader());
EXPECT_FALSE(result.pdu);
EXPECT_TRUE(result.frames_dropped);
}
TEST(L2CAP_RecombinerTest, FirstFragmentTooLong) {
Recombiner recombiner(kTestHandle);
auto result = recombiner.ConsumeFragment(FirstFragmentWithTooLargePayload());
EXPECT_FALSE(result.pdu);
EXPECT_TRUE(result.frames_dropped);
}
TEST(L2CAP_RecombinerTest, ContinuingFragmentWhenNotRecombining) {
Recombiner recombiner(kTestHandle);
auto result = recombiner.ConsumeFragment(ContinuingFragment(""));
EXPECT_FALSE(result.pdu);
EXPECT_TRUE(result.frames_dropped);
}
TEST(L2CAP_RecombinerTest, CompleteEmptyFirstFragment) {
Recombiner recombiner(kTestHandle);
auto result = recombiner.ConsumeFragment(FirstFragment(""));
EXPECT_FALSE(result.frames_dropped);
ASSERT_TRUE(result.pdu);
VALIDATE_PDU(std::move(*result.pdu), "");
}
TEST(L2CAP_RecombinerTest, CompleteNonEmptyFirstFragment) {
Recombiner recombiner(kTestHandle);
auto result = recombiner.ConsumeFragment(FirstFragment("Test"));
EXPECT_FALSE(result.frames_dropped);
ASSERT_TRUE(result.pdu);
VALIDATE_PDU(std::move(*result.pdu), "Test");
}
TEST(L2CAP_RecombinerTest, TwoPartRecombination) {
Recombiner recombiner(kTestHandle);
auto result = recombiner.ConsumeFragment(FirstFragment("der", {4}));
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu);
result = recombiner.ConsumeFragment(ContinuingFragment("p"));
EXPECT_FALSE(result.frames_dropped);
ASSERT_TRUE(result.pdu);
VALIDATE_PDU(std::move(*result.pdu), "derp");
}
TEST(L2CAP_RecombinerTest, ThreePartRecombination) {
Recombiner recombiner(kTestHandle);
auto result = recombiner.ConsumeFragment(FirstFragment("d", {4}));
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu);
result = recombiner.ConsumeFragment(ContinuingFragment("er"));
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu);
result = recombiner.ConsumeFragment(ContinuingFragment("p"));
EXPECT_FALSE(result.frames_dropped);
ASSERT_TRUE(result.pdu);
VALIDATE_PDU(std::move(*result.pdu), "derp");
}
TEST(L2CAP_RecombinerTest, RecombinationDroppedDueToCompleteFirstPacket) {
Recombiner recombiner(kTestHandle);
// Write a partial first fragment that initiates a recombination (complete frame length is 2 but
// payload contains 1 byte).
auto result = recombiner.ConsumeFragment(FirstFragment("a", {2}));
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu); // No complete PDU yet.
// Write a new complete first fragment. The previous (still recombining) frame should get dropped
// and the new frame should get delivered. This should report an error for the dropped PDU even
// though it also returns a valid PDU.
result = recombiner.ConsumeFragment(FirstFragment("derp"));
EXPECT_TRUE(result.frames_dropped);
// We should have a complete PDU that doesn't contain the dropped segment ("a").
ASSERT_TRUE(result.pdu);
VALIDATE_PDU(std::move(*result.pdu), "derp");
}
TEST(L2CAP_RecombinerTest, RecombinationDroppedDueToPartialFirstPacket) {
Recombiner recombiner(kTestHandle);
// Write a partial first fragment that initiates a recombination (complete frame length is 2 but
// payload contains 1 byte).
auto result = recombiner.ConsumeFragment(FirstFragment("a", {2}));
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu); // No complete PDU yet.
// Write a new partial first fragment. The previous (still recombining) frame should get dropped
// and the new frame should be buffered for recombination.
result = recombiner.ConsumeFragment(FirstFragment("de", {4}));
EXPECT_TRUE(result.frames_dropped);
EXPECT_FALSE(result.pdu); // No complete PDU yet.
// Complete the new sequence. This should not contain the dropped segment ("a")
result = recombiner.ConsumeFragment(ContinuingFragment("rp"));
EXPECT_FALSE(result.frames_dropped);
ASSERT_TRUE(result.pdu);
VALIDATE_PDU(std::move(*result.pdu), "derp");
}
TEST(L2CAP_RecombinerTest, RecombinationDroppedDueToMalformedFirstPacket) {
Recombiner recombiner(kTestHandle);
// Write a partial first fragment that initiates a recombination (complete frame length is 2 but
// payload contains 1 byte).
auto result = recombiner.ConsumeFragment(FirstFragment("a", {2}));
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu); // No complete PDU yet.
// Write a new partial first fragment. The previous (still recombining) frame should get dropped.
// The new fragment should also get dropped since it's malformed.
result = recombiner.ConsumeFragment(FirstFragmentWithShortL2capHeader());
EXPECT_TRUE(result.frames_dropped);
EXPECT_FALSE(result.pdu); // No complete PDU yet.
// Complete a new sequence. This should not contain the dropped segments.
result = recombiner.ConsumeFragment(FirstFragment("derp"));
EXPECT_FALSE(result.frames_dropped);
ASSERT_TRUE(result.pdu);
VALIDATE_PDU(std::move(*result.pdu), "derp");
}
TEST(L2CAP_RecombinerTest, RecombinationDroppedDueToTooLargeContinuingFrame) {
Recombiner recombiner(kTestHandle);
// Write a partial first fragment that initiates a recombination (complete frame length is 2 but
// payload contains 1 byte).
auto result = recombiner.ConsumeFragment(FirstFragment("a", {2}));
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu); // No complete PDU yet.
// Write a continuing fragment that makes the complete frame larger than 2 bytes.
// The previous (still recombining) frame should get dropped alongside the new fragment.
result = recombiner.ConsumeFragment(ContinuingFragment("bc"));
EXPECT_TRUE(result.frames_dropped);
EXPECT_FALSE(result.pdu); // No complete PDU.
// The next frame should not include the two dropped fragments.
result = recombiner.ConsumeFragment(FirstFragment("derp"));
EXPECT_FALSE(result.frames_dropped);
ASSERT_TRUE(result.pdu);
VALIDATE_PDU(std::move(*result.pdu), "derp");
}
TEST(L2CAP_RecombinerTest, RecombinationDroppedForFrameWithMaxSize) {
constexpr size_t kFrameSize = std::numeric_limits<uint16_t>::max();
constexpr size_t kRxSize = kFrameSize + 1;
Recombiner recombiner(kTestHandle);
const auto result = recombiner.ConsumeFragment(FirstFragment("", {kFrameSize}));
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu);
// Split the rest of the frame into multiple fragments (this is because Fuchsia's bt-hci layer
// currently requires ACL data payloads to be no larger than 1024 bytes).
//
// Loop until the frame is 1 byte larger than expected.
bool completed = false;
for (size_t acc = 0; acc < kRxSize;) {
const size_t remainder = kRxSize - acc;
const size_t size = std::min(hci::kMaxACLPayloadSize, remainder);
acc += size;
const auto result = recombiner.ConsumeFragment(ContinuingFragment(std::string(size, 'd')));
if (acc == kRxSize) {
completed = true;
EXPECT_TRUE(result.frames_dropped) << "last fragment should get dropped!";
EXPECT_FALSE(result.pdu);
} else {
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu);
}
}
EXPECT_TRUE(completed);
}
TEST(L2CAP_RecombinerTest, RecombinationSucceedsForFrameWithMaxSize) {
constexpr size_t kFrameSize = std::numeric_limits<uint16_t>::max();
Recombiner recombiner(kTestHandle);
const auto result = recombiner.ConsumeFragment(FirstFragment("", {kFrameSize}));
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu);
// Split the rest of the frame into multiple fragments (this is because Fuchsia's bt-hci layer
// currently requires ACL data payloads to be no larger than 1024 bytes).
//
// Loop until the frame is 1 byte larger than expected.
bool completed = false;
for (size_t acc = 0; acc < kFrameSize;) {
const size_t remainder = kFrameSize - acc;
const size_t size = std::min(hci::kMaxACLPayloadSize, remainder);
acc += size;
auto result = recombiner.ConsumeFragment(ContinuingFragment(std::string(size, 'd')));
if (acc == kFrameSize) {
completed = true;
EXPECT_FALSE(result.frames_dropped) << "last fragment should not cause a drop!";
ASSERT_TRUE(result.pdu) << "last fragment should result in PDU!";
VALIDATE_PDU(std::move(*result.pdu), std::string(kFrameSize, 'd'));
} else {
EXPECT_FALSE(result.frames_dropped);
EXPECT_FALSE(result.pdu);
}
}
EXPECT_TRUE(completed);
}
} // namespace
} // namespace bt::l2cap