blob: 16e63991440104ef1c146ee33622e24649cf24ce [file] [log] [blame] [edit]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_bluetooth_proxy/internal/recombiner.h"
#include <optional>
#include "pw_assert/check.h"
#include "pw_log/log.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
namespace pw::bluetooth::proxy {
pw::Status Recombiner::StartRecombination(LockedL2capChannel& channel,
size_t size) {
if (IsActive()) {
return Status::FailedPrecondition();
}
pw::Status status = channel.channel().StartRecombinationBuf(direction_, size);
if (status.IsResourceExhausted()) {
PW_LOG_ERROR(
"Channel %#x can not store recombination buffer %s of size %zu.",
channel.channel().local_cid(),
status.str(),
size);
return status;
}
// We only expect OK or ResourceExhausted from StartRecombinationBuf.
PW_CHECK(status.ok());
is_active_ = true;
local_cid_ = channel.channel().local_cid();
expected_size_ = size;
recombined_size_ = 0;
return pw::OkStatus();
}
pw::Status Recombiner::RecombineFragment(
std::optional<LockedL2capChannel>& channel, pw::span<const uint8_t> data) {
if (!IsActive()) {
return Status::FailedPrecondition();
}
if (!HasBuf(channel)) {
// Channel we started recombination with was destroyed. Even if we were
// passed a channel, if its recombination buffer is gone that indicates it
// is a new channel.
// We still need to complete recombination to read the fragments from ACL
// connection, but Recombiner won't be storing the data (just tracking
// sizes).
PW_LOG_INFO(
"The channel instance (local cid %#x) that was initially receiving the "
"recombined PDU %s is no longer acquired. Will complete reading the "
"recombined PDU, but result will be dropped.",
local_cid(),
DirectionToString(direction_));
} else {
// We have a channel, add data to its buf.
// Since channel was found in caller using cid, this should never be false.
PW_CHECK_INT_EQ(channel->channel().local_cid(), local_cid_);
PW_TRY(channel->channel().CopyToRecombinationBuf(
direction_, as_bytes(data), write_offset()));
}
recombined_size_ += data.size();
if (IsComplete()) {
// Recombination is complete so we are no longer active (no longer
// recombining). Note buf is left in channel to be taken later in static
// TakeBuf
is_active_ = false;
}
return pw::OkStatus();
}
// static
multibuf::MultiBuf Recombiner::TakeBuf(
std::optional<LockedL2capChannel>& channel, Direction direction) {
PW_CHECK(channel.has_value());
PW_CHECK(channel->channel().HasRecombinationBuf(direction));
multibuf::MultiBuf return_buf =
channel->channel().TakeRecombinationBuf(direction);
// Should always be true since we used `AllocateContiguous()` to create the
// MultiBuf.
PW_CHECK(return_buf.IsContiguous());
return return_buf;
}
void Recombiner::EndRecombination(std::optional<LockedL2capChannel>& channel) {
is_active_ = false;
expected_size_ = 0;
recombined_size_ = 0;
if (!channel.has_value()) {
// Channel and its recombination writer has already been destroyed.
return;
}
PW_CHECK_INT_EQ(channel->channel().local_cid(), local_cid_);
channel->channel().EndRecombinationBuf(direction_);
}
} // namespace pw::bluetooth::proxy