blob: ae87590fbb154b396d878209ebd0a8daf7818c09 [file]
// 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/config.h"
#if PW_BLUETOOTH_PROXY_ASYNC == 0
#include <mutex>
#include "pw_assert/check.h"
#include "pw_bluetooth_proxy/internal/generic_l2cap_channel.h"
#include "pw_bluetooth_proxy/internal/l2cap_channel.h"
#include "pw_bluetooth_proxy/internal/l2cap_channel_manager.h"
#include "pw_log/log.h"
#include "pw_status/status.h"
namespace pw::bluetooth::proxy::internal {
sync::Mutex L2capChannelImpl::static_mutex_;
BorrowedL2capChannel::BorrowedL2capChannel(L2capChannelImpl& impl)
: impl_(&impl) {
std::lock_guard lock(impl_->mutex_);
++impl_->num_borrows_;
PW_CHECK_UINT_NE(impl_->num_borrows_, 0);
}
BorrowedL2capChannel& BorrowedL2capChannel::operator=(
BorrowedL2capChannel&& other) {
if (this != &other) {
std::swap(impl_, other.impl_);
}
return *this;
}
BorrowedL2capChannel::~BorrowedL2capChannel() {
if (impl_ == nullptr) {
return;
}
std::lock_guard lock(impl_->mutex_);
PW_CHECK_UINT_NE(impl_->num_borrows_, 0);
if (--impl_->num_borrows_ == 0) {
impl_->notification_.release();
}
}
L2capChannel* BorrowedL2capChannel::operator->() { return &impl_->channel_; }
L2capChannel& BorrowedL2capChannel::operator*() { return impl_->channel_; }
void L2capChannelImpl::BlockWhileBorrowed(
std::unique_lock<internal::Mutex>& lock) PW_NO_LOCK_SAFETY_ANALYSIS {
while (num_borrows_ != 0) {
lock.unlock();
notification_.acquire();
lock.lock();
}
}
void L2capChannelImpl::Close() {
std::lock_guard lock(L2capChannelImpl::mutex());
if (client_.has_value() && *client_ != nullptr) {
(*client_)->channel_ = nullptr;
}
// Always set to null to mark channel as stale, even if it was not acquired.
client_ = nullptr;
}
StatusWithMultiBuf L2capChannelImpl::Write(multibuf::MultiBuf&& payload)
PW_NO_LOCK_SAFETY_ANALYSIS {
std::lock_guard lock(mutex_);
if (channel_.state_ != L2capChannel::State::kRunning) {
PW_LOG_WARN(
"btproxy: L2capChannel::Write called when not running. "
"local_cid: %#x, remote_cid: %#x, state: %u",
channel_.local_cid(),
channel_.remote_cid(),
cpp23::to_underlying(channel_.state_));
return {Status::FailedPrecondition(), std::move(payload)};
}
if (payload_queue_.full()) {
notify_on_dequeue_ = true;
return {Status::Unavailable(), std::move(payload)};
}
payload_queue_.push(std::move(payload));
channel_.ReportNewTxPacketsOrCredits();
return {OkStatus(), std::nullopt};
}
Status L2capChannelImpl::IsWriteAvailable() PW_NO_LOCK_SAFETY_ANALYSIS {
std::lock_guard lock(mutex_);
if (channel_.state_ != L2capChannel::State::kRunning) {
return Status::FailedPrecondition();
}
if (payload_queue_.full()) {
notify_on_dequeue_ = true;
return Status::Unavailable();
}
notify_on_dequeue_ = false;
return OkStatus();
}
std::optional<H4PacketWithH4> L2capChannelImpl::DequeuePacket()
PW_NO_LOCK_SAFETY_ANALYSIS {
std::optional<H4PacketWithH4> packet;
bool should_notify = false;
{
// Get a payload.
std::lock_guard lock(mutex_);
if (channel_.state_ != L2capChannel::State::kRunning) {
payload_queue_.clear();
}
if (payload_queue_.empty()) {
return std::nullopt;
}
const multibuf::MultiBuf& payload = payload_queue_.front();
// Create a packet from the payload.
bool keep_payload = false;
packet = channel_.GenerateNextTxPacket(payload, keep_payload);
// If no additional packets can be created from the payload, remove it.
if (!keep_payload) {
payload_queue_.pop();
should_notify = notify_on_dequeue_;
notify_on_dequeue_ = false;
}
}
// Notify the client if there is now room in the queue.
if (should_notify) {
SendEvent(L2capChannelEvent::kWriteAvailable);
}
return packet;
}
void L2capChannelImpl::ReportNewTxPacketsOrCredits() {
channel_.l2cap_channel_manager_.ReportNewTxPacketsOrCredits();
}
void L2capChannelImpl::ClearQueue() { payload_queue_.clear(); }
bool L2capChannelImpl::IsStale() const {
std::lock_guard lock(L2capChannelImpl::mutex());
return client_.has_value() && *client_ == nullptr;
}
bool L2capChannelImpl::PayloadQueueEmpty() const {
std::lock_guard lock(mutex_);
return payload_queue_.empty();
}
void L2capChannelImpl::SendEvent(L2capChannelEvent event) {
{
std::lock_guard lock(L2capChannelImpl::mutex());
if (!client_.has_value() || *client_ == nullptr) {
return;
}
}
// We don't log kWriteAvailable since they happen often. Optimally we would
// just debug log them also, but one of our downstreams logs all levels.
if (event != L2capChannelEvent::kWriteAvailable) {
PW_LOG_INFO(
"btproxy: L2capChannel::SendEvent - connection: %#x, "
"local_cid: %#x, event: %u,",
channel_.connection_handle(),
channel_.local_cid(),
cpp23::to_underlying(event));
}
if (channel_.event_fn_) {
channel_.event_fn_(event);
}
}
} // namespace pw::bluetooth::proxy::internal
#endif // PW_BLUETOOTH_PROXY_ASYNC