// 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 #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 = [¶ms](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