blob: 4124743a569f4dd86df3874429acc061f759f636 [file] [log] [blame]
// Copyright 2022 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.
#ifndef SRC_CONNECTIVITY_BLUETOOTH_HCI_TRANSPORT_USB_PACKET_REASSEMBLER_H_
#define SRC_CONNECTIVITY_BLUETOOTH_HCI_TRANSPORT_USB_PACKET_REASSEMBLER_H_
#include <lib/ddk/debug.h>
#include <lib/fit/function.h>
#include <lib/stdcompat/span.h>
#include <zircon/assert.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
namespace bt_transport_usb {
// PacketReassembler is a utility for accumulating packet chunks and reporting when a complete
// packet has been accumulated. It is intended to be used for SCO packets and HCI event packets,
// which both have a 1-byte length field in the header. |kMaxPacketSize| is the max frame size of
// the packet this reasembler is being used for.
template <size_t kMaxPacketSize>
class PacketReassembler {
public:
using PacketCallback = fit::function<void(cpp20::span<const uint8_t>)>;
// |length_param_index| is the index in the header of the 1-byte header field.
// |header_size| is the size of the packet header.
// |packet_cb| is called with packets as soon as a full packet is
// accumulated via ProcessData().
PacketReassembler(size_t length_param_index, size_t header_size, PacketCallback packet_cb)
: length_param_index_(length_param_index),
header_size_(header_size),
packet_cb_(std::move(packet_cb)) {
ZX_ASSERT(kMaxPacketSize <= header_size + std::numeric_limits<uint8_t>::max());
ZX_ASSERT(length_param_index_ < header_size_);
}
void clear() {
accumulated_bytes_ = 0;
accumulated_.fill(0);
}
// Append data to the accumulator and call packet handler if a complete packet has been
// accumulated. |buffer| may contain data from multiple packets.
void ProcessData(cpp20::span<const uint8_t> buffer) {
// If nothing has accumulated, report complete packets in buffer and accumulate the rest.
if (accumulated_bytes_ == 0) {
ReportCompletePacketsAndAccumulateRemainder(buffer);
return;
}
// Otherwise, try to complete accumulated packet.
uint8_t data_length = 0;
// The length parameter may be in |buffer|.
if (accumulated_bytes_ <= length_param_index_) {
// The length parameter may not be in |accumulated_| or |buffer|. In this case, just
// accumulate |buffer|.
if (accumulated_bytes_ + buffer.size() <= length_param_index_) {
Accumulate(buffer);
return;
}
data_length = buffer[length_param_index_ - accumulated_bytes_];
} else {
data_length = accumulated_[length_param_index_];
}
const size_t packet_size = header_size_ + data_length;
const size_t remaining_needed = packet_size - accumulated_bytes_;
// If the buffer completes a packet in the accumulator, report that packet before processing
// additional packets.
if (buffer.size() >= remaining_needed) {
Accumulate(buffer.subspan(0, remaining_needed));
size_t buffer_offset = remaining_needed;
packet_cb_({accumulated_.data(), packet_size});
clear();
ReportCompletePacketsAndAccumulateRemainder(buffer.subspan(buffer_offset));
return;
}
// |buffer| does not complete the packet. Add to accumulator.
Accumulate(buffer);
}
private:
void ReportCompletePacketsAndAccumulateRemainder(cpp20::span<const uint8_t> buffer) {
size_t offset = ReportCompletePackets(buffer);
Accumulate(buffer.subspan(offset));
}
// Returns the index of the byte after the last complete packet (or 0 if no complete packets
// exist).
size_t ReportCompletePackets(cpp20::span<const uint8_t> buffer) {
size_t buffer_offset = 0;
// Report all complete packets in buffer (avoiding copies).
while (buffer_offset + header_size_ < buffer.size()) {
const uint8_t data_length = buffer[buffer_offset + length_param_index_];
const size_t packet_size = header_size_ + data_length;
if (buffer.size() < buffer_offset + packet_size) {
break;
}
packet_cb_(buffer.subspan(buffer_offset, packet_size));
buffer_offset += packet_size;
}
return buffer_offset;
}
void Accumulate(cpp20::span<const uint8_t> buffer) {
std::copy(buffer.begin(), buffer.end(), accumulated_.begin() + accumulated_bytes_);
accumulated_bytes_ += buffer.size();
}
const size_t length_param_index_;
const size_t header_size_;
std::array<uint8_t, kMaxPacketSize> accumulated_;
// The size of the valid accumulated data in accumulated_.
size_t accumulated_bytes_ = 0;
PacketCallback packet_cb_;
};
} // namespace bt_transport_usb
#endif // SRC_CONNECTIVITY_BLUETOOTH_HCI_TRANSPORT_USB_PACKET_REASSEMBLER_H_