| // 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 |