blob: 729244f87781bf1fcc2c408cac71a3e26d6756ba [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 "fragmenter.h"
#include <endian.h>
#include "garnet/drivers/bluetooth/lib/hci/acl_data_packet.h"
#include "garnet/drivers/bluetooth/lib/l2cap/l2cap_defs.h"
#include "lib/fxl/logging.h"
namespace btlib {
namespace l2cap {
Fragmenter::Fragmenter(hci::ConnectionHandle connection_handle,
uint16_t max_acl_payload_size)
: connection_handle_(connection_handle),
max_acl_payload_size_(max_acl_payload_size) {
FXL_DCHECK(connection_handle_);
FXL_DCHECK(connection_handle_ <= hci::kConnectionHandleMax);
FXL_DCHECK(max_acl_payload_size_);
FXL_DCHECK(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::BuildBasicFrame(ChannelId channel_id,
const common::ByteBuffer& data,
bool flushable) {
FXL_DCHECK(data.size() < kMaxBasicFramePayloadSize);
FXL_DCHECK(channel_id);
const size_t frame_size = data.size() + sizeof(BasicHeader);
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++) {
FXL_DCHECK(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::ACLPacketBoundaryFlag::kContinuingFragment
: (flushable ? hci::ACLPacketBoundaryFlag::kFirstFlushable
: hci::ACLPacketBoundaryFlag::kFirstNonFlushable));
// TODO(armansito): allow passing Active Slave Broadcast flag when we
// support it.
auto acl_packet = hci::ACLDataPacket::New(
connection_handle_, pbf, hci::ACLBroadcastFlag::kPointToPoint,
fragment_size);
FXL_DCHECK(acl_packet);
auto mut_payload = acl_packet->mutable_view()->mutable_payload_data();
// Special-case the first fragment separately as it contains the basic
// header.
if (i == 0) {
auto* header = acl_packet->mutable_view()->mutable_payload<BasicHeader>();
// Write the Basic L2CAP header.
header->length = htole16(static_cast<uint16_t>(data.size()));
header->channel_id = htole16(channel_id);
mut_payload.Write(data.data(), fragment_size - sizeof(BasicHeader),
sizeof(BasicHeader));
} else {
mut_payload.Write(data.data() + processed - sizeof(BasicHeader),
fragment_size);
}
processed += fragment_size;
pdu.AppendFragment(std::move(acl_packet));
}
// The PDU should have been completely processed if we got here.
FXL_DCHECK(processed == frame_size);
return pdu;
}
} // namespace l2cap
} // namespace btlib