blob: e82a8d6d13fb2798dd5d6def92eabcb546630033 [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 "pdu.h"
#include "fragmenter.h"
#include "recombiner.h"
#include "gtest/gtest.h"
#include "garnet/drivers/bluetooth/lib/common/test_helpers.h"
#include "garnet/drivers/bluetooth/lib/hci/hci.h"
#include "garnet/drivers/bluetooth/lib/hci/packet.h"
namespace btlib {
namespace l2cap {
namespace {
using common::CreateStaticByteBuffer;
constexpr hci::ConnectionHandle kConnectionHandle = 0x0001;
constexpr ChannelId kChannelId = 0x0001;
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;
}
std::unique_ptr<PDU> PduFromByteBuffer(const common::ByteBuffer& buf,
size_t first_fragment_payload_size) {
return std::make_unique<PDU>(
Fragmenter(kConnectionHandle,
sizeof(BasicHeader) + first_fragment_payload_size)
.BuildBasicFrame(kChannelId, buf));
}
TEST(L2CAP_PduTest, Move) {
Recombiner recombiner;
// clang-format off
auto packet0 = PacketFromBytes(
// ACL data header
0x01, 0x00, 0x08, 0x00,
// Basic L2CAP header
0x04, 0x00, 0xFF, 0xFF, 'T', 'e', 's', 't'
);
// clang-format on
EXPECT_TRUE(recombiner.AddFragment(std::move(packet0)));
PDU pdu;
EXPECT_TRUE(recombiner.Release(&pdu));
EXPECT_TRUE(pdu.is_valid());
EXPECT_EQ(1u, pdu.fragment_count());
common::StaticByteBuffer<4> pdu_data;
// Read the entire PDU.
EXPECT_EQ(4u, pdu.Copy(&pdu_data));
EXPECT_EQ("Test", pdu_data.AsString());
PDU move_cted(std::move(pdu));
EXPECT_FALSE(pdu.is_valid());
EXPECT_EQ(0u, pdu.fragment_count());
EXPECT_TRUE(move_cted.is_valid());
EXPECT_EQ(1u, move_cted.fragment_count());
pdu_data.SetToZeros();
EXPECT_EQ(4u, move_cted.Copy(&pdu_data));
EXPECT_EQ("Test", pdu_data.AsString());
PDU move_assigned = std::move(move_cted);
EXPECT_FALSE(move_cted.is_valid());
EXPECT_EQ(0u, move_cted.fragment_count());
EXPECT_TRUE(move_assigned.is_valid());
EXPECT_EQ(1u, move_assigned.fragment_count());
pdu_data.SetToZeros();
EXPECT_EQ(4u, move_assigned.Copy(&pdu_data));
EXPECT_EQ("Test", pdu_data.AsString());
}
TEST(L2CAP_PduTest, ReleaseFragments) {
Recombiner recombiner;
// clang-format off
auto packet0 = PacketFromBytes(
// ACL data header
0x01, 0x00, 0x08, 0x00,
// Basic L2CAP header
0x04, 0x00, 0xFF, 0xFF, 'T', 'e', 's', 't'
);
// clang-format on
EXPECT_TRUE(recombiner.AddFragment(std::move(packet0)));
PDU pdu;
EXPECT_TRUE(recombiner.Release(&pdu));
EXPECT_TRUE(pdu.is_valid());
EXPECT_EQ(1u, pdu.fragment_count());
auto fragments = pdu.ReleaseFragments();
EXPECT_FALSE(pdu.is_valid());
ASSERT_FALSE(fragments.is_empty());
EXPECT_EQ(0u, pdu.fragment_count());
// Directly count the elements in |fragments| to make sure the count is
// correct.
size_t count = 0;
for (__UNUSED const auto& f : fragments)
count++;
EXPECT_EQ(1u, count);
// Check that the fragment we got out is identical to the one we fed in.
EXPECT_TRUE(
common::ContainersEqual(common::CreateStaticByteBuffer(
// ACL data header
0x01, 0x00, 0x08, 0x00,
// Basic L2CAP header
0x04, 0x00, 0xFF, 0xFF, 'T', 'e', 's', 't'),
fragments.begin()->view().data()));
}
TEST(L2CAP_PduTest, ReadSingleFragment) {
Recombiner recombiner;
// clang-format off
auto packet0 = PacketFromBytes(
// ACL data header
0x01, 0x00, 0x08, 0x00,
// Basic L2CAP header
0x04, 0x00, 0xFF, 0xFF, 'T', 'e', 's', 't'
);
// clang-format on
EXPECT_TRUE(recombiner.AddFragment(std::move(packet0)));
PDU pdu;
EXPECT_TRUE(recombiner.Release(&pdu));
EXPECT_TRUE(pdu.is_valid());
common::StaticByteBuffer<4> pdu_data;
// Read the entire PDU.
EXPECT_EQ(4u, pdu.Copy(&pdu_data));
EXPECT_EQ("Test", pdu_data.AsString());
// Read 1 byte at offset 1.
pdu_data.Fill('X');
EXPECT_EQ(1u, pdu.Copy(&pdu_data, 1, 1));
EXPECT_EQ("eXXX", pdu_data.AsString());
// Read bytes starting at offset 2.
pdu_data.Fill('X');
EXPECT_EQ(2u, pdu.Copy(&pdu_data, 2));
EXPECT_EQ("stXX", pdu_data.AsString());
// Read bytes starting at offset 3.
pdu_data.Fill('X');
EXPECT_EQ(1u, pdu.Copy(&pdu_data, 3));
EXPECT_EQ("tXXX", pdu_data.AsString());
}
TEST(L2CAP_PduTest, ReadMultipleFragments) {
Recombiner recombiner;
// clang-format off
// Partial initial fragment
auto packet0 = PacketFromBytes(
// ACL data header (PBF: initial fragment)
0x01, 0x00, 0x0A, 0x00,
// Basic L2CAP header
0x0F, 0x00, 0xFF, 0xFF, 'T', 'h', 'i', 's', ' ', 'i'
);
// Continuation fragment
auto packet1 = PacketFromBytes(
// ACL data header (PBF: continuing fragment)
0x01, 0x10, 0x06, 0x00,
// L2CAP PDU fragment
's', ' ', 'a', ' ', 't', 'e'
);
// Continuation fragment
auto packet2 = PacketFromBytes(
// ACL data header (PBF: continuing fragment)
0x01, 0x10, 0x02, 0x00,
// L2CAP PDU fragment
's', 't'
);
// Continuation fragment
auto packet3 = PacketFromBytes(
// ACL data header (PBF: continuing fragment)
0x01, 0x10, 0x01, 0x00,
// L2CAP PDU fragment
'!'
);
EXPECT_TRUE(recombiner.AddFragment(std::move(packet0)));
EXPECT_TRUE(recombiner.AddFragment(std::move(packet1)));
EXPECT_TRUE(recombiner.AddFragment(std::move(packet2)));
EXPECT_TRUE(recombiner.AddFragment(std::move(packet3)));
PDU pdu;
EXPECT_TRUE(recombiner.Release(&pdu));
EXPECT_TRUE(pdu.is_valid());
EXPECT_EQ(4u, pdu.fragment_count());
common::StaticByteBuffer<15> pdu_data;
// Read the entire PDU.
EXPECT_EQ(15u, pdu.Copy(&pdu_data));
EXPECT_EQ("This is a test!", pdu_data.AsString());
// Read 1 byte at offset 1.
pdu_data.Fill('X');
EXPECT_EQ(1u, pdu.Copy(&pdu_data, 1, 1));
EXPECT_EQ("hXXXXXXXXXXXXXX", pdu_data.AsString());
// Read bytes starting at offset 2.
pdu_data.Fill('X');
EXPECT_EQ(13u, pdu.Copy(&pdu_data, 2));
EXPECT_EQ("is is a test!XX", pdu_data.AsString());
// Read bytes starting at the last octet of the first fragment.
pdu_data.Fill('X');
EXPECT_EQ(10u, pdu.Copy(&pdu_data, 5));
EXPECT_EQ("is a test!XXXXX", pdu_data.AsString());
// Read bytes starting at the first octet of the second fragment.
pdu_data.Fill('X');
EXPECT_EQ(9u, pdu.Copy(&pdu_data, 6));
EXPECT_EQ("s a test!XXXXXX", pdu_data.AsString());
// Read the very last octet from the last fragment.
pdu_data.Fill('X');
EXPECT_EQ(1u, pdu.Copy(&pdu_data, 14));
EXPECT_EQ("!XXXXXXXXXXXXXX", pdu_data.AsString());
// Partial read across multiple fragments
pdu_data.Fill('X');
EXPECT_EQ(8u, pdu.Copy(&pdu_data, 5, 8));
EXPECT_EQ("is a tesXXXXXXX", pdu_data.AsString());
}
TEST(L2CAP_PduTest, ViewFirstFragment) {
Recombiner recombiner;
// clang-format off
// Partial initial fragment
auto packet0 = PacketFromBytes(
// ACL data header (PBF: initial fragment)
0x01, 0x00, 0x06, 0x00,
// Basic L2CAP header
0x04, 0x00, 0xFF, 0xFF, 'T', 'e'
);
// Continuation fragment
auto packet1 = PacketFromBytes(
// ACL data header (PBF: continuing fragment)
0x01, 0x10, 0x02, 0x00,
// L2CAP PDU fragment
's', 't'
);
// clang-format on
EXPECT_TRUE(recombiner.AddFragment(std::move(packet0)));
EXPECT_TRUE(recombiner.AddFragment(std::move(packet1)));
PDU pdu;
EXPECT_TRUE(recombiner.Release(&pdu));
ASSERT_TRUE(pdu.is_valid());
auto view = pdu.ViewFirstFragment(1);
EXPECT_EQ("T", view.AsString());
// Passing a large number for size. |view| should not exceed the size of the
// first fragment.
view = pdu.ViewFirstFragment(100);
EXPECT_EQ("Te", view.AsString());
}
TEST(L2CAP_PduTest, Reader) {
Recombiner recombiner;
// clang-format off
// Partial initial fragment
auto packet0 = PacketFromBytes(
// ACL data header (PBF: initial fragment)
0x01, 0x00, 0x09, 0x00,
// Basic L2CAP header
0x0F, 0x00, 0xFF, 0xFF, 'R', 'e', 'a', 'd', 'i'
);
// Continuation fragment
auto packet1 = PacketFromBytes(
// ACL data header (PBF: continuing fragment)
0x01, 0x10, 0x06, 0x00,
// L2CAP PDU fragment
'n', 'g', ' ', 'p', 'a', 'c'
);
// Continuation fragment
auto packet2 = PacketFromBytes(
// ACL data header (PBF: continuing fragment)
0x01, 0x10, 0x04, 0x00,
// L2CAP PDU fragment
'k', 'e', 't', 's'
);
// clang-format on
EXPECT_TRUE(recombiner.AddFragment(std::move(packet0)));
EXPECT_TRUE(recombiner.AddFragment(std::move(packet1)));
EXPECT_TRUE(recombiner.AddFragment(std::move(packet2)));
PDU pdu;
EXPECT_TRUE(recombiner.Release(&pdu));
ASSERT_TRUE(pdu.is_valid());
PDU::Reader reader(&pdu);
EXPECT_FALSE(reader.ReadNext(16, [](const auto&) {}));
EXPECT_FALSE(reader.ReadNext(0, [](const auto&) {}));
// Read the entire PDU.
EXPECT_TRUE(reader.ReadNext(15, [](const auto& data) {
EXPECT_EQ("Reading packets", data.AsString());
}));
reader = PDU::Reader(&pdu);
// Read 4 bytes (no-copy).
EXPECT_TRUE(reader.ReadNext(
4, [](const auto& data) { EXPECT_EQ("Read", data.AsString()); }));
// Read across fragment boundaries which will make a copy.
EXPECT_TRUE(reader.ReadNext(
4, [](const auto& data) { EXPECT_EQ("ing ", data.AsString()); }));
// Read until the end of the current fragment (no-copy). The iterator should
// point to the next fragment.
EXPECT_TRUE(reader.ReadNext(
3, [](const auto& data) { EXPECT_EQ("pac", data.AsString()); }));
// Read all of the last fragment (no-copy).
EXPECT_TRUE(reader.ReadNext(
4, [](const auto& data) { EXPECT_EQ("kets", data.AsString()); }));
}
TEST(L2CAP_PduTest,
DestroyingPduInReaderCallbackWithUnfragmentedPduDoesNotCrash) {
auto payload = CreateStaticByteBuffer('T', 'e', 's', 't');
std::unique_ptr<PDU> pdu = PduFromByteBuffer(payload, payload.size());
ASSERT_TRUE(pdu->is_valid());
ASSERT_EQ(1u, pdu->fragment_count());
size_t call_count = 0;
PDU::Reader(pdu.get()).ReadNext(payload.size(), [&](const auto& buf) {
++call_count;
EXPECT_EQ(payload.size(), buf.size());
pdu.reset();
});
EXPECT_EQ(1u, call_count);
}
TEST(L2CAP_PduTest,
DestroyingPduInReaderCallbackWithFullReadOfFragmentedPduDoesNotCrash) {
const auto kFirstFragmentPayloadSize = 1u;
auto payload = common::CreateStaticByteBuffer('a', 'b');
std::unique_ptr<PDU> pdu =
PduFromByteBuffer(payload, kFirstFragmentPayloadSize);
ASSERT_TRUE(pdu->is_valid());
ASSERT_EQ(2u, pdu->fragment_count());
size_t call_count = 0;
PDU::Reader(pdu.get()).ReadNext(payload.size(), [&](const auto& buf) {
++call_count;
EXPECT_EQ(payload.size(), buf.size());
pdu.reset();
});
EXPECT_EQ(1u, call_count);
}
TEST(L2CAP_PduTest,
DestroyingPduInReaderCallbackWithPartialReadOfFragmentedPduDoesNotCrash) {
const auto kFirstFragmentPayloadSize = 1u;
auto payload = CreateStaticByteBuffer('a', 'b', 'c');
std::unique_ptr<PDU> pdu =
PduFromByteBuffer(payload, kFirstFragmentPayloadSize);
ASSERT_TRUE(pdu->is_valid());
ASSERT_EQ(2u, pdu->fragment_count()); // Second fragment contains 'b', 'c'.
// Read across fragments, but without consuming the whole PDU.
size_t call_count = 0;
PDU::Reader(pdu.get()).ReadNext(kFirstFragmentPayloadSize + 1,
[&](const auto& buf) {
++call_count;
EXPECT_EQ(payload.size() - 1, buf.size());
pdu.reset();
});
EXPECT_EQ(1u, call_count);
}
} // namespace
} // namespace l2cap
} // namespace btlib