blob: 22577fc68db81c7598c848a497b792b4afff7f99 [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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fragmenter.h"
#include <gtest/gtest.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/pdu.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
namespace bt::l2cap {
namespace {
constexpr hci_spec::ConnectionHandle kTestHandle = 0x0001;
constexpr ChannelId kTestChannelId = 0x0001;
TEST(FragmenterTest, OutboundFrameEmptyPayload) {
StaticByteBuffer kExpectedFrame(
// Basic L2CAP header (0-length Information Payload)
0x00,
0x00,
0x01,
0x00);
OutboundFrame frame(
kTestChannelId, BufferView(), FrameCheckSequenceOption::kNoFcs);
EXPECT_EQ(kExpectedFrame.size(), frame.size());
decltype(kExpectedFrame) out_buffer;
frame.WriteToFragment(out_buffer.mutable_view(), 0);
EXPECT_TRUE(ContainersEqual(kExpectedFrame, out_buffer));
}
TEST(FragmenterTest, OutboundFrameEmptyPayloadWithFcs) {
StaticByteBuffer kExpectedFrame(
// Basic L2CAP header (2-byte Information Payload)
0x02,
0x00,
0x01,
0x00,
// FCS over preceding header (no other informational data)
0x00,
0x28);
OutboundFrame frame(
kTestChannelId, BufferView(), FrameCheckSequenceOption::kIncludeFcs);
EXPECT_EQ(kExpectedFrame.size(), frame.size());
decltype(kExpectedFrame) out_buffer;
frame.WriteToFragment(out_buffer.mutable_view(), 0);
EXPECT_TRUE(ContainersEqual(kExpectedFrame, out_buffer));
// Reset
out_buffer.Fill(0xaa);
// Copy just FCS footer
frame.WriteToFragment(out_buffer.mutable_view(4), 4);
EXPECT_EQ(0x00, out_buffer[4]);
EXPECT_EQ(0x28, out_buffer[5]);
}
TEST(FragmenterTest, OutboundFrameExactFit) {
StaticByteBuffer payload('T', 'e', 's', 't');
StaticByteBuffer kExpectedFrame(
// Basic L2CAP header
0x04,
0x00,
0x01,
0x00,
// Payload
'T',
'e',
's',
't');
OutboundFrame frame(
kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
EXPECT_EQ(kExpectedFrame.size(), frame.size());
decltype(kExpectedFrame) out_buffer;
frame.WriteToFragment(out_buffer.mutable_view(), 0);
EXPECT_TRUE(ContainersEqual(kExpectedFrame, out_buffer));
}
TEST(FragmenterTest, OutboundFrameExactFitWithFcs) {
// Test data from v5.0, Vol 3, Part A, Section 3.3.5, Example 1
StaticByteBuffer payload(
0x02, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09);
StaticByteBuffer kExpectedFrame(
// Basic L2CAP header
0x0e,
0x00,
0x40,
0x00,
// Payload
0x02,
0x00,
0x00,
0x01,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
0x09,
// FCS
0x38,
0x61);
OutboundFrame frame(
ChannelId(0x0040), payload, FrameCheckSequenceOption::kIncludeFcs);
EXPECT_EQ(kExpectedFrame.size(), frame.size());
decltype(kExpectedFrame) out_buffer;
frame.WriteToFragment(out_buffer.mutable_view(), 0);
EXPECT_TRUE(ContainersEqual(kExpectedFrame, out_buffer));
}
TEST(FragmenterTest, OutboundFrameOffsetInHeader) {
StaticByteBuffer payload('T', 'e', 's', 't');
StaticByteBuffer kExpectedFrameChunk(
// Basic L2CAP header (minus first byte)
0x00,
0x01,
0x00,
// Payload (first byte only, limited by size of output buffer)
'T',
'e',
's',
't',
// FCS
0xa4,
0xc3);
OutboundFrame frame(
kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs);
EXPECT_EQ(sizeof(BasicHeader) + payload.size() + sizeof(FrameCheckSequence),
frame.size());
decltype(kExpectedFrameChunk) out_buffer;
frame.WriteToFragment(out_buffer.mutable_view(), 1);
EXPECT_TRUE(ContainersEqual(kExpectedFrameChunk, out_buffer));
}
TEST(FragmenterTest, OutboundFrameOffsetInPayload) {
StaticByteBuffer payload('T', 'e', 's', 't');
StaticByteBuffer kExpectedFrameChunk(
// Payload
'e',
's',
't',
// First byte of FCS
0xa4);
OutboundFrame frame(
kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs);
EXPECT_EQ(sizeof(BasicHeader) + payload.size() + sizeof(FrameCheckSequence),
frame.size());
decltype(kExpectedFrameChunk) out_buffer;
frame.WriteToFragment(out_buffer.mutable_view(), sizeof(BasicHeader) + 1);
EXPECT_TRUE(ContainersEqual(kExpectedFrameChunk, out_buffer));
}
TEST(FragmenterTest, OutboundFrameOffsetInFcs) {
StaticByteBuffer payload('T', 'e', 's', 't');
// Second and last byte of FCS
StaticByteBuffer kExpectedFrameChunk(0xc3);
OutboundFrame frame(
kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs);
EXPECT_EQ(sizeof(BasicHeader) + payload.size() + sizeof(FrameCheckSequence),
frame.size());
decltype(kExpectedFrameChunk) out_buffer;
frame.WriteToFragment(out_buffer.mutable_view(),
sizeof(BasicHeader) + payload.size() + 1);
EXPECT_TRUE(ContainersEqual(kExpectedFrameChunk, out_buffer));
}
// This isn't expected to happen from Fragmenter.
TEST(FragmenterTest, OutboundFrameOutBufferBigger) {
StaticByteBuffer payload('T', 'e', 's', 't');
StaticByteBuffer kExpectedFrameChunk(
// Basic L2CAP header (minus first two bytes)
0x01,
0x00,
// Payload
'T',
'e',
's',
't',
// Extraneous unused bytes
0,
0,
0);
OutboundFrame frame(
kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
EXPECT_EQ(sizeof(BasicHeader) + payload.size(), frame.size());
decltype(kExpectedFrameChunk) out_buffer;
out_buffer.SetToZeros();
frame.WriteToFragment(out_buffer.mutable_view(), 2);
EXPECT_TRUE(ContainersEqual(kExpectedFrameChunk, out_buffer));
}
TEST(FragmenterTest, EmptyPayload) {
BufferView payload;
StaticByteBuffer expected_fragment(
// ACL data header
0x01,
0x00,
0x04,
0x00,
// Basic L2CAP header (0-length Information Payload)
0x00,
0x00,
0x01,
0x00);
// Make the fragment limit a lot larger than the test frame size.
Fragmenter fragmenter(kTestHandle, 1024);
PDU pdu = fragmenter.BuildFrame(
kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
ASSERT_TRUE(pdu.is_valid());
EXPECT_EQ(1u, pdu.fragment_count());
auto fragments = pdu.ReleaseFragments();
EXPECT_TRUE(
ContainersEqual(expected_fragment, (*fragments.begin())->view().data()));
}
TEST(FragmenterTest, SingleFragment) {
StaticByteBuffer payload('T', 'e', 's', 't');
StaticByteBuffer expected_fragment(
// ACL data header
0x01,
0x00,
0x08,
0x00,
// Basic L2CAP header
0x04,
0x00,
0x01,
0x00,
'T',
'e',
's',
't');
// Make the fragment limit a lot larger than the test frame size.
Fragmenter fragmenter(kTestHandle, 1024);
PDU pdu = fragmenter.BuildFrame(
kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
ASSERT_TRUE(pdu.is_valid());
EXPECT_EQ(1u, pdu.fragment_count());
auto fragments = pdu.ReleaseFragments();
EXPECT_TRUE(
ContainersEqual(expected_fragment, (*fragments.begin())->view().data()));
}
TEST(FragmenterTest, SingleFragmentExactFit) {
StaticByteBuffer payload('T', 'e', 's', 't');
StaticByteBuffer expected_fragment(
// ACL data header
0x01,
0x00,
0x08,
0x00,
// Basic L2CAP header
0x04,
0x00,
0x01,
0x00,
'T',
'e',
's',
't');
// Make the fragment limit large enough to fit exactly one B-frame containing
// |payload|.
Fragmenter fragmenter(kTestHandle, payload.size() + sizeof(BasicHeader));
PDU pdu = fragmenter.BuildFrame(
kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
ASSERT_TRUE(pdu.is_valid());
EXPECT_EQ(1u, pdu.fragment_count());
auto fragments = pdu.ReleaseFragments();
EXPECT_TRUE(
ContainersEqual(expected_fragment, (*fragments.begin())->view().data()));
}
TEST(FragmenterTest, TwoFragmentsOffByOne) {
StaticByteBuffer payload('T', 'e', 's', 't', '!');
StaticByteBuffer expected_fragment0(
// ACL data header
0x01,
0x00,
0x08,
0x00,
// Basic L2CAP header, contains the complete length but a partial payload
0x05,
0x00,
0x01,
0x00,
'T',
'e',
's',
't');
StaticByteBuffer expected_fragment1(
// ACL data header
0x01,
0x10,
0x01,
0x00,
// Continuing payload
'!');
// Make the fragment limit large enough to fit exactly one B-frame containing
// 1 octet less than |payload|. The last octet should be placed in a second
// fragment.
Fragmenter fragmenter(kTestHandle, payload.size() + sizeof(BasicHeader) - 1);
PDU pdu = fragmenter.BuildFrame(
kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
ASSERT_TRUE(pdu.is_valid());
EXPECT_EQ(2u, pdu.fragment_count());
auto fragments = pdu.ReleaseFragments();
EXPECT_TRUE(
ContainersEqual(expected_fragment0, (*fragments.begin())->view().data()));
EXPECT_TRUE(ContainersEqual(expected_fragment1,
(*++fragments.begin())->view().data()));
}
TEST(FragmenterTest, TwoFragmentsExact) {
StaticByteBuffer payload('T', 'e', 's', 't');
BT_DEBUG_ASSERT_MSG(payload.size() % 2 == 0,
"test payload size should be even");
StaticByteBuffer expected_fragment0(
// ACL data header
0x01,
0x00,
0x04,
0x00,
// Basic L2CAP header, contains the complete length but a partial payload
0x04,
0x00,
0x01,
0x00);
StaticByteBuffer expected_fragment1(
// ACL data header
0x01,
0x10,
0x04,
0x00,
// Continuing payload
'T',
'e',
's',
't');
// Make the fragment limit large enough to fit exactly half a B-frame
// containing |payload|. The frame should be evenly divided across two
// fragments.
Fragmenter fragmenter(kTestHandle,
(payload.size() + sizeof(BasicHeader)) / 2);
PDU pdu = fragmenter.BuildFrame(
kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
ASSERT_TRUE(pdu.is_valid());
EXPECT_EQ(2u, pdu.fragment_count());
auto fragments = pdu.ReleaseFragments();
EXPECT_TRUE(
ContainersEqual(expected_fragment0, (*fragments.begin())->view().data()));
EXPECT_TRUE(ContainersEqual(expected_fragment1,
(*++fragments.begin())->view().data()));
}
TEST(FragmenterTest, ManyFragmentsOffByOne) {
constexpr size_t kMaxFragmentPayloadSize = 5;
constexpr size_t kExpectedFragmentCount = 4;
constexpr size_t kFrameSize =
(kExpectedFragmentCount - 1) * kMaxFragmentPayloadSize + 1;
StaticByteBuffer<kFrameSize - sizeof(BasicHeader)> payload;
payload.Fill('X');
StaticByteBuffer expected_fragment0(
// ACL data header
0x01,
0x00,
0x05,
0x00,
// Basic L2CAP header contains the complete length but partial payload
0x0C,
0x00,
0x01,
0x00,
'X');
StaticByteBuffer expected_fragment1(
// ACL data header
0x01,
0x10,
0x05,
0x00,
// Continuing payload
'X',
'X',
'X',
'X',
'X');
StaticByteBuffer expected_fragment2(
// ACL data header
0x01,
0x10,
0x05,
0x00,
// Continuing payload
'X',
'X',
'X',
'X',
'X');
StaticByteBuffer expected_fragment3(
// ACL data header
0x01,
0x10,
0x01,
0x00,
// Continuing payload
'X');
Fragmenter fragmenter(kTestHandle, kMaxFragmentPayloadSize);
PDU pdu = fragmenter.BuildFrame(
kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
ASSERT_TRUE(pdu.is_valid());
EXPECT_EQ(kExpectedFragmentCount, pdu.fragment_count());
auto fragments = pdu.ReleaseFragments();
auto iter = fragments.begin();
EXPECT_TRUE(ContainersEqual(expected_fragment0, (*iter++)->view().data()));
EXPECT_TRUE(ContainersEqual(expected_fragment1, (*iter++)->view().data()));
EXPECT_TRUE(ContainersEqual(expected_fragment2, (*iter++)->view().data()));
EXPECT_TRUE(ContainersEqual(expected_fragment3, (*iter)->view().data()));
}
TEST(FragmenterTest, MaximalSizedPayload) {
DynamicByteBuffer payload(65535);
Fragmenter fragmenter(kTestHandle, 1024);
PDU pdu = fragmenter.BuildFrame(
kTestChannelId, payload, FrameCheckSequenceOption::kNoFcs);
ASSERT_TRUE(pdu.is_valid());
EXPECT_LT(64u, pdu.fragment_count());
}
TEST(FragmenterTest, FragmentsFrameCheckSequence) {
constexpr size_t kMaxFragmentPayloadSize = 5;
constexpr size_t kExpectedFragmentCount = 3;
constexpr size_t kFrameSize =
(kExpectedFragmentCount - 1) * kMaxFragmentPayloadSize + 1;
StaticByteBuffer payload('0', '1', '2', '3', '4');
EXPECT_EQ(kFrameSize - sizeof(BasicHeader) - sizeof(FrameCheckSequence),
payload.size());
StaticByteBuffer expected_fragment0(
// ACL data header
0x01,
0x00,
kMaxFragmentPayloadSize,
0x00,
// Basic L2CAP header contains the length of PDU (including FCS), partial
// payload
0x07,
0x00,
0x01,
0x00,
'0');
StaticByteBuffer expected_fragment1(
// ACL data header
0x01,
0x10,
kMaxFragmentPayloadSize,
0x00,
// Remaining bytes of payload and first byte of FCS
'1',
'2',
'3',
'4',
0xcc);
StaticByteBuffer expected_fragment2(
// ACL data header
0x01,
0x10,
0x01,
0x00,
// Last byte of FCS
0xe0);
Fragmenter fragmenter(kTestHandle, kMaxFragmentPayloadSize);
PDU pdu = fragmenter.BuildFrame(
kTestChannelId, payload, FrameCheckSequenceOption::kIncludeFcs);
ASSERT_TRUE(pdu.is_valid());
EXPECT_EQ(kExpectedFragmentCount, pdu.fragment_count());
auto fragments = pdu.ReleaseFragments();
auto iter = fragments.begin();
EXPECT_TRUE(ContainersEqual(expected_fragment0, (*iter++)->view().data()));
EXPECT_TRUE(ContainersEqual(expected_fragment1, (*iter++)->view().data()));
EXPECT_TRUE(ContainersEqual(expected_fragment2, (*iter++)->view().data()));
}
} // namespace
} // namespace bt::l2cap