blob: a9ef5664af4391d567956651c344aaacc2f87f9c [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/recombiner.h"
#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/common/log.h"
namespace bt::l2cap {
namespace {
const BasicHeader& GetBasicHeader(const hci::ACLDataPacket& fragment) {
BT_DEBUG_ASSERT(fragment.packet_boundary_flag() !=
hci_spec::ACLPacketBoundaryFlag::kContinuingFragment);
return fragment.view().payload<BasicHeader>();
}
} // namespace
Recombiner::Recombiner(hci_spec::ConnectionHandle handle) : handle_(handle) {}
Recombiner::Result Recombiner::ConsumeFragment(hci::ACLDataPacketPtr fragment) {
BT_DEBUG_ASSERT(fragment);
BT_DEBUG_ASSERT(fragment->connection_handle() == handle_);
TRACE_DURATION("bluetooth", "Recombiner::AddFragment");
if (!recombination_) {
return ProcessFirstFragment(std::move(fragment));
}
// If we received a new initial packet without completing the recombination,
// then drop the entire last sequence.
if (fragment->packet_boundary_flag() !=
hci_spec::ACLPacketBoundaryFlag::kContinuingFragment) {
bt_log(
WARN, "l2cap", "expected continuing fragment! (handle: %.4x)", handle_);
ClearRecombination();
// Try to initiate a new starting sequence with |fragment|.
auto result = ProcessFirstFragment(std::move(fragment));
// Report an error for the dropped frame, even if there was no error
// processing |fragment| itself.
result.frames_dropped = true;
return result;
}
recombination_->accumulated_length += fragment->view().payload_size();
recombination_->pdu.AppendFragment(std::move(fragment));
BeginTrace();
if (recombination_->accumulated_length >
recombination_->expected_frame_length) {
bt_log(
WARN, "l2cap", "continuing fragment too long! (handle: %.4x)", handle_);
ClearRecombination();
// Drop |fragment| since a continuing fragment cannot begin a sequence.
return {.pdu = {}, .frames_dropped = true};
}
if (recombination_->accumulated_length ==
recombination_->expected_frame_length) {
// The frame is complete!
auto pdu = std::move(recombination_->pdu);
ClearRecombination();
return {.pdu = {std::move(pdu)}, .frames_dropped = false};
}
// The frame is not complete yet.
return {.pdu = {}, .frames_dropped = false};
}
Recombiner::Result Recombiner::ProcessFirstFragment(
hci::ACLDataPacketPtr fragment) {
BT_DEBUG_ASSERT(fragment);
BT_DEBUG_ASSERT(!recombination_);
// The first fragment needs to at least contain the Basic L2CAP header and
// should not be a continuation fragment.
size_t current_length = fragment->view().payload_size();
if (fragment->packet_boundary_flag() ==
hci_spec::ACLPacketBoundaryFlag::kContinuingFragment ||
current_length < sizeof(BasicHeader)) {
bt_log(DEBUG, "l2cap", "bad first fragment (size: %zu)", current_length);
return {.pdu = {}, .frames_dropped = true};
}
// TODO(armansito): Also validate that the controller honors the HCI packet
// boundary flag contract for the controller-to-host flow direction.
size_t expected_frame_length =
le16toh(GetBasicHeader(*fragment).length) + sizeof(BasicHeader);
if (current_length > expected_frame_length) {
bt_log(DEBUG,
"l2cap",
"fragment malformed: payload too long (expected length: %zu, "
"fragment length: %zu)",
expected_frame_length,
current_length);
return {.pdu = {}, .frames_dropped = true};
}
// We can start building a PDU.
PDU pdu;
pdu.AppendFragment(std::move(fragment));
if (current_length == expected_frame_length) {
// The PDU is complete.
return {.pdu = {std::move(pdu)}, .frames_dropped = false};
}
// We need to recombine multiple fragments to obtain a complete PDU.
BeginTrace();
recombination_ = {
.pdu = std::move(pdu),
.expected_frame_length = expected_frame_length,
.accumulated_length = current_length,
};
return {.pdu = {}, .frames_dropped = false};
}
void Recombiner::ClearRecombination() {
BT_DEBUG_ASSERT(recombination_);
if (recombination_->pdu.is_valid()) {
bt_log(DEBUG,
"l2cap",
"recombiner dropped packet (fragments: %zu, expected length: %zu, "
"accumulated length: "
"%zu, handle: %.4x)",
recombination_->pdu.fragment_count(),
recombination_->expected_frame_length,
recombination_->accumulated_length,
handle_);
}
recombination_.reset();
EndTraces();
}
void Recombiner::BeginTrace() {
if (!TRACE_ENABLED()) {
return;
}
trace_flow_id_t flow_id = TRACE_NONCE();
TRACE_FLOW_BEGIN(
"bluetooth", "Recombiner buffered ACL data fragment", flow_id);
trace_ids_.push_back(flow_id);
}
void Recombiner::EndTraces() {
if (!TRACE_ENABLED()) {
return;
}
for ([[maybe_unused]] auto flow_id : trace_ids_) {
TRACE_FLOW_END(
"bluetooth", "Recombiner buffered ACL data fragment", flow_id);
}
trace_ids_.clear();
}
} // namespace bt::l2cap