blob: fb7e27dc3133dad5d53ba58eb61954ea55d7fefe [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 "recombiner.h"
#include <zircon/assert.h>
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
namespace bt {
namespace l2cap {
namespace {
const BasicHeader& GetBasicHeader(const hci::ACLDataPacket& fragment) {
ZX_DEBUG_ASSERT(fragment.packet_boundary_flag() !=
hci::ACLPacketBoundaryFlag::kContinuingFragment);
return fragment.view().payload<BasicHeader>();
}
} // namespace
Recombiner::Recombiner() : ready_(false), frame_length_(0u), cur_length_(0u) {}
bool Recombiner::AddFragment(hci::ACLDataPacketPtr&& fragment) {
ZX_DEBUG_ASSERT(fragment);
if (ready())
return false;
if (empty()) {
if (!ProcessFirstFragment(*fragment))
return false;
ZX_DEBUG_ASSERT(!empty());
} else {
if (fragment->packet_boundary_flag() !=
hci::ACLPacketBoundaryFlag::kContinuingFragment) {
bt_log(SPEW, "l2cap", "expected continuing fragment!");
return false;
}
if (cur_length_ + fragment->view().payload_size() > frame_length_) {
bt_log(SPEW, "l2cap", "continuing fragment too long!");
return false;
}
}
cur_length_ += fragment->view().payload_size();
if (frame_length_ == cur_length_) {
// The PDU is complete.
ready_ = true;
}
pdu_->AppendFragment(std::move(fragment));
return true;
}
bool Recombiner::Release(PDU* out_pdu) {
if (empty() || !ready())
return false;
ZX_DEBUG_ASSERT(out_pdu);
*out_pdu = std::move(*pdu_);
Drop();
return true;
}
void Recombiner::Drop() {
ready_ = false;
frame_length_ = 0u;
cur_length_ = 0u;
pdu_.reset();
}
bool Recombiner::ProcessFirstFragment(const hci::ACLDataPacket& fragment) {
ZX_DEBUG_ASSERT(!ready());
ZX_DEBUG_ASSERT(!frame_length_);
ZX_DEBUG_ASSERT(!cur_length_);
// The first fragment needs to at least contain the Basic L2CAP header and
// should not be a continuation fragment.
if (fragment.packet_boundary_flag() ==
hci::ACLPacketBoundaryFlag::kContinuingFragment ||
fragment.view().payload_size() < sizeof(BasicHeader)) {
bt_log(SPEW, "l2cap", "bad first fragment");
return false;
}
uint16_t frame_length =
le16toh(GetBasicHeader(fragment).length) + sizeof(BasicHeader);
if (fragment.view().payload_size() > frame_length) {
bt_log(SPEW, "l2cap", "fragment too long!");
return false;
}
pdu_ = PDU();
frame_length_ = frame_length;
return true;
}
} // namespace l2cap
} // namespace bt