"classic/internal/fixed_channel_service_manager_impl.cc",
"classic/internal/link.cc",
"classic/internal/link_manager.cc",
+ "classic/internal/signalling_manager.cc",
"classic/l2cap_classic_module.cc",
"internal/scheduler_fifo.cc",
"le/internal/fixed_channel_impl.cc",
"classic/internal/fixed_channel_impl_test.cc",
"classic/internal/fixed_channel_service_manager_test.cc",
"classic/internal/link_manager_test.cc",
+ "classic/internal/signalling_manager_test.cc",
"internal/fixed_channel_allocator_test.cc",
"internal/scheduler_fifo_test.cc",
"l2cap_packet_test.cc",
rpc FetchConnectionComplete(facade.EventStreamRequest) returns (stream ConnectionCompleteEvent) {}
rpc DisconnectLink(DisconnectLinkRequest) returns (google.protobuf.Empty) {}
rpc SendConnectionRequest(ConnectionRequest) returns (google.protobuf.Empty) {}
+ rpc SendDisconnectionRequest(DisconnectionRequest) returns (google.protobuf.Empty) {}
}
message L2capPacket {
uint32 dcid = 2;
uint32 scid = 3;
}
+
+message DisconnectionRequest {
+ facade.BluetoothAddress remote = 1;
+ uint32 dcid = 2;
+ uint32 scid = 3;
+}
\ No newline at end of file
return ::grpc::Status::OK;
}
+ ::grpc::Status SendConnectionRequest(::grpc::ServerContext* context, const cert::ConnectionRequest* request,
+ ::google::protobuf::Empty* response) override {
+ auto builder = ConnectionRequestBuilder::Create(1, 1, 101);
+ auto l2cap_builder = BasicFrameBuilder::Create(1, std::move(builder));
+ outgoing_packet_queue_.push(std::move(l2cap_builder));
+ if (outgoing_packet_queue_.size() == 1) {
+ acl_connection_->GetAclQueueEnd()->RegisterEnqueue(
+ handler_, common::Bind(&L2capModuleCertService::enqueue_packet_to_acl, common::Unretained(this)));
+ }
+
+ return ::grpc::Status::OK;
+ }
+
+ ::grpc::Status SendDisconnectionRequest(::grpc::ServerContext* context, const cert::DisconnectionRequest* request,
+ ::google::protobuf::Empty* response) override {
+ auto builder = DisconnectionRequestBuilder::Create(3, 0x40, 101);
+ auto l2cap_builder = BasicFrameBuilder::Create(1, std::move(builder));
+ outgoing_packet_queue_.push(std::move(l2cap_builder));
+ if (outgoing_packet_queue_.size() == 1) {
+ acl_connection_->GetAclQueueEnd()->RegisterEnqueue(
+ handler_, common::Bind(&L2capModuleCertService::enqueue_packet_to_acl, common::Unretained(this)));
+ }
+
+ return ::grpc::Status::OK;
+ }
+
std::unique_ptr<packet::BasePacketBuilder> enqueue_packet_to_acl() {
auto basic_frame_builder = std::move(outgoing_packet_queue_.front());
outgoing_packet_queue_.pop();
l2cap_stream_.OnIncomingEvent(l2cap_packet);
}
- std::queue<std::unique_ptr<BasicFrameBuilder>> outgoing_packet_queue_;
+ std::queue<std::unique_ptr<BasePacketBuilder>> outgoing_packet_queue_;
::bluetooth::os::Handler* handler_;
hci::AclManager* acl_manager_;
std::unique_ptr<hci::AclConnection> acl_connection_;
from facade import rootservice_pb2 as facade_rootservice_pb2
from google.protobuf import empty_pb2
from l2cap.classic import facade_pb2 as l2cap_facade_pb2
+from l2cap.classic.cert import api_pb2 as l2cap_cert_pb2
+
+import time
class SimpleL2capTest(GdBaseTestClass):
def setup_test(self):
def test_connect_and_send_data(self):
self.device_under_test.l2cap.RegisterChannel(l2cap_facade_pb2.RegisterChannelRequest(channel=2))
+ self.device_under_test.l2cap.SetDynamicChannel(l2cap_facade_pb2.SetEnableDynamicChannelRequest(psm=0x01))
dut_packet_stream = self.device_under_test.l2cap.packet_stream
cert_packet_stream = self.cert_device.l2cap.packet_stream
cert_connection_stream = self.cert_device.l2cap.connection_complete_stream
lambda device: device.remote == self.cert_address
)
dut_connection_stream.unsubscribe()
+
+ self.cert_device.l2cap.SendConnectionRequest(l2cap_cert_pb2.ConnectionRequest())
+ time.sleep(1)
+
dut_packet_stream.subscribe()
cert_packet_stream.subscribe()
self.cert_device.l2cap.SendL2capPacket(l2cap_facade_pb2.L2capPacket(channel=2, payload=b"abc"))
cert_packet_stream.assert_event_occurs(
lambda packet: b"123" in packet.payload
)
+
+ self.cert_device.l2cap.SendL2capPacket(l2cap_facade_pb2.L2capPacket(channel=64, payload=b"123"))
+ dut_packet_stream.assert_event_occurs(
+ lambda packet: b"123" in packet.payload
+ )
+
+ self.device_under_test.l2cap.SendDynamicChannelPacket(l2cap_facade_pb2.DynamicChannelPacket(psm=1, payload=b'abc'))
+ cert_packet_stream.assert_event_occurs(
+ lambda packet: b"abc" in packet.payload
+ )
+
+ self.cert_device.l2cap.SendDisconnectionRequest(l2cap_cert_pb2.DisconnectionRequest())
+ time.sleep(1)
dut_packet_stream.unsubscribe()
cert_packet_stream.unsubscribe()
bool DynamicChannelManager::ConnectChannel(hci::Address device, Psm psm, OnConnectionOpenCallback on_connection_open,
OnConnectionFailureCallback on_fail_callback, os::Handler* handler) {
- // TODO impl me when there is no link, and when there is link
- return false;
+ internal::LinkManager::PendingDynamicChannelConnection pending_dynamic_channel_connection{
+ .on_open_callback_ = std::move(on_connection_open),
+ .on_fail_callback_ = std::move(on_fail_callback),
+ .handler_ = handler};
+ l2cap_layer_handler_->Post(common::BindOnce(&internal::LinkManager::ConnectDynamicChannelServices,
+ common::Unretained(link_manager_), device,
+ std::move(pending_dynamic_channel_connection), psm));
+
+ return true;
}
bool DynamicChannelManager::RegisterService(Psm psm, const SecurityPolicy& security_policy,
::bluetooth::grpc::GrpcEventStream<ConnectionCompleteEvent, ConnectionCompleteEvent> connection_complete_stream_{
&connection_complete_callback_};
- ::grpc::Status FetchConnectionComplete(
- ::grpc::ServerContext* context, const ::bluetooth::facade::EventStreamRequest* request,
- ::grpc::ServerWriter<::bluetooth::l2cap::classic::ConnectionCompleteEvent>* writer) override {
+ ::grpc::Status FetchConnectionComplete(::grpc::ServerContext* context,
+ const ::bluetooth::facade::EventStreamRequest* request,
+ ::grpc::ServerWriter<classic::ConnectionCompleteEvent>* writer) override {
return connection_complete_stream_.HandleRequest(context, request, writer);
}
return ::grpc::Status::OK;
}
- ::grpc::Status SendL2capPacket(::grpc::ServerContext* context,
- const ::bluetooth::l2cap::classic::L2capPacket* request,
- ::bluetooth::l2cap::classic::SendL2capPacketResult* response) override {
- if (connection_less_channel_helper_map_.find(request->channel()) == connection_less_channel_helper_map_.end()) {
+ ::grpc::Status SendL2capPacket(::grpc::ServerContext* context, const classic::L2capPacket* request,
+ SendL2capPacketResult* response) override {
+ if (fixed_channel_helper_map_.find(request->channel()) == fixed_channel_helper_map_.end()) {
return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Channel not registered");
}
std::vector<uint8_t> packet(request->payload().begin(), request->payload().end());
- connection_less_channel_helper_map_[request->channel()]->SendPacket(packet);
- response->set_result_type(::bluetooth::l2cap::classic::SendL2capPacketResultType::OK);
+ fixed_channel_helper_map_[request->channel()]->SendPacket(packet);
+ response->set_result_type(SendL2capPacketResultType::OK);
+ return ::grpc::Status::OK;
+ }
+
+ ::grpc::Status SendDynamicChannelPacket(::grpc::ServerContext* context, const DynamicChannelPacket* request,
+ ::google::protobuf::Empty* response) override {
+ if (dynamic_channel_helper_map_.find(request->psm()) == dynamic_channel_helper_map_.end()) {
+ return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Psm not registered");
+ }
+ std::vector<uint8_t> packet(request->payload().begin(), request->payload().end());
+ dynamic_channel_helper_map_[request->psm()]->SendPacket(packet);
return ::grpc::Status::OK;
}
::grpc::Status FetchL2capData(::grpc::ServerContext* context, const ::bluetooth::facade::EventStreamRequest* request,
- ::grpc::ServerWriter<::bluetooth::l2cap::classic::L2capPacket>* writer) override {
+ ::grpc::ServerWriter<classic::L2capPacket>* writer) override {
return l2cap_stream_.HandleRequest(context, request, writer);
}
- ::grpc::Status RegisterChannel(::grpc::ServerContext* context,
- const ::bluetooth::l2cap::classic::RegisterChannelRequest* request,
+ ::grpc::Status RegisterChannel(::grpc::ServerContext* context, const classic::RegisterChannelRequest* request,
::google::protobuf::Empty* response) override {
- if (connection_less_channel_helper_map_.find(request->channel()) != connection_less_channel_helper_map_.end()) {
+ if (fixed_channel_helper_map_.find(request->channel()) != fixed_channel_helper_map_.end()) {
return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Already registered");
}
- connection_less_channel_helper_map_.emplace(
- request->channel(),
- std::make_unique<L2capFixedChannelHelper>(this, l2cap_layer_, facade_handler_, request->channel()));
+ fixed_channel_helper_map_.emplace(request->channel(), std::make_unique<L2capFixedChannelHelper>(
+ this, l2cap_layer_, facade_handler_, request->channel()));
return ::grpc::Status::OK;
}
Cid cid_;
};
- l2cap::classic::L2capClassicModule* l2cap_layer_;
+ ::grpc::Status SetDynamicChannel(::grpc::ServerContext* context, const SetEnableDynamicChannelRequest* request,
+ google::protobuf::Empty* response) override {
+ dynamic_channel_helper_map_.emplace(request->psm(), std::make_unique<L2capDynamicChannelHelper>(
+ this, l2cap_layer_, facade_handler_, request->psm()));
+ return ::grpc::Status::OK;
+ }
+
+ class L2capDynamicChannelHelper {
+ public:
+ L2capDynamicChannelHelper(L2capModuleFacadeService* service, L2capClassicModule* l2cap_layer, os::Handler* handler,
+ Psm psm)
+ : facade_service_(service), l2cap_layer_(l2cap_layer), handler_(handler), psm_(psm) {
+ dynamic_channel_manager_ = l2cap_layer_->GetDynamicChannelManager();
+ dynamic_channel_manager_->RegisterService(
+ psm, {},
+ common::BindOnce(&L2capDynamicChannelHelper::on_l2cap_service_registration_complete,
+ common::Unretained(this)),
+ common::Bind(&L2capDynamicChannelHelper::on_connection_open, common::Unretained(this)), handler_);
+ }
+
+ void on_l2cap_service_registration_complete(DynamicChannelManager::RegistrationResult registration_result,
+ std::unique_ptr<DynamicChannelService> service) {}
+
+ void on_connection_open(std::unique_ptr<DynamicChannel> channel) {
+ ConnectionCompleteEvent event;
+ event.mutable_remote()->set_address(channel->GetDevice().ToString());
+ facade_service_->connection_complete_stream_.OnIncomingEvent(event);
+ channel_ = std::move(channel);
+ }
+
+ void on_incoming_packet() {
+ auto packet = channel_->GetQueueUpEnd()->TryDequeue();
+ std::string data = std::string(packet->begin(), packet->end());
+ L2capPacket l2cap_data;
+ // l2cap_data.set_channel(cid_);
+ l2cap_data.set_payload(data);
+ facade_service_->l2cap_stream_.OnIncomingEvent(l2cap_data);
+ }
+
+ void SendPacket(std::vector<uint8_t> packet) {
+ if (channel_ == nullptr) {
+ LOG_WARN("Channel is not open");
+ return;
+ }
+ channel_->GetQueueUpEnd()->RegisterEnqueue(
+ handler_, common::Bind(&L2capDynamicChannelHelper::enqueue_callback, common::Unretained(this), packet));
+ }
+
+ std::unique_ptr<packet::BasePacketBuilder> enqueue_callback(std::vector<uint8_t> packet) {
+ auto packet_one = std::make_unique<packet::RawBuilder>();
+ packet_one->AddOctets(packet);
+ channel_->GetQueueUpEnd()->UnregisterEnqueue();
+ return packet_one;
+ };
+
+ L2capModuleFacadeService* facade_service_;
+ L2capClassicModule* l2cap_layer_;
+ os::Handler* handler_;
+ std::unique_ptr<DynamicChannelManager> dynamic_channel_manager_;
+ std::unique_ptr<DynamicChannelService> service_;
+ std::unique_ptr<DynamicChannel> channel_ = nullptr;
+ Psm psm_;
+ };
+
+ L2capClassicModule* l2cap_layer_;
::bluetooth::os::Handler* facade_handler_;
- std::map<Cid, std::unique_ptr<L2capFixedChannelHelper>> connection_less_channel_helper_map_;
+ std::map<Cid, std::unique_ptr<L2capFixedChannelHelper>> fixed_channel_helper_map_;
+ std::map<Psm, std::unique_ptr<L2capDynamicChannelHelper>> dynamic_channel_helper_map_;
class L2capStreamCallback : public ::bluetooth::grpc::GrpcEventStreamCallback<L2capPacket, L2capPacket> {
public:
L2capStreamCallback(L2capModuleFacadeService* service) : service_(service) {}
~L2capStreamCallback() {
- for (const auto& connection : service_->connection_less_channel_helper_map_) {
- if (subscribed_[connection.first] && connection.second->channel_ != nullptr) {
+ for (const auto& connection : service_->fixed_channel_helper_map_) {
+ if (subscribed_fixed_channel_[connection.first] && connection.second->channel_ != nullptr) {
connection.second->channel_->GetQueueUpEnd()->UnregisterDequeue();
- subscribed_[connection.first] = false;
+ subscribed_fixed_channel_[connection.first] = false;
+ }
+ }
+
+ for (const auto& connection : service_->dynamic_channel_helper_map_) {
+ if (subscribed_dynamic_channel_[connection.first] && connection.second->channel_ != nullptr) {
+ connection.second->channel_->GetQueueUpEnd()->UnregisterDequeue();
+ subscribed_dynamic_channel_[connection.first] = false;
}
}
}
void OnSubscribe() override {
- for (auto& connection : service_->connection_less_channel_helper_map_) {
- if (!subscribed_[connection.first] && connection.second->channel_ != nullptr) {
+ for (auto& connection : service_->fixed_channel_helper_map_) {
+ if (!subscribed_fixed_channel_[connection.first] && connection.second->channel_ != nullptr) {
connection.second->channel_->GetQueueUpEnd()->RegisterDequeue(
service_->facade_handler_,
common::Bind(&L2capFixedChannelHelper::on_incoming_packet, common::Unretained(connection.second.get())));
- subscribed_[connection.first] = true;
+ subscribed_fixed_channel_[connection.first] = true;
+ }
+ }
+
+ for (auto& connection : service_->dynamic_channel_helper_map_) {
+ if (!subscribed_dynamic_channel_[connection.first] && connection.second->channel_ != nullptr) {
+ connection.second->channel_->GetQueueUpEnd()->RegisterDequeue(
+ service_->facade_handler_, common::Bind(&L2capDynamicChannelHelper::on_incoming_packet,
+ common::Unretained(connection.second.get())));
+ subscribed_dynamic_channel_[connection.first] = true;
}
}
}
void OnUnsubscribe() override {
- for (const auto& connection : service_->connection_less_channel_helper_map_) {
- if (subscribed_[connection.first] && connection.second->channel_ != nullptr) {
+ for (const auto& connection : service_->fixed_channel_helper_map_) {
+ if (subscribed_fixed_channel_[connection.first] && connection.second->channel_ != nullptr) {
+ connection.second->channel_->GetQueueUpEnd()->UnregisterDequeue();
+ subscribed_fixed_channel_[connection.first] = false;
+ }
+ }
+
+ for (const auto& connection : service_->dynamic_channel_helper_map_) {
+ if (subscribed_dynamic_channel_[connection.first] && connection.second->channel_ != nullptr) {
connection.second->channel_->GetQueueUpEnd()->UnregisterDequeue();
- subscribed_[connection.first] = false;
+ subscribed_dynamic_channel_[connection.first] = false;
}
}
}
}
L2capModuleFacadeService* service_;
- std::map<Cid, bool> subscribed_;
+ std::map<Cid, bool> subscribed_fixed_channel_;
+ std::map<Psm, bool> subscribed_dynamic_channel_;
} l2cap_stream_callback_{this};
::bluetooth::grpc::GrpcEventStream<L2capPacket, L2capPacket> l2cap_stream_{&l2cap_stream_callback_};
rpc ConfigureChannel(ConfigureChannelRequest) returns (google.protobuf.Empty) {}
rpc SendL2capPacket(L2capPacket) returns (SendL2capPacketResult) {}
rpc FetchL2capData(facade.EventStreamRequest) returns (stream L2capPacket) {}
+ rpc RegisterDynamicChannel(RegisterDynamicChannelRequest) returns (google.protobuf.Empty) {}
+ rpc SetDynamicChannel(SetEnableDynamicChannelRequest) returns (google.protobuf.Empty) {}
+ rpc SendDynamicChannelPacket(DynamicChannelPacket) returns (google.protobuf.Empty) {}
}
message RegisterChannelRequest {
uint32 channel = 2;
bytes payload = 3;
}
+
+message RegisterDynamicChannelRequest {
+ uint32 psm = 1;
+}
+
+message SetEnableDynamicChannelRequest {
+ uint32 psm = 1;
+ bool enable = 2;
+}
+
+message DynamicChannelPacket {
+ facade.BluetoothAddress remote = 1;
+ uint32 psm = 2;
+ bytes payload = 3;
+}
std::shared_ptr<DynamicChannelImpl> DynamicChannelAllocator::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;
- }
+ ASSERT_LOG(!IsPsmUsed((psm)), "Psm 0x%x for device %s is already in use", psm, link_->GetDevice().ToString().c_str());
+ ASSERT_LOG(IsPsmValid(psm), "Psm 0x%x is invalid", psm);
+
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 (channels_.find(cid) == channels_.end()) break;
}
if (cid > kLastDynamicChannel) {
LOG_WARN("All cid are used");
return nullptr;
}
auto elem =
- channels_.try_emplace(psm, std::make_shared<DynamicChannelImpl>(psm, cid, remote_cid, link_, l2cap_handler_));
+ channels_.try_emplace(cid, std::make_shared<DynamicChannelImpl>(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 DynamicChannelAllocator::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);
+void DynamicChannelAllocator::FreeChannel(Cid cid) {
+ auto channel = FindChannelByCid(cid);
+ if (channel == nullptr) {
+ LOG_INFO("Channel is not in use: psm %d, device %s", cid, link_->GetDevice().ToString().c_str());
+ return;
+ }
+ used_remote_cid_.erase(channel->GetRemoteCid());
+ channels_.erase(cid);
}
-bool DynamicChannelAllocator::IsChannelAllocated(Psm psm) const {
- return channels_.find(psm) != channels_.end();
+bool DynamicChannelAllocator::IsPsmUsed(Psm psm) const {
+ for (const auto& channel : channels_) {
+ if (channel.second->GetPsm() == psm) {
+ return true;
+ }
+ }
+ return false;
}
-std::shared_ptr<DynamicChannelImpl> DynamicChannelAllocator::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;
+std::shared_ptr<DynamicChannelImpl> DynamicChannelAllocator::FindChannelByCid(Cid cid) {
+ if (channels_.find(cid) == channels_.end()) {
+ LOG_WARN("Can't find cid %d", cid);
+ return nullptr;
+ }
+ return channels_.find(cid)->second;
}
size_t DynamicChannelAllocator::NumberOfChannels() const {
#pragma once
-#include <set>
#include <unordered_map>
+#include <unordered_set>
#include "l2cap/cid.h"
#include "l2cap/classic/internal/dynamic_channel_impl.h"
std::shared_ptr<DynamicChannelImpl> AllocateChannel(Psm psm, Cid remote_cid, SecurityPolicy security_policy);
// Frees a channel. If psm doesn't exist, it will crash
- void FreeChannel(Psm psm);
+ void FreeChannel(Cid cid);
- bool IsChannelAllocated(Psm psm) const;
+ bool IsPsmUsed(Psm psm) const;
- std::shared_ptr<DynamicChannelImpl> FindChannel(Psm psm);
+ std::shared_ptr<DynamicChannelImpl> FindChannelByCid(Cid cid);
size_t NumberOfChannels() const;
private:
Link* link_;
os::Handler* l2cap_handler_;
- std::unordered_map<Psm, std::shared_ptr<DynamicChannelImpl>> channels_;
- std::set<Cid> used_cid_;
- std::set<Cid> used_remote_cid_;
+ std::unordered_map<Cid, std::shared_ptr<DynamicChannelImpl>> channels_;
+ std::unordered_set<Cid> used_remote_cid_;
};
} // namespace internal
TEST_F(L2capClassicDynamicChannelAllocatorTest, precondition) {
Psm psm = 0x03;
- EXPECT_FALSE(channel_allocator_->IsChannelAllocated(psm));
+ EXPECT_FALSE(channel_allocator_->IsPsmUsed(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));
+ Cid local_cid = channel->GetCid();
+ EXPECT_TRUE(channel_allocator_->IsPsmUsed(psm));
+ EXPECT_EQ(channel, channel_allocator_->FindChannelByCid(local_cid));
+ ASSERT_NO_FATAL_FAILURE(channel_allocator_->FreeChannel(local_cid));
+ EXPECT_FALSE(channel_allocator_->IsPsmUsed(psm));
}
} // namespace internal
}
void DynamicChannelImpl::Close() {
- // TODO: Implement it by sending signalling packet
+ link_->SendDisconnectionRequest(cid_, remote_cid_);
}
void DynamicChannelImpl::OnClosed(hci::ErrorCode status) {
std::string DynamicChannelImpl::ToString() {
std::ostringstream ss;
- ss << "Device " << device_.ToString() << "Psm 0x" << std::hex << psm_ << " Cid 0x" << std::hex << cid_;
+ ss << "Device " << device_ << "Psm 0x" << std::hex << psm_ << " Cid 0x" << std::hex << cid_;
return ss.str();
}
return cid_;
}
+ virtual Cid GetRemoteCid() const {
+ return remote_cid_;
+ }
+
+ virtual Psm GetPsm() const {
+ return psm_;
+ }
+
private:
const Psm psm_;
const Cid cid_;
virtual DynamicChannelServiceImpl* GetService(Psm psm);
virtual std::vector<std::pair<Psm, DynamicChannelServiceImpl*>> GetRegisteredServices();
- //
private:
os::Handler* l2cap_layer_handler_ = nullptr;
std::unordered_map<Psm, DynamicChannelServiceImpl> service_map_;
--- /dev/null
+/*
+ * 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 "l2cap/classic/internal/dynamic_channel_impl.h"
+#include "l2cap/classic/internal/dynamic_channel_service_manager_impl.h"
+
+#include <gmock/gmock.h>
+
+// Unit test interfaces
+namespace bluetooth {
+namespace l2cap {
+namespace classic {
+namespace internal {
+namespace testing {
+
+class MockDynamicChannelServiceManagerImpl : public DynamicChannelServiceManagerImpl {
+ public:
+ MockDynamicChannelServiceManagerImpl() : DynamicChannelServiceManagerImpl(nullptr) {}
+ MOCK_METHOD(void, Register, (Psm psm, DynamicChannelServiceImpl::PendingRegistration pending_registration),
+ (override));
+ MOCK_METHOD(void, Unregister, (Psm psm, DynamicChannelService::OnUnregisteredCallback callback, os::Handler* handler),
+ (override));
+ MOCK_METHOD(bool, IsServiceRegistered, (Psm psm), (const, override));
+ MOCK_METHOD(DynamicChannelServiceImpl*, GetService, (Psm psm), (override));
+ MOCK_METHOD((std::vector<std::pair<Psm, DynamicChannelServiceImpl*>>), GetRegisteredServices, (), (override));
+};
+
+} // namespace testing
+} // namespace internal
+} // namespace classic
+} // namespace l2cap
+} // namespace bluetooth
\ No newline at end of file
FixedChannelImpl::FixedChannelImpl(Cid cid, Link* link, os::Handler* l2cap_handler)
: cid_(cid), device_(link->GetDevice()), link_(link), l2cap_handler_(l2cap_handler) {
ASSERT_LOG(cid_ >= kFirstFixedChannel && cid_ <= kLastFixedChannel, "Invalid cid: %d", cid_);
- ASSERT(!device_.IsEmpty());
ASSERT(link_ != nullptr);
ASSERT(l2cap_handler_ != nullptr);
}
return results;
}
+namespace {
+constexpr uint64_t kSignallingChannelMask = 0x02;
+constexpr uint64_t kConnectionlessReceptionMask = 0x04;
+constexpr uint64_t kBrEdrSecurityManager = 0x80;
+} // namespace
+
+uint64_t FixedChannelServiceManagerImpl::GetSupportedFixedChannelMask() {
+ uint64_t result = 0;
+ result |= kSignallingChannelMask; // Signalling channel is mandatory
+ for (const auto& elem : service_map_) {
+ Cid cid = elem.first;
+ switch (cid) {
+ case kConnectionlessCid:
+ result |= kConnectionlessReceptionMask;
+ continue;
+ case kSmpBrCid:
+ result |= kBrEdrSecurityManager;
+ continue;
+ default:
+ LOG_WARN("Unknown fixed channel is registered: 0x%x", cid);
+ continue;
+ }
+ }
+ return result;
+}
} // namespace internal
} // namespace classic
} // namespace l2cap
virtual bool IsServiceRegistered(Cid cid) const;
virtual FixedChannelServiceImpl* GetService(Cid cid);
virtual std::vector<std::pair<Cid, FixedChannelServiceImpl*>> GetRegisteredServices();
+ virtual uint64_t GetSupportedFixedChannelMask();
private:
os::Handler* l2cap_layer_handler_ = nullptr;
Link::Link(os::Handler* l2cap_handler, std::unique_ptr<hci::AclConnection> acl_connection,
std::unique_ptr<l2cap::internal::Scheduler> scheduler,
- l2cap::internal::ParameterProvider* parameter_provider)
+ l2cap::internal::ParameterProvider* parameter_provider,
+ DynamicChannelServiceManagerImpl* dynamic_service_manager,
+ FixedChannelServiceManagerImpl* fixed_service_manager)
: l2cap_handler_(l2cap_handler), acl_connection_(std::move(acl_connection)), scheduler_(std::move(scheduler)),
- parameter_provider_(parameter_provider) {
+ parameter_provider_(parameter_provider), dynamic_service_manager_(dynamic_service_manager),
+ fixed_service_manager_(fixed_service_manager),
+ signalling_manager_(l2cap_handler_, this, dynamic_service_manager_, &dynamic_channel_allocator_,
+ fixed_service_manager_) {
ASSERT(l2cap_handler_ != nullptr);
ASSERT(acl_connection_ != nullptr);
ASSERT(scheduler_ != nullptr);
void Link::OnAclDisconnected(hci::ErrorCode status) {
fixed_channel_allocator_.OnAclDisconnected(status);
- // TODO hsz: add dynamic channel part
+ dynamic_channel_allocator_.OnAclDisconnected(status);
}
void Link::Disconnect() {
return fixed_channel_allocator_.IsChannelAllocated(cid);
}
+void Link::SendConnectionRequest(Psm psm, Cid local_cid) {
+ signalling_manager_.SendConnectionRequest(psm, local_cid);
+}
+
+void Link::SendDisconnectionRequest(Cid local_cid, Cid remote_cid) {
+ signalling_manager_.SendDisconnectionRequest(local_cid, remote_cid);
+}
+
std::shared_ptr<DynamicChannelImpl> Link::AllocateDynamicChannel(Psm psm, Cid remote_cid,
SecurityPolicy security_policy) {
auto channel = dynamic_channel_allocator_.AllocateChannel(psm, remote_cid, security_policy);
- scheduler_->AttachChannel(channel->GetCid(), channel->GetQueueDownEnd());
+ if (channel != nullptr) {
+ scheduler_->AttachChannel(channel->GetCid(), channel->GetQueueDownEnd());
+ }
return channel;
}
-void Link::FreeDynamicChannel(Cid cid) {}
+void Link::FreeDynamicChannel(Cid cid) {
+ if (dynamic_channel_allocator_.FindChannelByCid(cid) == nullptr) {
+ return;
+ }
+ scheduler_->DetachChannel(cid);
+ dynamic_channel_allocator_.FreeChannel(cid);
+}
void Link::RefreshRefCount() {
int ref_count = 0;
#include "hci/acl_manager.h"
#include "l2cap/classic/internal/dynamic_channel_allocator.h"
#include "l2cap/classic/internal/dynamic_channel_impl.h"
+#include "l2cap/classic/internal/dynamic_channel_service_manager_impl.h"
#include "l2cap/classic/internal/fixed_channel_impl.h"
+#include "l2cap/classic/internal/fixed_channel_service_manager_impl.h"
#include "l2cap/internal/fixed_channel_allocator.h"
#include "l2cap/internal/parameter_provider.h"
#include "l2cap/internal/scheduler.h"
#include "os/alarm.h"
+#include "os/handler.h"
+#include "signalling_manager.h"
namespace bluetooth {
namespace l2cap {
class Link {
public:
Link(os::Handler* l2cap_handler, std::unique_ptr<hci::AclConnection> acl_connection,
- std::unique_ptr<l2cap::internal::Scheduler> scheduler, l2cap::internal::ParameterProvider* parameter_provider);
+ std::unique_ptr<l2cap::internal::Scheduler> scheduler, l2cap::internal::ParameterProvider* parameter_provider,
+ DynamicChannelServiceManagerImpl* dynamic_service_manager,
+ FixedChannelServiceManagerImpl* fixed_service_manager);
virtual ~Link() = default;
virtual bool IsFixedChannelAllocated(Cid cid);
- // ClassicDynamicChannel methods
+ // DynamicChannel methods
+
+ virtual void SendConnectionRequest(Psm psm, Cid local_cid);
+
+ virtual void SendDisconnectionRequest(Cid local_cid, Cid remote_cid);
virtual std::shared_ptr<DynamicChannelImpl> AllocateDynamicChannel(Psm psm, Cid remote_cid,
SecurityPolicy security_policy);
std::unique_ptr<hci::AclConnection> acl_connection_;
std::unique_ptr<l2cap::internal::Scheduler> scheduler_;
l2cap::internal::ParameterProvider* parameter_provider_;
+ DynamicChannelServiceManagerImpl* dynamic_service_manager_;
+ FixedChannelServiceManagerImpl* fixed_service_manager_;
+ ClassicSignallingManager signalling_manager_;
os::Alarm link_idle_disconnect_alarm_{l2cap_handler_};
DISALLOW_COPY_AND_ASSIGN(Link);
};
void LinkManager::ConnectFixedChannelServices(hci::Address device,
PendingFixedChannelConnection pending_fixed_channel_connection) {
// Check if there is any service registered
- auto fixed_channel_services = service_manager_->GetRegisteredServices();
+ auto fixed_channel_services = fixed_channel_service_manager_->GetRegisteredServices();
if (fixed_channel_services.empty()) {
// If so, return error
pending_fixed_channel_connection.handler_->Post(common::BindOnce(
acl_manager_->CreateConnection(device);
}
+void LinkManager::ConnectDynamicChannelServices(hci::Address device,
+ PendingDynamicChannelConnection pending_dynamic_channel_connection,
+ Psm psm) {
+ // TODO: if there is no link, establish link. Otherwise send command.
+ auto* link = GetLink(device);
+ if (link != nullptr) {
+ return;
+ }
+}
+
Link* LinkManager::GetLink(const hci::Address device) {
if (links_.find(device) == links_.end()) {
return nullptr;
acl_connection->GetAddress().ToString().c_str());
auto* link_queue_up_end = acl_connection->GetAclQueueEnd();
links_.try_emplace(device, l2cap_handler_, std::move(acl_connection),
- std::make_unique<l2cap::internal::Fifo>(link_queue_up_end, l2cap_handler_), parameter_provider_);
+ std::make_unique<l2cap::internal::Fifo>(link_queue_up_end, l2cap_handler_), parameter_provider_,
+ dynamic_channel_service_manager_, fixed_channel_service_manager_);
auto* link = GetLink(device);
// Allocate and distribute channels for all registered fixed channel services
- auto fixed_channel_services = service_manager_->GetRegisteredServices();
+ auto fixed_channel_services = fixed_channel_service_manager_->GetRegisteredServices();
for (auto& fixed_channel_service : fixed_channel_services) {
auto fixed_channel_impl = link->AllocateFixedChannel(fixed_channel_service.first, SecurityPolicy());
fixed_channel_service.second->NotifyChannelCreation(
#include "hci/address.h"
#include "l2cap/classic/dynamic_channel_manager.h"
#include "l2cap/classic/fixed_channel_manager.h"
+#include "l2cap/classic/internal/dynamic_channel_service_manager_impl.h"
#include "l2cap/classic/internal/fixed_channel_service_manager_impl.h"
#include "l2cap/classic/internal/link.h"
#include "l2cap/internal/parameter_provider.h"
class LinkManager : public hci::ConnectionCallbacks {
public:
- LinkManager(os::Handler* l2cap_handler, hci::AclManager* acl_manager, FixedChannelServiceManagerImpl* service_manager,
+ LinkManager(os::Handler* l2cap_handler, hci::AclManager* acl_manager,
+ FixedChannelServiceManagerImpl* fixed_channel_service_manager,
+ DynamicChannelServiceManagerImpl* dynamic_channel_service_manager,
l2cap::internal::ParameterProvider* parameter_provider)
- : l2cap_handler_(l2cap_handler), acl_manager_(acl_manager), service_manager_(service_manager),
- parameter_provider_(parameter_provider) {
+ : l2cap_handler_(l2cap_handler), acl_manager_(acl_manager),
+ fixed_channel_service_manager_(fixed_channel_service_manager),
+ dynamic_channel_service_manager_(dynamic_channel_service_manager), parameter_provider_(parameter_provider) {
acl_manager_->RegisterCallbacks(this, l2cap_handler_);
}
void ConnectFixedChannelServices(hci::Address device, PendingFixedChannelConnection pending_fixed_channel_connection);
+ // DynamicChannelManager methods
+
+ void ConnectDynamicChannelServices(hci::Address device,
+ PendingDynamicChannelConnection pending_dynamic_channel_connection, Psm psm);
+
private:
// Dependencies
os::Handler* l2cap_handler_;
hci::AclManager* acl_manager_;
- FixedChannelServiceManagerImpl* service_manager_;
+ FixedChannelServiceManagerImpl* fixed_channel_service_manager_;
+ DynamicChannelServiceManagerImpl* dynamic_channel_service_manager_;
l2cap::internal::ParameterProvider* parameter_provider_;
// Internal states
#include "common/bind.h"
#include "common/testing/bind_test_util.h"
+#include "dynamic_channel_service_manager_impl_mock.h"
#include "hci/acl_manager_mock.h"
#include "hci/address.h"
#include "l2cap/cid.h"
using ::testing::_; // Matcher to any value
using ::testing::ByMove;
using ::testing::DoAll;
+using testing::MockDynamicChannelServiceManagerImpl;
using testing::MockFixedChannelServiceImpl;
using testing::MockFixedChannelServiceManagerImpl;
using ::testing::Return;
TEST_F(L2capClassicLinkManagerTest, connect_fixed_channel_service_without_acl) {
MockFixedChannelServiceManagerImpl mock_classic_fixed_channel_service_manager;
+ MockDynamicChannelServiceManagerImpl mock_classic_dynamic_channel_service_manager;
MockAclManager mock_acl_manager;
hci::Address device{{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}};
auto user_handler = std::make_unique<os::Handler>(thread_);
EXPECT_CALL(mock_acl_manager, RegisterCallbacks(_, _))
.WillOnce(DoAll(SaveArg<0>(&hci_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
LinkManager classic_link_manager(l2cap_handler_, &mock_acl_manager, &mock_classic_fixed_channel_service_manager,
- mock_parameter_provider_);
+ &mock_classic_dynamic_channel_service_manager, mock_parameter_provider_);
EXPECT_EQ(hci_connection_callbacks, &classic_link_manager);
EXPECT_EQ(hci_callback_handler, l2cap_handler_);
EXPECT_CALL(mock_acl_manager, RegisterCallbacks(_, _))
.WillOnce(DoAll(SaveArg<0>(&hci_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
LinkManager classic_link_manager(l2cap_handler_, &mock_acl_manager, &mock_classic_fixed_channel_service_manager,
- mock_parameter_provider_);
+ nullptr, mock_parameter_provider_);
EXPECT_EQ(hci_connection_callbacks, &classic_link_manager);
EXPECT_EQ(hci_callback_handler, l2cap_handler_);
EXPECT_CALL(mock_acl_manager, RegisterCallbacks(_, _))
.WillOnce(DoAll(SaveArg<0>(&hci_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
LinkManager classic_link_manager(l2cap_handler_, &mock_acl_manager, &mock_classic_fixed_channel_service_manager,
- mock_parameter_provider_);
+ nullptr, mock_parameter_provider_);
EXPECT_EQ(hci_connection_callbacks, &classic_link_manager);
EXPECT_EQ(hci_callback_handler, l2cap_handler_);
EXPECT_CALL(mock_acl_manager, RegisterCallbacks(_, _))
.WillOnce(DoAll(SaveArg<0>(&hci_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
LinkManager classic_link_manager(l2cap_handler_, &mock_acl_manager, &mock_classic_fixed_channel_service_manager,
- mock_parameter_provider_);
+ nullptr, mock_parameter_provider_);
EXPECT_EQ(hci_connection_callbacks, &classic_link_manager);
EXPECT_EQ(hci_callback_handler, l2cap_handler_);
EXPECT_CALL(mock_acl_manager, RegisterCallbacks(_, _))
.WillOnce(DoAll(SaveArg<0>(&hci_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
LinkManager classic_link_manager(l2cap_handler_, &mock_acl_manager, &mock_classic_fixed_channel_service_manager,
- mock_parameter_provider_);
+ nullptr, mock_parameter_provider_);
EXPECT_EQ(hci_connection_callbacks, &classic_link_manager);
EXPECT_EQ(hci_callback_handler, l2cap_handler_);
EXPECT_CALL(mock_acl_manager, RegisterCallbacks(_, _))
.WillOnce(DoAll(SaveArg<0>(&hci_connection_callbacks), SaveArg<1>(&hci_callback_handler)));
LinkManager classic_link_manager(l2cap_handler_, &mock_acl_manager, &mock_classic_fixed_channel_service_manager,
- mock_parameter_provider_);
+ nullptr, mock_parameter_provider_);
EXPECT_EQ(hci_connection_callbacks, &classic_link_manager);
EXPECT_EQ(hci_callback_handler, l2cap_handler_);
public:
explicit MockLink(os::Handler* handler, l2cap::internal::ParameterProvider* parameter_provider)
: Link(handler, std::make_unique<MockAclConnection>(),
- std::make_unique<l2cap::internal::testing::MockScheduler>(), parameter_provider){};
+ std::make_unique<l2cap::internal::testing::MockScheduler>(), parameter_provider, nullptr, nullptr){};
MOCK_METHOD(hci::Address, GetDevice, (), (override));
MOCK_METHOD(void, OnAclDisconnected, (hci::ErrorCode status), (override));
MOCK_METHOD(void, Disconnect, (), (override));
MOCK_METHOD(std::shared_ptr<FixedChannelImpl>, AllocateFixedChannel, (Cid cid, SecurityPolicy security_policy),
(override));
+ MOCK_METHOD(std::shared_ptr<DynamicChannelImpl>, AllocateDynamicChannel,
+ (Psm psm, Cid cid, SecurityPolicy security_policy), (override));
MOCK_METHOD(bool, IsFixedChannelAllocated, (Cid cid), (override));
MOCK_METHOD(void, RefreshRefCount, (), (override));
};
--- /dev/null
+/*
+ * 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/classic/internal/signalling_manager.h"
+
+#include <chrono>
+
+#include "common/bind.h"
+#include "l2cap/classic/internal/link.h"
+#include "l2cap/l2cap_packets.h"
+#include "os/log.h"
+#include "packet/raw_builder.h"
+
+namespace bluetooth {
+namespace l2cap {
+namespace classic {
+namespace internal {
+static constexpr auto kTimeout = std::chrono::seconds(3);
+
+ClassicSignallingManager::ClassicSignallingManager(os::Handler* handler, Link* link,
+ DynamicChannelServiceManagerImpl* dynamic_service_manager,
+ DynamicChannelAllocator* channel_allocator,
+ FixedChannelServiceManagerImpl* fixed_service_manager)
+ : handler_(handler), link_(link), dynamic_service_manager_(dynamic_service_manager),
+ channel_allocator_(channel_allocator), fixed_service_manager_(fixed_service_manager), alarm_(handler) {
+ ASSERT(handler_ != nullptr);
+ ASSERT(link_ != nullptr);
+ signalling_channel_ = link_->AllocateFixedChannel(kClassicSignallingCid, {});
+ signalling_channel_->GetQueueUpEnd()->RegisterDequeue(
+ handler_, common::Bind(&ClassicSignallingManager::on_incoming_packet, common::Unretained(this)));
+ enqueue_buffer_ =
+ std::make_unique<os::EnqueueBuffer<packet::BasePacketBuilder>>(signalling_channel_->GetQueueUpEnd());
+}
+
+ClassicSignallingManager::~ClassicSignallingManager() {
+ enqueue_buffer_.reset();
+ signalling_channel_->GetQueueUpEnd()->UnregisterDequeue();
+ signalling_channel_ = nullptr;
+}
+
+void ClassicSignallingManager::OnCommandReject(CommandRejectView command_reject_view) {
+ SignalId signal_id = command_reject_view.GetIdentifier();
+ if (pending_command_.signal_id_ != signal_id) {
+ LOG_WARN("Unknown command reject");
+ return;
+ }
+ pending_command_ = {};
+
+ LOG_INFO("Command rejected");
+}
+
+void ClassicSignallingManager::SendConnectionRequest(Psm psm, Cid local_cid) {
+ PendingCommand pending_command = {next_signal_id_, CommandCode::CONNECTION_REQUEST, psm, local_cid, {}, {}};
+ next_signal_id_++;
+ pending_commands_.push(pending_command);
+ if (pending_commands_.size() == 1) {
+ handle_send_next_command();
+ }
+}
+
+void ClassicSignallingManager::SendConfigurationRequest() {}
+
+void ClassicSignallingManager::SendDisconnectionRequest(Cid local_cid, Cid remote_cid) {
+ PendingCommand pending_command = {next_signal_id_, CommandCode::DISCONNECTION_REQUEST, {}, local_cid, remote_cid, {}};
+ next_signal_id_++;
+ pending_commands_.push(pending_command);
+ if (pending_commands_.size() == 1) {
+ handle_send_next_command();
+ }
+}
+
+void ClassicSignallingManager::SendInformationRequest(InformationRequestInfoType type) {
+ PendingCommand pending_command = {next_signal_id_, CommandCode::INFORMATION_REQUEST, {}, {}, {}, type};
+ next_signal_id_++;
+ pending_commands_.push(pending_command);
+ if (pending_commands_.size() == 1) {
+ handle_send_next_command();
+ }
+}
+
+void ClassicSignallingManager::SendEchoRequest(std::unique_ptr<packet::RawBuilder> payload) {
+ LOG_WARN("Not supported");
+}
+
+void ClassicSignallingManager::OnConnectionRequest(SignalId signal_id, Psm psm, Cid remote_cid) {
+ if (!IsPsmValid(psm)) {
+ LOG_WARN("Invalid psm received from remote psm:%d remote_cid:%d", psm, remote_cid);
+ send_connection_response(signal_id, remote_cid, kInvalidCid, ConnectionResponseResult::PSM_NOT_SUPPORTED,
+ ConnectionResponseStatus::NO_FURTHER_INFORMATION_AVAILABLE);
+ return;
+ }
+
+ if (remote_cid == kInvalidCid) {
+ LOG_WARN("Invalid remote cid received from remote psm:%d remote_cid:%d", psm, remote_cid);
+ send_connection_response(signal_id, remote_cid, kInvalidCid, ConnectionResponseResult::INVALID_CID,
+ ConnectionResponseStatus::NO_FURTHER_INFORMATION_AVAILABLE);
+ return;
+ }
+ if (channel_allocator_->IsPsmUsed(psm)) {
+ LOG_WARN("Psm already exists");
+ send_connection_response(signal_id, remote_cid, kInvalidCid, ConnectionResponseResult::PSM_NOT_SUPPORTED,
+ ConnectionResponseStatus::NO_FURTHER_INFORMATION_AVAILABLE);
+ return;
+ }
+
+ if (!dynamic_service_manager_->IsServiceRegistered(psm)) {
+ LOG_INFO("Service for this psm (%d) is not registered", psm);
+ send_connection_response(signal_id, remote_cid, kInvalidCid, ConnectionResponseResult::PSM_NOT_SUPPORTED,
+ ConnectionResponseStatus::NO_FURTHER_INFORMATION_AVAILABLE);
+ return;
+ }
+
+ auto new_channel = link_->AllocateDynamicChannel(psm, remote_cid, {});
+ if (new_channel == nullptr) {
+ LOG_WARN("Can't allocate dynamic channel");
+ return;
+ }
+ send_connection_response(signal_id, remote_cid, new_channel->GetCid(), ConnectionResponseResult::SUCCESS,
+ ConnectionResponseStatus::NO_FURTHER_INFORMATION_AVAILABLE);
+ std::unique_ptr<DynamicChannel> channel = std::make_unique<DynamicChannel>(new_channel, handler_);
+ dynamic_service_manager_->GetService(psm)->NotifyChannelCreation(std::move(channel));
+}
+
+void ClassicSignallingManager::OnConnectionResponse(SignalId signal_id, Cid cid, Cid remote_cid,
+ ConnectionResponseResult result, ConnectionResponseStatus status) {
+ if (pending_command_.signal_id_ != signal_id || pending_command_.command_code_ != CommandCode::CONNECTION_REQUEST) {
+ LOG_WARN("Received unexpected connection response");
+ return;
+ }
+ if (pending_command_.source_cid_ != cid) {
+ LOG_WARN("SCID doesn't match");
+ return;
+ }
+ if (result != ConnectionResponseResult::SUCCESS) {
+ return;
+ }
+ Psm pending_psm = pending_command_.psm_;
+ pending_command_ = {};
+ auto new_channel = link_->AllocateDynamicChannel(pending_psm, remote_cid, {});
+ if (new_channel == nullptr) {
+ LOG_WARN("Can't allocate dynamic channel");
+ return;
+ }
+ send_connection_response(signal_id, remote_cid, new_channel->GetCid(), ConnectionResponseResult::SUCCESS,
+ ConnectionResponseStatus::NO_FURTHER_INFORMATION_AVAILABLE);
+ std::unique_ptr<DynamicChannel> channel = std::make_unique<DynamicChannel>(new_channel, handler_);
+ dynamic_service_manager_->GetService(pending_psm)->NotifyChannelCreation(std::move(channel));
+ alarm_.Cancel();
+}
+
+void ClassicSignallingManager::OnConfigurationRequest(SignalId signal_id, Cid cid, Continuation is_continuation,
+ std::vector<std::unique_ptr<ConfigurationOption>> option) {}
+
+void ClassicSignallingManager::OnConfigurationResponse(SignalId signal_id, Cid cid, Continuation is_continuation,
+ ConfigurationResponseResult result,
+ std::vector<std::unique_ptr<ConfigurationOption>> option) {}
+
+void ClassicSignallingManager::OnDisconnectionRequest(SignalId signal_id, Cid cid, Cid remote_cid) {
+ // TODO: check cid match
+ auto channel = channel_allocator_->FindChannelByCid(cid);
+ if (channel == nullptr) {
+ LOG_WARN("Disconnect request for an unknown channel");
+ return;
+ }
+ auto builder = DisconnectionResponseBuilder::Create(signal_id.Value(), remote_cid, cid);
+ enqueue_buffer_->Enqueue(std::move(builder), handler_);
+ channel->OnClosed(hci::ErrorCode::SUCCESS);
+ link_->FreeDynamicChannel(cid);
+}
+
+void ClassicSignallingManager::OnDisconnectionResponse(SignalId signal_id, Cid cid, Cid remote_cid) {
+ if (pending_command_.signal_id_ != signal_id ||
+ pending_command_.command_code_ != CommandCode::DISCONNECTION_REQUEST) {
+ return;
+ }
+
+ auto channel = channel_allocator_->FindChannelByCid(cid);
+ if (channel == nullptr) {
+ LOG_WARN("Disconnect response for an unknown channel");
+ return;
+ }
+
+ channel->OnClosed(hci::ErrorCode::SUCCESS);
+ link_->FreeDynamicChannel(cid);
+}
+
+void ClassicSignallingManager::OnEchoRequest(SignalId signal_id, const PacketView<kLittleEndian>& packet) {
+ std::vector<uint8_t> packet_vector{packet.begin(), packet.end()};
+ auto raw_builder = std::make_unique<packet::RawBuilder>();
+ raw_builder->AddOctets(packet_vector);
+ auto builder = EchoRequestBuilder::Create(signal_id.Value(), std::move(raw_builder));
+ enqueue_buffer_->Enqueue(std::move(builder), handler_);
+}
+
+void ClassicSignallingManager::OnEchoResponse(SignalId signal_id, const PacketView<kLittleEndian>& packet) {
+ if (pending_command_.signal_id_ != signal_id || pending_command_.command_code_ != CommandCode::ECHO_REQUEST) {
+ return;
+ }
+ LOG_INFO("Echo response received");
+}
+
+void ClassicSignallingManager::OnInformationRequest(SignalId signal_id, InformationRequestInfoType type) {
+ switch (type) {
+ case InformationRequestInfoType::CONNECTIONLESS_MTU: {
+ auto response = InformationResponseConnectionlessMtuBuilder::Create(signal_id.Value(),
+ InformationRequestResult::NOT_SUPPORTED, 0);
+ enqueue_buffer_->Enqueue(std::move(response), handler_);
+ return;
+ }
+ case InformationRequestInfoType::EXTENDED_FEATURES_SUPPORTED: {
+ // TODO: implement this response
+ auto response = InformationResponseExtendedFeaturesBuilder::Create(
+ signal_id.Value(), InformationRequestResult::NOT_SUPPORTED, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ enqueue_buffer_->Enqueue(std::move(response), handler_);
+ return;
+ }
+ case InformationRequestInfoType::FIXED_CHANNELS_SUPPORTED: {
+ auto response = InformationResponseFixedChannelsBuilder::Create(
+ signal_id.Value(), InformationRequestResult::SUCCESS, fixed_service_manager_->GetSupportedFixedChannelMask());
+ enqueue_buffer_->Enqueue(std::move(response), handler_);
+ return;
+ }
+ }
+}
+
+void ClassicSignallingManager::OnInformationResponse(SignalId signal_id, const InformationResponseView& view) {
+ if (pending_command_.signal_id_ != signal_id || pending_command_.command_code_ != CommandCode::INFORMATION_REQUEST) {
+ return;
+ }
+ if (view.GetResult() != InformationRequestResult::SUCCESS) {
+ return;
+ }
+}
+
+void ClassicSignallingManager::on_incoming_packet() {
+ auto packet = signalling_channel_->GetQueueUpEnd()->TryDequeue();
+ ControlView control_packet_view = ControlView::Create(*packet);
+ if (!control_packet_view.IsValid()) {
+ LOG_WARN("Invalid signalling packet received");
+ return;
+ }
+ auto code = control_packet_view.GetCode();
+ switch (code) {
+ case CommandCode::COMMAND_REJECT: {
+ CommandRejectView command_reject_view = CommandRejectView::Create(control_packet_view);
+ if (!command_reject_view.IsValid()) {
+ return;
+ }
+ OnCommandReject(command_reject_view);
+ return;
+ }
+ case CommandCode::CONNECTION_REQUEST: {
+ ConnectionRequestView connection_request_view = ConnectionRequestView::Create(control_packet_view);
+ if (!connection_request_view.IsValid()) {
+ return;
+ }
+ OnConnectionRequest(control_packet_view.GetIdentifier(), connection_request_view.GetPsm(),
+ connection_request_view.GetSourceCid());
+ return;
+ }
+ case CommandCode::CONNECTION_RESPONSE: {
+ ConnectionResponseView connection_response_view = ConnectionResponseView::Create(control_packet_view);
+ if (!connection_response_view.IsValid()) {
+ return;
+ }
+ OnConnectionResponse(connection_response_view.GetIdentifier(), connection_response_view.GetDestinationCid(),
+ connection_response_view.GetSourceCid(), connection_response_view.GetResult(),
+ connection_response_view.GetStatus());
+ return;
+ }
+ case CommandCode::CONFIGURATION_REQUEST: {
+ ConfigurationRequestView configuration_request_view = ConfigurationRequestView::Create(control_packet_view);
+ if (!configuration_request_view.IsValid()) {
+ return;
+ }
+ OnConfigurationRequest(configuration_request_view.GetIdentifier(), configuration_request_view.GetDestinationCid(),
+ configuration_request_view.GetContinuation(), configuration_request_view.GetConfig());
+ return;
+ }
+ case CommandCode::CONFIGURATION_RESPONSE: {
+ ConfigurationResponseView configuration_response_view = ConfigurationResponseView::Create(control_packet_view);
+ if (!configuration_response_view.IsValid()) {
+ return;
+ }
+ OnConfigurationResponse(configuration_response_view.GetIdentifier(), configuration_response_view.GetSourceCid(),
+ configuration_response_view.GetContinuation(), configuration_response_view.GetResult(),
+ configuration_response_view.GetConfig());
+ }
+ case CommandCode::DISCONNECTION_REQUEST: {
+ DisconnectionRequestView disconnection_request_view = DisconnectionRequestView::Create(control_packet_view);
+ if (!disconnection_request_view.IsValid()) {
+ return;
+ }
+ OnDisconnectionRequest(disconnection_request_view.GetIdentifier(), disconnection_request_view.GetDestinationCid(),
+ disconnection_request_view.GetSourceCid());
+ return;
+ }
+ case CommandCode::DISCONNECTION_RESPONSE: {
+ DisconnectionResponseView disconnection_response_view = DisconnectionResponseView::Create(control_packet_view);
+ if (!disconnection_response_view.IsValid()) {
+ return;
+ }
+ OnDisconnectionResponse(disconnection_response_view.GetIdentifier(),
+ disconnection_response_view.GetDestinationCid(),
+ disconnection_response_view.GetSourceCid());
+ return;
+ }
+ case CommandCode::ECHO_REQUEST: {
+ EchoRequestView echo_request_view = EchoRequestView::Create(control_packet_view);
+ if (!echo_request_view.IsValid()) {
+ return;
+ }
+ OnEchoRequest(echo_request_view.GetIdentifier(), echo_request_view.GetPayload());
+ return;
+ }
+ case CommandCode::ECHO_RESPONSE: {
+ EchoResponseView echo_response_view = EchoResponseView::Create(control_packet_view);
+ if (!echo_response_view.IsValid()) {
+ return;
+ }
+ OnEchoResponse(echo_response_view.GetIdentifier(), echo_response_view.GetPayload());
+ return;
+ }
+ case CommandCode::INFORMATION_REQUEST: {
+ InformationRequestView information_request_view = InformationRequestView::Create(control_packet_view);
+ if (!information_request_view.IsValid()) {
+ return;
+ }
+ OnInformationRequest(information_request_view.GetIdentifier(), information_request_view.GetInfoType());
+ return;
+ }
+ case CommandCode::INFORMATION_RESPONSE: {
+ InformationResponseView information_response_view = InformationResponseView::Create(control_packet_view);
+ if (!information_response_view.IsValid()) {
+ return;
+ }
+ OnInformationResponse(information_response_view.GetIdentifier(), information_response_view);
+ return;
+ }
+ default:
+ LOG_WARN("Unhandled event 0x%x", static_cast<int>(code));
+ return;
+ }
+}
+
+void ClassicSignallingManager::send_connection_response(SignalId signal_id, Cid remote_cid, Cid local_cid,
+ ConnectionResponseResult result,
+ ConnectionResponseStatus status) {
+ auto builder = ConnectionResponseBuilder::Create(signal_id.Value(), remote_cid, local_cid, result, status);
+ enqueue_buffer_->Enqueue(std::move(builder), handler_);
+}
+
+void ClassicSignallingManager::on_command_timeout() {
+ LOG_WARN("Response time out");
+ // TODO: drop the link?
+}
+
+void ClassicSignallingManager::handle_send_next_command() {
+ if (pending_commands_.empty()) {
+ return;
+ }
+ pending_command_ = pending_commands_.front();
+ pending_commands_.pop();
+
+ auto signal_id = pending_command_.signal_id_;
+ auto psm = pending_command_.psm_;
+ auto source_cid = pending_command_.source_cid_;
+ auto destination_cid = pending_command_.destination_cid_;
+ auto info_type = pending_command_.info_type_;
+ switch (pending_command_.command_code_) {
+ case CommandCode::CONNECTION_REQUEST: {
+ auto builder = ConnectionRequestBuilder::Create(signal_id.Value(), psm, source_cid);
+ enqueue_buffer_->Enqueue(std::move(builder), handler_);
+ alarm_.Schedule(common::BindOnce(&ClassicSignallingManager::on_command_timeout, common::Unretained(this)),
+ kTimeout);
+ break;
+ }
+ case CommandCode::CONFIGURATION_REQUEST:
+ break;
+ case CommandCode::DISCONNECTION_REQUEST: {
+ auto builder = DisconnectionRequestBuilder::Create(signal_id.Value(), destination_cid, source_cid);
+ enqueue_buffer_->Enqueue(std::move(builder), handler_);
+ alarm_.Schedule(common::BindOnce(&ClassicSignallingManager::on_command_timeout, common::Unretained(this)),
+ kTimeout);
+ break;
+ }
+ case CommandCode::INFORMATION_REQUEST: {
+ auto builder = InformationRequestBuilder::Create(signal_id.Value(), info_type);
+ enqueue_buffer_->Enqueue(std::move(builder), handler_);
+ alarm_.Schedule(common::BindOnce(&ClassicSignallingManager::on_command_timeout, common::Unretained(this)),
+ kTimeout);
+ break;
+ }
+ default:
+ LOG_WARN("Unsupported command code 0x%x", static_cast<int>(pending_command_.command_code_));
+ }
+}
+
+} // namespace internal
+} // namespace classic
+} // namespace l2cap
+} // namespace bluetooth
--- /dev/null
+/*
+ * 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 <cstdint>
+#include <queue>
+
+#include "l2cap/cid.h"
+#include "l2cap/classic/internal/dynamic_channel_allocator.h"
+#include "l2cap/classic/internal/dynamic_channel_service_manager_impl.h"
+#include "l2cap/classic/internal/fixed_channel_impl.h"
+#include "l2cap/classic/internal/fixed_channel_service_manager_impl.h"
+#include "l2cap/l2cap_packets.h"
+#include "l2cap/psm.h"
+#include "l2cap/signal_id.h"
+#include "os/alarm.h"
+#include "os/handler.h"
+#include "os/queue.h"
+#include "packet/raw_builder.h"
+
+namespace bluetooth {
+namespace l2cap {
+namespace classic {
+namespace internal {
+
+struct PendingCommand {
+ SignalId signal_id_;
+ CommandCode command_code_;
+ Psm psm_;
+ Cid source_cid_;
+ Cid destination_cid_;
+ InformationRequestInfoType info_type_;
+};
+
+class Link;
+
+class ClassicSignallingManager {
+ public:
+ ClassicSignallingManager(os::Handler* handler, Link* link,
+ classic::internal::DynamicChannelServiceManagerImpl* dynamic_service_manager,
+ classic::internal::DynamicChannelAllocator* channel_allocator,
+ classic::internal::FixedChannelServiceManagerImpl* fixed_service_manager);
+
+ virtual ~ClassicSignallingManager();
+
+ void OnCommandReject(CommandRejectView command_reject_view);
+
+ void SendConnectionRequest(Psm psm, Cid local_cid);
+
+ void SendConfigurationRequest();
+
+ void SendDisconnectionRequest(Cid local_cid, Cid remote_cid);
+
+ void SendInformationRequest(InformationRequestInfoType type);
+
+ void SendEchoRequest(std::unique_ptr<packet::RawBuilder> payload);
+
+ void OnConnectionRequest(SignalId signal_id, Psm psm, Cid remote_cid);
+
+ void OnConnectionResponse(SignalId signal_id, Cid cid, Cid remote_cid, ConnectionResponseResult result,
+ ConnectionResponseStatus status);
+
+ void OnDisconnectionRequest(SignalId signal_id, Cid cid, Cid remote_cid);
+
+ void OnDisconnectionResponse(SignalId signal_id, Cid cid, Cid remote_cid);
+
+ void OnConfigurationRequest(SignalId signal_id, Cid cid, Continuation is_continuation,
+ std::vector<std::unique_ptr<ConfigurationOption>>);
+
+ void OnConfigurationResponse(SignalId signal_id, Cid cid, Continuation is_continuation,
+ ConfigurationResponseResult result, std::vector<std::unique_ptr<ConfigurationOption>>);
+
+ void OnEchoRequest(SignalId signal_id, const PacketView<kLittleEndian>& packet);
+
+ void OnEchoResponse(SignalId signal_id, const PacketView<kLittleEndian>& packet);
+
+ void OnInformationRequest(SignalId signal_id, InformationRequestInfoType type);
+
+ void OnInformationResponse(SignalId signal_id, const InformationResponseView& view);
+
+ private:
+ void on_incoming_packet();
+ void send_connection_response(SignalId signal_id, Cid remote_cid, Cid local_cid, ConnectionResponseResult result,
+ ConnectionResponseStatus status);
+ void on_command_timeout();
+ void handle_send_next_command();
+
+ os::Handler* handler_;
+ Link* link_;
+ std::shared_ptr<classic::internal::FixedChannelImpl> signalling_channel_;
+ DynamicChannelServiceManagerImpl* dynamic_service_manager_;
+ DynamicChannelAllocator* channel_allocator_;
+ FixedChannelServiceManagerImpl* fixed_service_manager_;
+ std::unique_ptr<os::EnqueueBuffer<packet::BasePacketBuilder>> enqueue_buffer_;
+ PendingCommand pending_command_;
+ std::queue<std::unique_ptr<ControlBuilder>> pending_requests_;
+ std::queue<PendingCommand> pending_commands_;
+ os::Alarm alarm_;
+ SignalId next_signal_id_ = kInvalidSignalId;
+};
+
+} // namespace internal
+} // namespace classic
+} // namespace l2cap
+} // namespace bluetooth
--- /dev/null
+/*
+ * 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/classic/internal/signalling_manager.h"
+
+#include "l2cap/classic/internal/dynamic_channel_service_manager_impl_mock.h"
+#include "l2cap/classic/internal/fixed_channel_service_manager_impl_mock.h"
+#include "l2cap/classic/internal/link_mock.h"
+#include "l2cap/internal/parameter_provider_mock.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace bluetooth {
+namespace l2cap {
+namespace classic {
+namespace internal {
+namespace {
+
+class L2capClassicSignallingManagerTest : public ::testing::Test {
+ public:
+ static void SyncHandler(os::Handler* handler) {
+ std::promise<void> promise;
+ auto future = promise.get_future();
+ handler->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
+ future.wait_for(std::chrono::milliseconds(3));
+ }
+
+ protected:
+ void SetUp() override {
+ thread_ = new os::Thread("test_thread", os::Thread::Priority::NORMAL);
+ l2cap_handler_ = new os::Handler(thread_);
+ }
+
+ void TearDown() override {
+ l2cap_handler_->Clear();
+ delete l2cap_handler_;
+ delete thread_;
+ }
+
+ os::Thread* thread_ = nullptr;
+ os::Handler* l2cap_handler_ = nullptr;
+};
+
+PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
+ auto bytes = std::make_shared<std::vector<uint8_t>>();
+ BitInserter i(*bytes);
+ bytes->reserve(packet->size());
+ packet->Serialize(i);
+ return packet::PacketView<packet::kLittleEndian>(bytes);
+}
+
+TEST_F(L2capClassicSignallingManagerTest, handle_connection_request) {
+ l2cap::internal::testing::MockParameterProvider parameter_provider;
+ testing::MockDynamicChannelServiceManagerImpl dynamic_service_manager_;
+ testing::MockFixedChannelServiceManagerImpl fixed_service_manager_;
+ testing::MockLink link{l2cap_handler_, ¶meter_provider};
+ std::shared_ptr<FixedChannelImpl> signalling_channel = std::make_shared<FixedChannelImpl>(1, &link, l2cap_handler_);
+ EXPECT_CALL(link, AllocateFixedChannel(_, _)).WillRepeatedly(Return(signalling_channel));
+ auto service_psm = 0x1;
+ EXPECT_CALL(dynamic_service_manager_, IsServiceRegistered(service_psm)).WillRepeatedly(Return(true));
+ DynamicChannelAllocator channel_allocator{&link, l2cap_handler_};
+ ClassicSignallingManager signalling_manager{l2cap_handler_, &link, &dynamic_service_manager_, &channel_allocator,
+ &fixed_service_manager_};
+ auto* down_end = signalling_channel->GetQueueDownEnd();
+ os::EnqueueBuffer<packet::PacketView<kLittleEndian>> enqueue_buffer{down_end};
+ auto dcid = 0x101;
+ auto builder = ConnectionRequestBuilder::Create(1, service_psm, dcid);
+ enqueue_buffer.Enqueue(std::make_unique<PacketView<kLittleEndian>>(GetPacketView(std::move(builder))),
+ l2cap_handler_);
+ SyncHandler(l2cap_handler_);
+ EXPECT_CALL(link, AllocateDynamicChannel(_, dcid, _));
+ SyncHandler(l2cap_handler_);
+}
+
+} // namespace
+} // namespace internal
+} // namespace classic
+} // namespace l2cap
+} // namespace bluetooth
#include "hci/address.h"
#include "hci/hci_layer.h"
#include "hci/hci_packets.h"
+#include "l2cap/classic/internal/dynamic_channel_service_manager_impl.h"
#include "l2cap/classic/internal/fixed_channel_service_manager_impl.h"
#include "l2cap/classic/internal/link_manager.h"
#include "l2cap/internal/parameter_provider.h"
hci::AclManager* acl_manager_;
l2cap::internal::ParameterProvider parameter_provider_;
internal::FixedChannelServiceManagerImpl fixed_channel_service_manager_impl_{l2cap_handler_};
+ internal::DynamicChannelServiceManagerImpl dynamic_channel_service_manager_impl_{l2cap_handler_};
internal::LinkManager link_manager_{l2cap_handler_, acl_manager_, &fixed_channel_service_manager_impl_,
- ¶meter_provider_};
+ &dynamic_channel_service_manager_impl_, ¶meter_provider_};
};
void L2capClassicModule::ListDependencies(ModuleList* list) {
&pimpl_->link_manager_, pimpl_->l2cap_handler_));
}
+std::unique_ptr<DynamicChannelManager> L2capClassicModule::GetDynamicChannelManager() {
+ return std::unique_ptr<DynamicChannelManager>(new DynamicChannelManager(
+ &pimpl_->dynamic_channel_service_manager_impl_, &pimpl_->link_manager_, pimpl_->l2cap_handler_));
+}
+
} // namespace classic
} // namespace l2cap
} // namespace bluetooth
\ No newline at end of file
#include <memory>
+#include "l2cap/classic/dynamic_channel_manager.h"
#include "l2cap/classic/fixed_channel_manager.h"
#include "module.h"
*/
std::unique_ptr<FixedChannelManager> GetFixedChannelManager();
+ /**
+ * Get the api to the classic dynamic channel l2cap module
+ */
+ std::unique_ptr<DynamicChannelManager> GetDynamicChannelManager();
+
static const ModuleFactory Factory;
protected:
constexpr Psm kDefaultPsm = 0; // Invalid Psm as a default value
constexpr bool IsPsmValid(Psm psm) {
+ // See Core spec 5.1 Vol 3 Part A 4.2 for definition
return (psm & 0x0101u) == 0x0001u;
}