blob: 56bb3838f7783661b69b68eab2b2c2a98d82933a [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 <endian.h>
#include <limits>
#include <optional>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fcs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/acl_data_packet.h"
namespace bt::l2cap {
namespace {
// ByteBuffer::Copy does not allow copying to a smaller destination for safety.
// This clamps the copy size to both the source size and the destination size.
size_t CopyBounded(MutableBufferView destination, const ByteBuffer& source) {
const size_t size = std::min(destination.size(), source.size());
source.Copy(&destination, 0, size);
return size;
}
} // namespace
OutboundFrame::OutboundFrame(ChannelId channel_id,
const ByteBuffer& data,
FrameCheckSequenceOption fcs_option)
: channel_id_(channel_id),
data_(data.view()),
fcs_option_(fcs_option),
fcs_(include_fcs() ? std::optional(MakeFcs()) : std::nullopt) {}
size_t OutboundFrame::size() const {
return sizeof(BasicHeader) + data_.size() +
(include_fcs() ? sizeof(FrameCheckSequence) : 0);
}
void OutboundFrame::WriteToFragment(MutableBufferView fragment_payload,
size_t offset) {
// Build a table of the pages making up the frame's content, in sorted order.
const StaticByteBuffer header_buffer = MakeBasicHeader();
const std::optional fcs_buffer =
include_fcs() ? std::optional(MakeFcs()) : std::nullopt;
const BufferView footer_buffer =
fcs_buffer ? fcs_buffer->view() : BufferView();
const std::array pages = {
header_buffer.view(), data_.view(), footer_buffer, BufferView()};
const std::array offsets = {size_t{0},
header_buffer.size(),
header_buffer.size() + data_.size(),
size()};
static_assert(pages.size() == offsets.size());
BT_ASSERT(offset <= size());
size_t output_offset = 0;
// Find the last page whose offset is not greater than the current offset.
const auto page_iter =
std::prev(std::upper_bound(offsets.begin(), offsets.end(), offset));
for (size_t page_index = page_iter - offsets.begin();
page_index < pages.size();
page_index++) {
if (fragment_payload.size() - output_offset == 0) {
break;
}
const auto& page_buffer = pages[page_index];
const size_t bytes_copied =
CopyBounded(fragment_payload.mutable_view(output_offset),
page_buffer.view(offset - offsets[page_index]));
offset += bytes_copied;
output_offset += bytes_copied;
}
BT_ASSERT(output_offset <= fragment_payload.size());
}
OutboundFrame::BasicHeaderBuffer OutboundFrame::MakeBasicHeader() const {
// Length is "the length of the entire L2CAP PDU in octets, excluding the
// Length and CID field" (v5.0 Vol 3, Part A, Section 3.3.1)
const size_t pdu_content_length = size() - sizeof(BasicHeader);
BT_ASSERT_MSG(pdu_content_length <=
std::numeric_limits<decltype(BasicHeader::length)>::max(),
"PDU payload is too large to be encoded");
BasicHeader header = {};
header.length = htole16(pdu_content_length);
header.channel_id = htole16(channel_id_);
BasicHeaderBuffer buffer;
buffer.WriteObj(header);
return buffer;
}
OutboundFrame::FrameCheckSequenceBuffer OutboundFrame::MakeFcs() const {
BT_ASSERT(include_fcs());
const BasicHeaderBuffer header = MakeBasicHeader();
const FrameCheckSequence header_fcs = l2cap::ComputeFcs(header.view());
const FrameCheckSequence whole_fcs =
l2cap::ComputeFcs(data_.view(), header_fcs);
FrameCheckSequenceBuffer buffer;
buffer.WriteObj(htole16(whole_fcs.fcs));
return buffer;
}
Fragmenter::Fragmenter(hci_spec::ConnectionHandle connection_handle,
uint16_t max_acl_payload_size)
: connection_handle_(connection_handle),
max_acl_payload_size_(max_acl_payload_size) {
BT_ASSERT(connection_handle_ <= hci_spec::kConnectionHandleMax);
BT_ASSERT(max_acl_payload_size_);
BT_ASSERT(max_acl_payload_size_ >= sizeof(BasicHeader));
}
// NOTE(armansito): The following method copies the contents of |data| into ACL
// data packets. This copying is currently necessary because the complete HCI
// frame (ACL header + payload fragment) we send over the channel to the bt-hci
// driver need to be stored contiguously before the call to zx_channel_write.
// Plus, we perform the HCI flow-control on the host-stack side which requires
// ACL packets to be buffered.
//
// As our future driver architecture will remove the IPC between the HCI driver
// and the host stack, our new interface could support scatter-gather for the
// header and the payload. Then, the bt-hci driver could read the payload
// fragment directly out of |data| and we would only construct the headers,
// removing the extra copy.
//
// * Current theoretical number of data copies:
// 1. service -> L2CAP channel
// 2. channel -> fragmenter ->(move) HCI layer
// 3. HCI layer ->(zx_channel_write)
// 4. (zx_channel_read)-> bt-hci driver
// 5. bt-hci driver -> transport driver
//
// * Potential number of data copies
// 1. service -> L2CAP channel
// 2. channel -> fragmenter ->(move) HCI layer ->(move) bt-hci driver
// if buffering is needed:
// 3. bt-hci driver -> transport driver
PDU Fragmenter::BuildFrame(ChannelId channel_id,
const ByteBuffer& data,
FrameCheckSequenceOption fcs_option,
bool flushable) const {
BT_DEBUG_ASSERT(data.size() <= kMaxBasicFramePayloadSize);
BT_DEBUG_ASSERT(channel_id);
OutboundFrame frame(channel_id, data, fcs_option);
const size_t frame_size = frame.size();
const size_t num_fragments = frame_size / max_acl_payload_size_ +
(frame_size % max_acl_payload_size_ ? 1 : 0);
PDU pdu;
size_t processed = 0;
for (size_t i = 0; i < num_fragments; i++) {
BT_DEBUG_ASSERT(frame_size > processed);
const size_t fragment_size = std::min(
frame_size - processed, static_cast<size_t>(max_acl_payload_size_));
auto pbf =
(i ? hci_spec::ACLPacketBoundaryFlag::kContinuingFragment
: (flushable ? hci_spec::ACLPacketBoundaryFlag::kFirstFlushable
: hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable));
// TODO(armansito): allow passing Active Peripheral Broadcast flag when we
// support it.
auto acl_packet =
hci::ACLDataPacket::New(connection_handle_,
pbf,
hci_spec::ACLBroadcastFlag::kPointToPoint,
static_cast<uint16_t>(fragment_size));
BT_DEBUG_ASSERT(acl_packet);
frame.WriteToFragment(acl_packet->mutable_view()->mutable_payload_data(),
processed);
processed += fragment_size;
pdu.AppendFragment(std::move(acl_packet));
}
// The PDU should have been completely processed if we got here.
BT_DEBUG_ASSERT(processed == frame_size);
return pdu;
}
} // namespace bt::l2cap