Loading system/gd/l2cap/Android.bp +4 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ filegroup { "classic_fixed_channel.cc", "classic_fixed_channel_manager.cc", "classic_fixed_channel_service.cc", "internal/classic_dynamic_channel_allocator.cc", "internal/classic_dynamic_channel_impl.cc", "internal/classic_fixed_channel_allocator.cc", "internal/classic_fixed_channel_impl.cc", "internal/classic_fixed_channel_service_manager_impl.cc", Loading @@ -21,6 +23,8 @@ filegroup { name: "BluetoothL2capTestSources", srcs: [ "l2cap_packet_test.cc", "internal/classic_dynamic_channel_allocator_test.cc", "internal/classic_dynamic_channel_impl_test.cc", "internal/classic_fixed_channel_allocator_test.cc", "internal/classic_fixed_channel_impl_test.cc", "internal/classic_fixed_channel_service_manager_test.cc", Loading system/gd/l2cap/internal/classic_dynamic_channel_allocator.cc 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * Copyright 2019 The Android Open Source Project * * 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 * * http://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 <unordered_map> #include "classic_dynamic_channel_allocator.h" #include "l2cap/cid.h" #include "l2cap/internal/classic_dynamic_channel_allocator.h" #include "l2cap/internal/classic_link.h" #include "l2cap/security_policy.h" #include "os/handler.h" #include "os/log.h" namespace bluetooth { namespace l2cap { namespace internal { std::shared_ptr<ClassicDynamicChannelImpl> ClassicDynamicChannelAllocator::AllocateChannel( Psm psm, Cid remote_cid, SecurityPolicy security_policy) { if (IsChannelAllocated((psm))) { LOG_INFO("Psm 0x%x for device %s is already in use", psm, link_->GetDevice().ToString().c_str()); return nullptr; } if (!IsPsmValid(psm)) { LOG_INFO("Psm 0x%x is invalid", psm); return nullptr; } if (used_remote_cid_.find(remote_cid) != used_remote_cid_.end()) { LOG_INFO("Remote cid 0x%x is used", remote_cid); return nullptr; } Cid cid = kFirstDynamicChannel; for (; cid <= kLastDynamicChannel; cid++) { LOG_INFO(); if (used_cid_.count(cid) == 0) break; } if (cid > kLastDynamicChannel) { LOG_WARN("All cid are used"); return nullptr; } auto elem = channels_.try_emplace( psm, std::make_shared<ClassicDynamicChannelImpl>(psm, cid, remote_cid, link_, l2cap_handler_)); ASSERT_LOG(elem.second, "Failed to create channel for psm 0x%x device %s", psm, link_->GetDevice().ToString().c_str()); ASSERT(elem.first->second != nullptr); used_cid_.insert(cid); used_remote_cid_.insert(remote_cid); return elem.first->second; } void ClassicDynamicChannelAllocator::FreeChannel(Psm psm) { ASSERT_LOG(IsChannelAllocated(psm), "Channel is not in use: psm %d, device %s", psm, link_->GetDevice().ToString().c_str()); channels_.erase(psm); } bool ClassicDynamicChannelAllocator::IsChannelAllocated(Psm psm) const { return channels_.find(psm) != channels_.end(); } std::shared_ptr<ClassicDynamicChannelImpl> ClassicDynamicChannelAllocator::FindChannel(Psm psm) { ASSERT_LOG(IsChannelAllocated(psm), "Channel is not in use: psm %d, device %s", psm, link_->GetDevice().ToString().c_str()); return channels_.find(psm)->second; } size_t ClassicDynamicChannelAllocator::NumberOfChannels() const { return channels_.size(); } void ClassicDynamicChannelAllocator::OnAclDisconnected(hci::ErrorCode reason) { for (auto& elem : channels_) { elem.second->OnClosed(reason); } } } // namespace internal } // namespace l2cap } // namespace bluetooth system/gd/l2cap/internal/classic_dynamic_channel_allocator.h 0 → 100644 +70 −0 Original line number Diff line number Diff line /* * Copyright 2019 The Android Open Source Project * * 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 * * http://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. */ #pragma once #include <set> #include <unordered_map> #include "l2cap/cid.h" #include "l2cap/internal/classic_dynamic_channel_impl.h" #include "l2cap/psm.h" #include "l2cap/security_policy.h" #include "os/handler.h" #include "os/log.h" namespace bluetooth { namespace l2cap { namespace internal { class ClassicLink; // Helper class for keeping channels in a Link. It allocates and frees Channel object, and supports querying whether a // channel is in use class ClassicDynamicChannelAllocator { public: ClassicDynamicChannelAllocator(ClassicLink* link, os::Handler* l2cap_handler) : link_(link), l2cap_handler_(l2cap_handler) { ASSERT(link_ != nullptr); ASSERT(l2cap_handler_ != nullptr); } // Allocates a channel. If psm is used, OR the remote cid already exists, return nullptr. // NOTE: The returned ClassicDynamicChannelImpl object is still owned by the channel allocator, NOT the client. std::shared_ptr<ClassicDynamicChannelImpl> AllocateChannel(Psm psm, Cid remote_cid, SecurityPolicy security_policy); // Frees a channel. If psm doesn't exist, it will crash void FreeChannel(Psm psm); bool IsChannelAllocated(Psm psm) const; std::shared_ptr<ClassicDynamicChannelImpl> FindChannel(Psm psm); size_t NumberOfChannels() const; void OnAclDisconnected(hci::ErrorCode hci_status); private: ClassicLink* link_; os::Handler* l2cap_handler_; std::unordered_map<Psm, std::shared_ptr<ClassicDynamicChannelImpl>> channels_; std::set<Cid> used_cid_; std::set<Cid> used_remote_cid_; }; } // namespace internal } // namespace l2cap } // namespace bluetooth system/gd/l2cap/internal/classic_dynamic_channel_allocator_test.cc 0 → 100644 +78 −0 Original line number Diff line number Diff line /* * Copyright 2019 The Android Open Source Project * * 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 * * http://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 "l2cap/internal/classic_dynamic_channel_allocator.h" #include "l2cap/internal/classic_link_mock.h" #include "l2cap/internal/parameter_provider_mock.h" #include <gmock/gmock.h> #include <gtest/gtest.h> namespace bluetooth { namespace l2cap { namespace internal { using testing::MockClassicLink; using testing::MockParameterProvider; using ::testing::Return; const hci::Address device{{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}}; class L2capClassicDynamicChannelAllocatorTest : public ::testing::Test { protected: void SetUp() override { thread_ = new os::Thread("test_thread", os::Thread::Priority::NORMAL); handler_ = new os::Handler(thread_); mock_parameter_provider_ = new MockParameterProvider(); mock_classic_link_ = new MockClassicLink(handler_, mock_parameter_provider_); EXPECT_CALL(*mock_classic_link_, GetDevice()).WillRepeatedly(Return(device)); channel_allocator_ = std::make_unique<ClassicDynamicChannelAllocator>(mock_classic_link_, handler_); } void TearDown() override { channel_allocator_.reset(); delete mock_classic_link_; delete mock_parameter_provider_; handler_->Clear(); delete handler_; delete thread_; } os::Thread* thread_{nullptr}; os::Handler* handler_{nullptr}; MockParameterProvider* mock_parameter_provider_{nullptr}; MockClassicLink* mock_classic_link_{nullptr}; std::unique_ptr<ClassicDynamicChannelAllocator> channel_allocator_; }; TEST_F(L2capClassicDynamicChannelAllocatorTest, precondition) { Psm psm = 0x03; EXPECT_FALSE(channel_allocator_->IsChannelAllocated(psm)); } TEST_F(L2capClassicDynamicChannelAllocatorTest, allocate_and_free_channel) { Psm psm = 0x03; Cid remote_cid = kFirstDynamicChannel; auto channel = channel_allocator_->AllocateChannel(psm, remote_cid, {}); EXPECT_TRUE(channel_allocator_->IsChannelAllocated(psm)); EXPECT_EQ(channel, channel_allocator_->FindChannel(psm)); ASSERT_NO_FATAL_FAILURE(channel_allocator_->FreeChannel(psm)); EXPECT_FALSE(channel_allocator_->IsChannelAllocated(psm)); } } // namespace internal } // namespace l2cap } // namespace bluetooth system/gd/l2cap/internal/classic_dynamic_channel_impl.cc 0 → 100644 +86 −0 Original line number Diff line number Diff line /* * Copyright 2019 The Android Open Source Project * * 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 * * http://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 <unordered_map> #include "l2cap/cid.h" #include "l2cap/internal/classic_dynamic_channel_impl.h" #include "l2cap/internal/classic_link.h" #include "l2cap/psm.h" #include "l2cap/security_policy.h" #include "os/handler.h" #include "os/log.h" namespace bluetooth { namespace l2cap { namespace internal { ClassicDynamicChannelImpl::ClassicDynamicChannelImpl(Psm psm, Cid cid, Cid remote_cid, ClassicLink* link, os::Handler* l2cap_handler) : psm_(psm), cid_(cid), remote_cid_(remote_cid), link_(link), l2cap_handler_(l2cap_handler), device_(link->GetDevice()) { ASSERT(IsPsmValid(psm_)); ASSERT(cid_ > 0); ASSERT(remote_cid_ > 0); ASSERT(link_ != nullptr); ASSERT(l2cap_handler_ != nullptr); } hci::Address ClassicDynamicChannelImpl::GetDevice() const { return device_; } void ClassicDynamicChannelImpl::RegisterOnCloseCallback(os::Handler* user_handler, ClassicDynamicChannel::OnCloseCallback on_close_callback) { ASSERT_LOG(user_handler_ == nullptr, "OnCloseCallback can only be registered once"); // If channel is already closed, call the callback immediately without saving it if (closed_) { user_handler->Post(common::BindOnce(std::move(on_close_callback), close_reason_)); return; } user_handler_ = user_handler; on_close_callback_ = std::move(on_close_callback); } void ClassicDynamicChannelImpl::Close() { // TODO: Implement it by sending signalling packet } void ClassicDynamicChannelImpl::OnClosed(hci::ErrorCode status) { ASSERT_LOG(!closed_, "Device %s Cid 0x%x closed twice, old status 0x%x, new status 0x%x", device_.ToString().c_str(), cid_, static_cast<int>(close_reason_), static_cast<int>(status)); closed_ = true; close_reason_ = status; link_ = nullptr; l2cap_handler_ = nullptr; if (user_handler_ == nullptr) { return; } // On close callback can only be called once user_handler_->Post(common::BindOnce(std::move(on_close_callback_), status)); user_handler_ = nullptr; on_close_callback_.Reset(); } std::string ClassicDynamicChannelImpl::ToString() { std::ostringstream ss; ss << "Device " << device_.ToString() << "Psm 0x" << std::hex << psm_ << " Cid 0x" << std::hex << cid_; return ss.str(); } } // namespace internal } // namespace l2cap } // namespace bluetooth Loading
system/gd/l2cap/Android.bp +4 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ filegroup { "classic_fixed_channel.cc", "classic_fixed_channel_manager.cc", "classic_fixed_channel_service.cc", "internal/classic_dynamic_channel_allocator.cc", "internal/classic_dynamic_channel_impl.cc", "internal/classic_fixed_channel_allocator.cc", "internal/classic_fixed_channel_impl.cc", "internal/classic_fixed_channel_service_manager_impl.cc", Loading @@ -21,6 +23,8 @@ filegroup { name: "BluetoothL2capTestSources", srcs: [ "l2cap_packet_test.cc", "internal/classic_dynamic_channel_allocator_test.cc", "internal/classic_dynamic_channel_impl_test.cc", "internal/classic_fixed_channel_allocator_test.cc", "internal/classic_fixed_channel_impl_test.cc", "internal/classic_fixed_channel_service_manager_test.cc", Loading
system/gd/l2cap/internal/classic_dynamic_channel_allocator.cc 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * Copyright 2019 The Android Open Source Project * * 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 * * http://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 <unordered_map> #include "classic_dynamic_channel_allocator.h" #include "l2cap/cid.h" #include "l2cap/internal/classic_dynamic_channel_allocator.h" #include "l2cap/internal/classic_link.h" #include "l2cap/security_policy.h" #include "os/handler.h" #include "os/log.h" namespace bluetooth { namespace l2cap { namespace internal { std::shared_ptr<ClassicDynamicChannelImpl> ClassicDynamicChannelAllocator::AllocateChannel( Psm psm, Cid remote_cid, SecurityPolicy security_policy) { if (IsChannelAllocated((psm))) { LOG_INFO("Psm 0x%x for device %s is already in use", psm, link_->GetDevice().ToString().c_str()); return nullptr; } if (!IsPsmValid(psm)) { LOG_INFO("Psm 0x%x is invalid", psm); return nullptr; } if (used_remote_cid_.find(remote_cid) != used_remote_cid_.end()) { LOG_INFO("Remote cid 0x%x is used", remote_cid); return nullptr; } Cid cid = kFirstDynamicChannel; for (; cid <= kLastDynamicChannel; cid++) { LOG_INFO(); if (used_cid_.count(cid) == 0) break; } if (cid > kLastDynamicChannel) { LOG_WARN("All cid are used"); return nullptr; } auto elem = channels_.try_emplace( psm, std::make_shared<ClassicDynamicChannelImpl>(psm, cid, remote_cid, link_, l2cap_handler_)); ASSERT_LOG(elem.second, "Failed to create channel for psm 0x%x device %s", psm, link_->GetDevice().ToString().c_str()); ASSERT(elem.first->second != nullptr); used_cid_.insert(cid); used_remote_cid_.insert(remote_cid); return elem.first->second; } void ClassicDynamicChannelAllocator::FreeChannel(Psm psm) { ASSERT_LOG(IsChannelAllocated(psm), "Channel is not in use: psm %d, device %s", psm, link_->GetDevice().ToString().c_str()); channels_.erase(psm); } bool ClassicDynamicChannelAllocator::IsChannelAllocated(Psm psm) const { return channels_.find(psm) != channels_.end(); } std::shared_ptr<ClassicDynamicChannelImpl> ClassicDynamicChannelAllocator::FindChannel(Psm psm) { ASSERT_LOG(IsChannelAllocated(psm), "Channel is not in use: psm %d, device %s", psm, link_->GetDevice().ToString().c_str()); return channels_.find(psm)->second; } size_t ClassicDynamicChannelAllocator::NumberOfChannels() const { return channels_.size(); } void ClassicDynamicChannelAllocator::OnAclDisconnected(hci::ErrorCode reason) { for (auto& elem : channels_) { elem.second->OnClosed(reason); } } } // namespace internal } // namespace l2cap } // namespace bluetooth
system/gd/l2cap/internal/classic_dynamic_channel_allocator.h 0 → 100644 +70 −0 Original line number Diff line number Diff line /* * Copyright 2019 The Android Open Source Project * * 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 * * http://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. */ #pragma once #include <set> #include <unordered_map> #include "l2cap/cid.h" #include "l2cap/internal/classic_dynamic_channel_impl.h" #include "l2cap/psm.h" #include "l2cap/security_policy.h" #include "os/handler.h" #include "os/log.h" namespace bluetooth { namespace l2cap { namespace internal { class ClassicLink; // Helper class for keeping channels in a Link. It allocates and frees Channel object, and supports querying whether a // channel is in use class ClassicDynamicChannelAllocator { public: ClassicDynamicChannelAllocator(ClassicLink* link, os::Handler* l2cap_handler) : link_(link), l2cap_handler_(l2cap_handler) { ASSERT(link_ != nullptr); ASSERT(l2cap_handler_ != nullptr); } // Allocates a channel. If psm is used, OR the remote cid already exists, return nullptr. // NOTE: The returned ClassicDynamicChannelImpl object is still owned by the channel allocator, NOT the client. std::shared_ptr<ClassicDynamicChannelImpl> AllocateChannel(Psm psm, Cid remote_cid, SecurityPolicy security_policy); // Frees a channel. If psm doesn't exist, it will crash void FreeChannel(Psm psm); bool IsChannelAllocated(Psm psm) const; std::shared_ptr<ClassicDynamicChannelImpl> FindChannel(Psm psm); size_t NumberOfChannels() const; void OnAclDisconnected(hci::ErrorCode hci_status); private: ClassicLink* link_; os::Handler* l2cap_handler_; std::unordered_map<Psm, std::shared_ptr<ClassicDynamicChannelImpl>> channels_; std::set<Cid> used_cid_; std::set<Cid> used_remote_cid_; }; } // namespace internal } // namespace l2cap } // namespace bluetooth
system/gd/l2cap/internal/classic_dynamic_channel_allocator_test.cc 0 → 100644 +78 −0 Original line number Diff line number Diff line /* * Copyright 2019 The Android Open Source Project * * 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 * * http://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 "l2cap/internal/classic_dynamic_channel_allocator.h" #include "l2cap/internal/classic_link_mock.h" #include "l2cap/internal/parameter_provider_mock.h" #include <gmock/gmock.h> #include <gtest/gtest.h> namespace bluetooth { namespace l2cap { namespace internal { using testing::MockClassicLink; using testing::MockParameterProvider; using ::testing::Return; const hci::Address device{{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}}; class L2capClassicDynamicChannelAllocatorTest : public ::testing::Test { protected: void SetUp() override { thread_ = new os::Thread("test_thread", os::Thread::Priority::NORMAL); handler_ = new os::Handler(thread_); mock_parameter_provider_ = new MockParameterProvider(); mock_classic_link_ = new MockClassicLink(handler_, mock_parameter_provider_); EXPECT_CALL(*mock_classic_link_, GetDevice()).WillRepeatedly(Return(device)); channel_allocator_ = std::make_unique<ClassicDynamicChannelAllocator>(mock_classic_link_, handler_); } void TearDown() override { channel_allocator_.reset(); delete mock_classic_link_; delete mock_parameter_provider_; handler_->Clear(); delete handler_; delete thread_; } os::Thread* thread_{nullptr}; os::Handler* handler_{nullptr}; MockParameterProvider* mock_parameter_provider_{nullptr}; MockClassicLink* mock_classic_link_{nullptr}; std::unique_ptr<ClassicDynamicChannelAllocator> channel_allocator_; }; TEST_F(L2capClassicDynamicChannelAllocatorTest, precondition) { Psm psm = 0x03; EXPECT_FALSE(channel_allocator_->IsChannelAllocated(psm)); } TEST_F(L2capClassicDynamicChannelAllocatorTest, allocate_and_free_channel) { Psm psm = 0x03; Cid remote_cid = kFirstDynamicChannel; auto channel = channel_allocator_->AllocateChannel(psm, remote_cid, {}); EXPECT_TRUE(channel_allocator_->IsChannelAllocated(psm)); EXPECT_EQ(channel, channel_allocator_->FindChannel(psm)); ASSERT_NO_FATAL_FAILURE(channel_allocator_->FreeChannel(psm)); EXPECT_FALSE(channel_allocator_->IsChannelAllocated(psm)); } } // namespace internal } // namespace l2cap } // namespace bluetooth
system/gd/l2cap/internal/classic_dynamic_channel_impl.cc 0 → 100644 +86 −0 Original line number Diff line number Diff line /* * Copyright 2019 The Android Open Source Project * * 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 * * http://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 <unordered_map> #include "l2cap/cid.h" #include "l2cap/internal/classic_dynamic_channel_impl.h" #include "l2cap/internal/classic_link.h" #include "l2cap/psm.h" #include "l2cap/security_policy.h" #include "os/handler.h" #include "os/log.h" namespace bluetooth { namespace l2cap { namespace internal { ClassicDynamicChannelImpl::ClassicDynamicChannelImpl(Psm psm, Cid cid, Cid remote_cid, ClassicLink* link, os::Handler* l2cap_handler) : psm_(psm), cid_(cid), remote_cid_(remote_cid), link_(link), l2cap_handler_(l2cap_handler), device_(link->GetDevice()) { ASSERT(IsPsmValid(psm_)); ASSERT(cid_ > 0); ASSERT(remote_cid_ > 0); ASSERT(link_ != nullptr); ASSERT(l2cap_handler_ != nullptr); } hci::Address ClassicDynamicChannelImpl::GetDevice() const { return device_; } void ClassicDynamicChannelImpl::RegisterOnCloseCallback(os::Handler* user_handler, ClassicDynamicChannel::OnCloseCallback on_close_callback) { ASSERT_LOG(user_handler_ == nullptr, "OnCloseCallback can only be registered once"); // If channel is already closed, call the callback immediately without saving it if (closed_) { user_handler->Post(common::BindOnce(std::move(on_close_callback), close_reason_)); return; } user_handler_ = user_handler; on_close_callback_ = std::move(on_close_callback); } void ClassicDynamicChannelImpl::Close() { // TODO: Implement it by sending signalling packet } void ClassicDynamicChannelImpl::OnClosed(hci::ErrorCode status) { ASSERT_LOG(!closed_, "Device %s Cid 0x%x closed twice, old status 0x%x, new status 0x%x", device_.ToString().c_str(), cid_, static_cast<int>(close_reason_), static_cast<int>(status)); closed_ = true; close_reason_ = status; link_ = nullptr; l2cap_handler_ = nullptr; if (user_handler_ == nullptr) { return; } // On close callback can only be called once user_handler_->Post(common::BindOnce(std::move(on_close_callback_), status)); user_handler_ = nullptr; on_close_callback_.Reset(); } std::string ClassicDynamicChannelImpl::ToString() { std::ostringstream ss; ss << "Device " << device_.ToString() << "Psm 0x" << std::hex << psm_ << " Cid 0x" << std::hex << cid_; return ss.str(); } } // namespace internal } // namespace l2cap } // namespace bluetooth