blob: 70286fb64f80712b46e704716c7a8f6be98fc50a [file] [log] [blame] [edit]
// Copyright 2024 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/l2cap_status_tracker.h"
#include <mutex>
#include "pw_containers/algorithm.h"
#include "pw_log/log.h"
namespace pw::bluetooth::proxy {
void L2capStatusTracker::RegisterDelegate(L2capStatusDelegate& delegate) {
std::lock_guard lock(mutex_);
delegates_.push_front(delegate);
}
void L2capStatusTracker::UnregisterDelegate(L2capStatusDelegate& delegate) {
std::lock_guard lock(mutex_);
delegates_.remove(delegate);
}
void L2capStatusTracker::HandleConnectionComplete(
const L2capChannelConnectionInfo& info) {
std::lock_guard lock(mutex_);
if (pending_connection_complete_.has_value()) {
PW_LOG_ERROR("Connection complete already pending");
return;
}
pending_connection_complete_ = info;
}
void L2capStatusTracker::HandleAclDisconnectionComplete(
uint16_t connection_handle) {
std::lock_guard lock(mutex_);
if (pending_acl_disconnection_complete_.has_value()) {
PW_LOG_ERROR("ACL disconnection complete already pending");
return;
}
pending_acl_disconnection_complete_ = connection_handle;
}
void L2capStatusTracker::HandleDisconnectionComplete(
const DisconnectParams& params) {
std::lock_guard lock(mutex_);
if (pending_disconnection_complete_.has_value()) {
PW_LOG_ERROR("Disconnection complete already pending");
return;
}
pending_disconnection_complete_ = params;
}
void L2capStatusTracker::DeliverPendingConnectionComplete(
const L2capChannelConnectionInfo& info) {
bool track = false;
for (L2capStatusDelegate& delegate : delegates_) {
if (!delegate.ShouldTrackPsm(info.psm)) {
continue;
}
track = true;
delegate.HandleConnectionComplete(info);
}
if (track) {
if (connected_channel_infos_.full()) {
// TODO: https://pwbug.dev/379558046 - Let client know we won't be able to
// notify on disconnect.
PW_LOG_ERROR(
"Couldn't track l2cap channel connection as requested, so will not "
"be able to send disconnect event to client.");
return;
}
connected_channel_infos_.push_back(info);
}
}
void L2capStatusTracker::DeliverPendingAclDisconnectionComplete(
uint16_t connection_handle) {
for (size_t i = 0; i < connected_channel_infos_.size();) {
L2capChannelConnectionInfo& info = connected_channel_infos_[i];
if (info.connection_handle == connection_handle) {
containers::ForEach(delegates_, [info](L2capStatusDelegate& delegate) {
if (delegate.ShouldTrackPsm(info.psm)) {
delegate.HandleDisconnectionComplete(info);
}
});
// Deleting this entry in Vector, so do not increment index.
connected_channel_infos_.erase(&info);
} else {
// Not deleting this entry in Vector, so increment index.
++i;
}
}
}
void L2capStatusTracker::DeliverPendingDisconnectionComplete(
const DisconnectParams& params) {
for (L2capStatusDelegate& delegate : delegates_) {
auto match = [&params](const L2capChannelConnectionInfo& i) {
return params.connection_handle == i.connection_handle &&
params.remote_cid == i.remote_cid &&
params.local_cid == i.local_cid;
};
auto connection_it = std::find_if(connected_channel_infos_.begin(),
connected_channel_infos_.end(),
match);
if (connection_it == connected_channel_infos_.end()) {
return;
}
delegate.HandleDisconnectionComplete(*connection_it);
connected_channel_infos_.erase(connection_it);
}
}
void L2capStatusTracker::DeliverPendingEvents() {
std::lock_guard lock(mutex_);
if (pending_connection_complete_.has_value()) {
DeliverPendingConnectionComplete(*pending_connection_complete_);
pending_connection_complete_.reset();
}
if (pending_acl_disconnection_complete_.has_value()) {
DeliverPendingAclDisconnectionComplete(
*pending_acl_disconnection_complete_);
pending_acl_disconnection_complete_.reset();
}
if (pending_disconnection_complete_.has_value()) {
DeliverPendingDisconnectionComplete(*pending_disconnection_complete_);
pending_disconnection_complete_.reset();
}
}
} // namespace pw::bluetooth::proxy