1 /*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "l2cap/le/facade.h"
18
19 #include <bluetooth/log.h>
20
21 #include "blueberry/facade/l2cap/le/facade.grpc.pb.h"
22 #include "grpc/grpc_event_queue.h"
23 #include "l2cap/le/dynamic_channel.h"
24 #include "l2cap/le/dynamic_channel_manager.h"
25 #include "l2cap/le/dynamic_channel_service.h"
26 #include "l2cap/le/l2cap_le_module.h"
27 #include "l2cap/le/security_policy.h"
28 #include "l2cap/psm.h"
29 #include "packet/raw_builder.h"
30
31 namespace bluetooth {
32 namespace l2cap {
33 namespace le {
34
35 using namespace blueberry::facade::l2cap::le;
36
SecurityLevelToPolicy(SecurityLevel level)37 SecurityPolicy SecurityLevelToPolicy(SecurityLevel level) {
38 switch (level) {
39 case SecurityLevel::NO_SECURITY:
40 return SecurityPolicy::NO_SECURITY_WHATSOEVER_PLAINTEXT_TRANSPORT_OK;
41 case SecurityLevel::UNAUTHENTICATED_PAIRING_WITH_ENCRYPTION:
42 return SecurityPolicy::ENCRYPTED_TRANSPORT;
43 case SecurityLevel::AUTHENTICATED_PAIRING_WITH_ENCRYPTION:
44 return SecurityPolicy::AUTHENTICATED_ENCRYPTED_TRANSPORT;
45 case SecurityLevel::AUTHENTICATED_PAIRING_WITH_128_BIT_KEY:
46 return SecurityPolicy::_NOT_FOR_YOU__AUTHENTICATED_PAIRING_WITH_128_BIT_KEY;
47 case SecurityLevel::AUTHORIZATION:
48 return SecurityPolicy::_NOT_FOR_YOU__AUTHORIZATION;
49 default:
50 return SecurityPolicy::NO_SECURITY_WHATSOEVER_PLAINTEXT_TRANSPORT_OK;
51 }
52 }
53
54 static constexpr auto kChannelOpenTimeout = std::chrono::seconds(4);
55
56 class L2capLeModuleFacadeService : public L2capLeModuleFacade::Service {
57 public:
L2capLeModuleFacadeService(L2capLeModule * l2cap_layer,os::Handler * facade_handler)58 L2capLeModuleFacadeService(L2capLeModule* l2cap_layer, os::Handler* facade_handler)
59 : l2cap_layer_(l2cap_layer), facade_handler_(facade_handler) {
60 log::assert_that(l2cap_layer_ != nullptr, "assert failed: l2cap_layer_ != nullptr");
61 log::assert_that(facade_handler_ != nullptr, "assert failed: facade_handler_ != nullptr");
62 }
63
FetchL2capData(::grpc::ServerContext * context,const::google::protobuf::Empty *,::grpc::ServerWriter<::bluetooth::l2cap::le::L2capPacket> * writer)64 ::grpc::Status FetchL2capData(
65 ::grpc::ServerContext* context,
66 const ::google::protobuf::Empty* /* request */,
67 ::grpc::ServerWriter<::bluetooth::l2cap::le::L2capPacket>* writer) override {
68 return pending_l2cap_data_.RunLoop(context, writer);
69 }
70
OpenDynamicChannel(::grpc::ServerContext *,const OpenDynamicChannelRequest * request,OpenDynamicChannelResponse * response)71 ::grpc::Status OpenDynamicChannel(
72 ::grpc::ServerContext* /* context */,
73 const OpenDynamicChannelRequest* request,
74 OpenDynamicChannelResponse* response) override {
75 auto service_helper = dynamic_channel_helper_map_.find(request->psm());
76 if (service_helper == dynamic_channel_helper_map_.end()) {
77 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Psm not registered");
78 }
79 hci::Address peer_address;
80 log::assert_that(
81 hci::Address::FromString(request->remote().address().address(), peer_address),
82 "assert failed: hci::Address::FromString(request->remote().address().address(), "
83 "peer_address)");
84 // TODO: Support different address type
85 hci::AddressWithType peer(peer_address, hci::AddressType::RANDOM_DEVICE_ADDRESS);
86 service_helper->second->Connect(peer);
87 response->set_status(
88 static_cast<int>(service_helper->second->channel_open_fail_reason_.l2cap_connection_response_result));
89 return ::grpc::Status::OK;
90 }
91
CloseDynamicChannel(::grpc::ServerContext *,const CloseDynamicChannelRequest * request,::google::protobuf::Empty *)92 ::grpc::Status CloseDynamicChannel(
93 ::grpc::ServerContext* /* context */,
94 const CloseDynamicChannelRequest* request,
95 ::google::protobuf::Empty* /* response */) override {
96 auto service_helper = dynamic_channel_helper_map_.find(request->psm());
97 if (service_helper == dynamic_channel_helper_map_.end()) {
98 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Psm not registered");
99 }
100 if (service_helper->second->channel_ == nullptr) {
101 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Channel not open");
102 }
103 auto address = service_helper->second->channel_->GetDevice().GetAddress();
104 hci::Address peer_address;
105 log::assert_that(
106 hci::Address::FromString(request->remote().address().address(), peer_address),
107 "assert failed: hci::Address::FromString(request->remote().address().address(), "
108 "peer_address)");
109 if (address != peer_address) {
110 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Remote address doesn't match");
111 }
112 service_helper->second->channel_->Close();
113 return ::grpc::Status::OK;
114 }
115
SetDynamicChannel(::grpc::ServerContext *,const::bluetooth::l2cap::le::SetEnableDynamicChannelRequest * request,::google::protobuf::Empty *)116 ::grpc::Status SetDynamicChannel(
117 ::grpc::ServerContext* /* context */,
118 const ::bluetooth::l2cap::le::SetEnableDynamicChannelRequest* request,
119 ::google::protobuf::Empty* /* response */) override {
120 if (request->enable()) {
121 dynamic_channel_helper_map_.emplace(request->psm(), std::make_unique<L2capDynamicChannelHelper>(
122 this, l2cap_layer_, facade_handler_, request->psm(),
123 SecurityLevelToPolicy(request->security_level())));
124 return ::grpc::Status::OK;
125 } else {
126 auto service_helper = dynamic_channel_helper_map_.find(request->psm());
127 if (service_helper == dynamic_channel_helper_map_.end()) {
128 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Psm not registered");
129 }
130 service_helper->second->service_->Unregister(common::BindOnce([] {}), facade_handler_);
131 return ::grpc::Status::OK;
132 }
133 }
134
SendDynamicChannelPacket(::grpc::ServerContext *,const::bluetooth::l2cap::le::DynamicChannelPacket * request,::google::protobuf::Empty *)135 ::grpc::Status SendDynamicChannelPacket(
136 ::grpc::ServerContext* /* context */,
137 const ::bluetooth::l2cap::le::DynamicChannelPacket* request,
138 ::google::protobuf::Empty* /* response */) override {
139 std::unique_lock<std::mutex> lock(channel_map_mutex_);
140 if (dynamic_channel_helper_map_.find(request->psm()) == dynamic_channel_helper_map_.end()) {
141 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Psm not registered");
142 }
143 std::vector<uint8_t> packet(request->payload().begin(), request->payload().end());
144 if (!dynamic_channel_helper_map_[request->psm()]->SendPacket(packet)) {
145 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Channel not open");
146 }
147 return ::grpc::Status::OK;
148 }
149
150 class L2capDynamicChannelHelper {
151 public:
L2capDynamicChannelHelper(L2capLeModuleFacadeService * service,L2capLeModule * l2cap_layer,os::Handler * handler,Psm psm,SecurityPolicy security_policy)152 L2capDynamicChannelHelper(L2capLeModuleFacadeService* service, L2capLeModule* l2cap_layer, os::Handler* handler,
153 Psm psm, SecurityPolicy security_policy)
154 : facade_service_(service), l2cap_layer_(l2cap_layer), handler_(handler), psm_(psm) {
155 dynamic_channel_manager_ = l2cap_layer_->GetDynamicChannelManager();
156 dynamic_channel_manager_->RegisterService(
157 psm, {}, security_policy,
158 common::BindOnce(&L2capDynamicChannelHelper::on_l2cap_service_registration_complete,
159 common::Unretained(this)),
160 common::Bind(&L2capDynamicChannelHelper::on_connection_open, common::Unretained(this)), handler_);
161 }
162
~L2capDynamicChannelHelper()163 ~L2capDynamicChannelHelper() {
164 if (channel_ != nullptr) {
165 channel_->GetQueueUpEnd()->UnregisterDequeue();
166 channel_ = nullptr;
167 }
168 }
169
Connect(hci::AddressWithType address)170 void Connect(hci::AddressWithType address) {
171 dynamic_channel_manager_->ConnectChannel(
172 address, {}, psm_, common::Bind(&L2capDynamicChannelHelper::on_connection_open, common::Unretained(this)),
173 common::Bind(&L2capDynamicChannelHelper::on_connect_fail, common::Unretained(this)), handler_);
174 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
175 if (!channel_open_cv_.wait_for(lock, kChannelOpenTimeout, [this] { return channel_ != nullptr; })) {
176 log::warn("Channel is not open for psm {}", psm_);
177 }
178 }
179
on_l2cap_service_registration_complete(DynamicChannelManager::RegistrationResult registration_result,std::unique_ptr<DynamicChannelService> service)180 void on_l2cap_service_registration_complete(DynamicChannelManager::RegistrationResult registration_result,
181 std::unique_ptr<DynamicChannelService> service) {
182 if (registration_result != DynamicChannelManager::RegistrationResult::SUCCESS) {
183 log::error("Service registration failed");
184 } else {
185 service_ = std::move(service);
186 }
187 }
188
189 // invoked from Facade Handler
on_connection_open(std::unique_ptr<DynamicChannel> channel)190 void on_connection_open(std::unique_ptr<DynamicChannel> channel) {
191 {
192 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
193 channel_ = std::move(channel);
194 }
195 channel_open_cv_.notify_all();
196 channel_->RegisterOnCloseCallback(
197 facade_service_->facade_handler_->BindOnceOn(this, &L2capDynamicChannelHelper::on_close_callback));
198 channel_->GetQueueUpEnd()->RegisterDequeue(
199 facade_service_->facade_handler_,
200 common::Bind(&L2capDynamicChannelHelper::on_incoming_packet, common::Unretained(this)));
201 }
202
on_close_callback(hci::ErrorCode)203 void on_close_callback(hci::ErrorCode /* error_code */) {
204 {
205 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
206 channel_->GetQueueUpEnd()->UnregisterDequeue();
207 }
208 channel_ = nullptr;
209 }
210
on_connect_fail(DynamicChannelManager::ConnectionResult result)211 void on_connect_fail(DynamicChannelManager::ConnectionResult result) {
212 {
213 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
214 channel_ = nullptr;
215 channel_open_fail_reason_ = result;
216 }
217 channel_open_cv_.notify_all();
218 }
219
on_incoming_packet()220 void on_incoming_packet() {
221 auto packet = channel_->GetQueueUpEnd()->TryDequeue();
222 std::string data = std::string(packet->begin(), packet->end());
223 L2capPacket l2cap_data;
224 l2cap_data.set_psm(psm_);
225 l2cap_data.set_payload(data);
226 facade_service_->pending_l2cap_data_.OnIncomingEvent(l2cap_data);
227 }
228
SendPacket(std::vector<uint8_t> packet)229 bool SendPacket(std::vector<uint8_t> packet) {
230 if (channel_ == nullptr) {
231 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
232 if (!channel_open_cv_.wait_for(lock, kChannelOpenTimeout, [this] { return channel_ != nullptr; })) {
233 log::warn("Channel is not open for psm {}", psm_);
234 return false;
235 }
236 }
237 std::promise<void> promise;
238 auto future = promise.get_future();
239 channel_->GetQueueUpEnd()->RegisterEnqueue(
240 handler_, common::Bind(&L2capDynamicChannelHelper::enqueue_callback, common::Unretained(this), packet,
241 common::Passed(std::move(promise))));
242 auto status = future.wait_for(std::chrono::milliseconds(500));
243 if (status != std::future_status::ready) {
244 log::error("Can't send packet because the previous packet wasn't sent yet");
245 return false;
246 }
247 return true;
248 }
249
enqueue_callback(std::vector<uint8_t> packet,std::promise<void> promise)250 std::unique_ptr<packet::BasePacketBuilder> enqueue_callback(std::vector<uint8_t> packet,
251 std::promise<void> promise) {
252 auto packet_one = std::make_unique<packet::RawBuilder>(2000);
253 packet_one->AddOctets(packet);
254 channel_->GetQueueUpEnd()->UnregisterEnqueue();
255 promise.set_value();
256 return packet_one;
257 }
258
259 L2capLeModuleFacadeService* facade_service_;
260 L2capLeModule* l2cap_layer_;
261 os::Handler* handler_;
262 std::unique_ptr<DynamicChannelManager> dynamic_channel_manager_;
263 std::unique_ptr<DynamicChannelService> service_;
264 std::unique_ptr<DynamicChannel> channel_ = nullptr;
265 Psm psm_;
266 DynamicChannelManager::ConnectionResult channel_open_fail_reason_;
267 std::condition_variable channel_open_cv_;
268 std::mutex channel_open_cv_mutex_;
269 };
270
SetFixedChannel(::grpc::ServerContext *,const SetEnableFixedChannelRequest * request,::google::protobuf::Empty *)271 ::grpc::Status SetFixedChannel(
272 ::grpc::ServerContext* /* context */,
273 const SetEnableFixedChannelRequest* request,
274 ::google::protobuf::Empty* /* response */) override {
275 if (request->enable()) {
276 fixed_channel_helper_map_.emplace(request->cid(), std::make_unique<L2capFixedChannelHelper>(
277 this, l2cap_layer_, facade_handler_, request->cid()));
278 return ::grpc::Status::OK;
279 } else {
280 auto service_helper = fixed_channel_helper_map_.find(request->cid());
281 if (service_helper == fixed_channel_helper_map_.end()) {
282 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Cid not registered");
283 }
284 service_helper->second->channel_->Release();
285 service_helper->second->service_->Unregister(common::BindOnce([] {}), facade_handler_);
286 return ::grpc::Status::OK;
287 }
288 }
289
SendFixedChannelPacket(::grpc::ServerContext *,const FixedChannelPacket * request,::google::protobuf::Empty *)290 ::grpc::Status SendFixedChannelPacket(
291 ::grpc::ServerContext* /* context */,
292 const FixedChannelPacket* request,
293 ::google::protobuf::Empty* /* response */) override {
294 std::unique_lock<std::mutex> lock(channel_map_mutex_);
295 if (fixed_channel_helper_map_.find(request->cid()) == fixed_channel_helper_map_.end()) {
296 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Cid not registered");
297 }
298 std::vector<uint8_t> packet(request->payload().begin(), request->payload().end());
299 if (!fixed_channel_helper_map_[request->cid()]->SendPacket(packet)) {
300 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Channel not open");
301 }
302 return ::grpc::Status::OK;
303 }
304
305 class L2capFixedChannelHelper {
306 public:
L2capFixedChannelHelper(L2capLeModuleFacadeService * service,L2capLeModule * l2cap_layer,os::Handler * handler,Cid cid)307 L2capFixedChannelHelper(L2capLeModuleFacadeService* service, L2capLeModule* l2cap_layer, os::Handler* handler,
308 Cid cid)
309 : facade_service_(service), l2cap_layer_(l2cap_layer), handler_(handler), cid_(cid) {
310 fixed_channel_manager_ = l2cap_layer_->GetFixedChannelManager();
311 fixed_channel_manager_->RegisterService(
312 cid_,
313 common::BindOnce(&L2capFixedChannelHelper::on_l2cap_service_registration_complete, common::Unretained(this)),
314 common::Bind(&L2capFixedChannelHelper::on_connection_open, common::Unretained(this)), handler_);
315 }
316
~L2capFixedChannelHelper()317 ~L2capFixedChannelHelper() {
318 if (channel_ != nullptr) {
319 channel_->GetQueueUpEnd()->UnregisterDequeue();
320 channel_->Release();
321 channel_ = nullptr;
322 }
323 }
324
Connect(hci::AddressWithType address)325 void Connect(hci::AddressWithType address) {
326 fixed_channel_manager_->ConnectServices(
327 address, common::BindOnce(&L2capFixedChannelHelper::on_connect_fail, common::Unretained(this)), handler_);
328 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
329 if (!channel_open_cv_.wait_for(lock, kChannelOpenTimeout, [this] { return channel_ != nullptr; })) {
330 log::warn("Channel is not open for cid {}", cid_);
331 }
332 }
333
on_l2cap_service_registration_complete(FixedChannelManager::RegistrationResult registration_result,std::unique_ptr<FixedChannelService> service)334 void on_l2cap_service_registration_complete(FixedChannelManager::RegistrationResult registration_result,
335 std::unique_ptr<FixedChannelService> service) {
336 if (registration_result != FixedChannelManager::RegistrationResult::SUCCESS) {
337 log::error("Service registration failed");
338 } else {
339 service_ = std::move(service);
340 }
341 }
342
343 // invoked from Facade Handler
on_connection_open(std::unique_ptr<FixedChannel> channel)344 void on_connection_open(std::unique_ptr<FixedChannel> channel) {
345 {
346 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
347 channel_ = std::move(channel);
348 channel_->RegisterOnCloseCallback(
349 handler_, common::BindOnce(&L2capFixedChannelHelper::on_close_callback, common::Unretained(this)));
350 channel_->Acquire();
351 }
352 channel_open_cv_.notify_all();
353 channel_->GetQueueUpEnd()->RegisterDequeue(
354 facade_service_->facade_handler_,
355 common::Bind(&L2capFixedChannelHelper::on_incoming_packet, common::Unretained(this)));
356 }
357
on_close_callback(hci::ErrorCode)358 void on_close_callback(hci::ErrorCode /* error_code */) {
359 {
360 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
361 channel_->GetQueueUpEnd()->UnregisterDequeue();
362 }
363 channel_ = nullptr;
364 }
365
on_connect_fail(FixedChannelManager::ConnectionResult)366 void on_connect_fail(FixedChannelManager::ConnectionResult /* result */) {
367 {
368 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
369 channel_ = nullptr;
370 }
371 channel_open_cv_.notify_all();
372 }
373
on_incoming_packet()374 void on_incoming_packet() {
375 auto packet = channel_->GetQueueUpEnd()->TryDequeue();
376 std::string data = std::string(packet->begin(), packet->end());
377 L2capPacket l2cap_data;
378 l2cap_data.set_fixed_cid(cid_);
379 l2cap_data.set_payload(data);
380 facade_service_->pending_l2cap_data_.OnIncomingEvent(l2cap_data);
381 }
382
SendPacket(std::vector<uint8_t> packet)383 bool SendPacket(std::vector<uint8_t> packet) {
384 if (channel_ == nullptr) {
385 std::unique_lock<std::mutex> lock(channel_open_cv_mutex_);
386 if (!channel_open_cv_.wait_for(lock, kChannelOpenTimeout, [this] { return channel_ != nullptr; })) {
387 log::warn("Channel is not open for cid {}", cid_);
388 return false;
389 }
390 }
391 std::promise<void> promise;
392 auto future = promise.get_future();
393 channel_->GetQueueUpEnd()->RegisterEnqueue(
394 handler_, common::Bind(&L2capFixedChannelHelper::enqueue_callback, common::Unretained(this), packet,
395 common::Passed(std::move(promise))));
396 auto status = future.wait_for(std::chrono::milliseconds(500));
397 if (status != std::future_status::ready) {
398 log::error("Can't send packet because the previous packet wasn't sent yet");
399 return false;
400 }
401 return true;
402 }
403
enqueue_callback(std::vector<uint8_t> packet,std::promise<void> promise)404 std::unique_ptr<packet::BasePacketBuilder> enqueue_callback(std::vector<uint8_t> packet,
405 std::promise<void> promise) {
406 auto packet_one = std::make_unique<packet::RawBuilder>(2000);
407 packet_one->AddOctets(packet);
408 channel_->GetQueueUpEnd()->UnregisterEnqueue();
409 promise.set_value();
410 return packet_one;
411 }
412
413 L2capLeModuleFacadeService* facade_service_;
414 L2capLeModule* l2cap_layer_;
415 os::Handler* handler_;
416 std::unique_ptr<FixedChannelManager> fixed_channel_manager_;
417 std::unique_ptr<FixedChannelService> service_;
418 std::unique_ptr<FixedChannel> channel_ = nullptr;
419 Cid cid_;
420 std::condition_variable channel_open_cv_;
421 std::mutex channel_open_cv_mutex_;
422 };
423
SendConnectionParameterUpdate(::grpc::ServerContext *,const ConnectionParameter * request,::google::protobuf::Empty *)424 ::grpc::Status SendConnectionParameterUpdate(
425 ::grpc::ServerContext* /* context */,
426 const ConnectionParameter* request,
427 ::google::protobuf::Empty* /* response */) override {
428 if (dynamic_channel_helper_map_.empty()) {
429 return ::grpc::Status(::grpc::StatusCode::FAILED_PRECONDITION, "Need to open at least one dynamic channel first");
430 }
431 auto& dynamic_channel_helper = dynamic_channel_helper_map_.begin()->second;
432 dynamic_channel_helper->channel_->GetLinkOptions()->UpdateConnectionParameter(
433 request->conn_interval_min(), request->conn_interval_max(), request->conn_latency(),
434 request->supervision_timeout(), request->min_ce_length(), request->max_ce_length());
435
436 return ::grpc::Status::OK;
437 }
438
439 L2capLeModule* l2cap_layer_;
440 os::Handler* facade_handler_;
441 std::mutex channel_map_mutex_;
442 std::map<Psm, std::unique_ptr<L2capDynamicChannelHelper>> dynamic_channel_helper_map_;
443 std::map<Cid, std::unique_ptr<L2capFixedChannelHelper>> fixed_channel_helper_map_;
444 ::bluetooth::grpc::GrpcEventQueue<L2capPacket> pending_l2cap_data_{"FetchL2capData"};
445 };
446
ListDependencies(ModuleList * list) const447 void L2capLeModuleFacadeModule::ListDependencies(ModuleList* list) const {
448 ::bluetooth::grpc::GrpcFacadeModule::ListDependencies(list);
449 list->add<l2cap::le::L2capLeModule>();
450 }
451
Start()452 void L2capLeModuleFacadeModule::Start() {
453 ::bluetooth::grpc::GrpcFacadeModule::Start();
454 service_ = new L2capLeModuleFacadeService(GetDependency<l2cap::le::L2capLeModule>(), GetHandler());
455 }
456
Stop()457 void L2capLeModuleFacadeModule::Stop() {
458 delete service_;
459 ::bluetooth::grpc::GrpcFacadeModule::Stop();
460 }
461
GetService() const462 ::grpc::Service* L2capLeModuleFacadeModule::GetService() const {
463 return service_;
464 }
465
466 const ModuleFactory L2capLeModuleFacadeModule::Factory =
__anonbd0aa1f10702() 467 ::bluetooth::ModuleFactory([]() { return new L2capLeModuleFacadeModule(); });
468
469 } // namespace le
470 } // namespace l2cap
471 } // namespace bluetooth
472