1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "device/fido/cable/fido_ble_connection.h"
6 
7 #include <algorithm>
8 #include <ostream>
9 #include <utility>
10 
11 #include "base/barrier_closure.h"
12 #include "base/bind.h"
13 #include "base/callback_helpers.h"
14 #include "base/logging.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "build/build_config.h"
18 #include "components/device_event_log/device_event_log.h"
19 #include "device/bluetooth/bluetooth_gatt_connection.h"
20 #include "device/bluetooth/bluetooth_gatt_notify_session.h"
21 #include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
22 #include "device/bluetooth/bluetooth_remote_gatt_service.h"
23 #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
24 #include "device/fido/cable/fido_ble_uuids.h"
25 
26 namespace device {
27 
28 namespace {
29 
30 using ServiceRevisionsCallback =
31     base::OnceCallback<void(std::vector<FidoBleConnection::ServiceRevision>)>;
32 
ToString(BluetoothDevice::ConnectErrorCode error_code)33 constexpr const char* ToString(BluetoothDevice::ConnectErrorCode error_code) {
34   switch (error_code) {
35     case BluetoothDevice::ERROR_AUTH_CANCELED:
36       return "ERROR_AUTH_CANCELED";
37     case BluetoothDevice::ERROR_AUTH_FAILED:
38       return "ERROR_AUTH_FAILED";
39     case BluetoothDevice::ERROR_AUTH_REJECTED:
40       return "ERROR_AUTH_REJECTED";
41     case BluetoothDevice::ERROR_AUTH_TIMEOUT:
42       return "ERROR_AUTH_TIMEOUT";
43     case BluetoothDevice::ERROR_FAILED:
44       return "ERROR_FAILED";
45     case BluetoothDevice::ERROR_INPROGRESS:
46       return "ERROR_INPROGRESS";
47     case BluetoothDevice::ERROR_UNKNOWN:
48       return "ERROR_UNKNOWN";
49     case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE:
50       return "ERROR_UNSUPPORTED_DEVICE";
51     default:
52       NOTREACHED();
53       return "";
54   }
55 }
56 
ToString(BluetoothGattService::GattErrorCode error_code)57 constexpr const char* ToString(BluetoothGattService::GattErrorCode error_code) {
58   switch (error_code) {
59     case BluetoothGattService::GATT_ERROR_UNKNOWN:
60       return "GATT_ERROR_UNKNOWN";
61     case BluetoothGattService::GATT_ERROR_FAILED:
62       return "GATT_ERROR_FAILED";
63     case BluetoothGattService::GATT_ERROR_IN_PROGRESS:
64       return "GATT_ERROR_IN_PROGRESS";
65     case BluetoothGattService::GATT_ERROR_INVALID_LENGTH:
66       return "GATT_ERROR_INVALID_LENGTH";
67     case BluetoothGattService::GATT_ERROR_NOT_PERMITTED:
68       return "GATT_ERROR_NOT_PERMITTED";
69     case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED:
70       return "GATT_ERROR_NOT_AUTHORIZED";
71     case BluetoothGattService::GATT_ERROR_NOT_PAIRED:
72       return "GATT_ERROR_NOT_PAIRED";
73     case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED:
74       return "GATT_ERROR_NOT_SUPPORTED";
75     default:
76       NOTREACHED();
77       return "";
78   }
79 }
80 
operator <<(std::ostream & os,FidoBleConnection::ServiceRevision revision)81 std::ostream& operator<<(std::ostream& os,
82                          FidoBleConnection::ServiceRevision revision) {
83   switch (revision) {
84     case FidoBleConnection::ServiceRevision::kU2f11:
85       return os << "U2F 1.1";
86     case FidoBleConnection::ServiceRevision::kU2f12:
87       return os << "U2F 1.2";
88     case FidoBleConnection::ServiceRevision::kFido2:
89       return os << "FIDO2";
90   }
91 
92   NOTREACHED();
93   return os;
94 }
95 
OnWriteRemoteCharacteristic(FidoBleConnection::WriteCallback callback)96 void OnWriteRemoteCharacteristic(FidoBleConnection::WriteCallback callback) {
97   FIDO_LOG(DEBUG) << "Writing Remote Characteristic Succeeded.";
98   std::move(callback).Run(true);
99 }
100 
OnWriteRemoteCharacteristicError(FidoBleConnection::WriteCallback callback,BluetoothGattService::GattErrorCode error_code)101 void OnWriteRemoteCharacteristicError(
102     FidoBleConnection::WriteCallback callback,
103     BluetoothGattService::GattErrorCode error_code) {
104   FIDO_LOG(ERROR) << "Writing Remote Characteristic Failed: "
105                   << ToString(error_code);
106   std::move(callback).Run(false);
107 }
108 
OnReadServiceRevisionBitfield(ServiceRevisionsCallback callback,const std::vector<uint8_t> & value)109 void OnReadServiceRevisionBitfield(ServiceRevisionsCallback callback,
110                                    const std::vector<uint8_t>& value) {
111   if (value.empty()) {
112     FIDO_LOG(DEBUG) << "Service Revision Bitfield is empty.";
113     std::move(callback).Run({});
114     return;
115   }
116 
117   if (value.size() != 1u) {
118     FIDO_LOG(DEBUG) << "Service Revision Bitfield has unexpected size: "
119                     << value.size() << ". Ignoring all but the first byte.";
120   }
121 
122   const uint8_t bitset = value[0];
123   if (bitset & 0x1F) {
124     FIDO_LOG(DEBUG) << "Service Revision Bitfield has unexpected bits set: "
125                     << base::StringPrintf("0x%02X", bitset)
126                     << ". Ignoring all but the first three bits.";
127   }
128 
129   std::vector<FidoBleConnection::ServiceRevision> service_revisions;
130   for (auto revision : {FidoBleConnection::ServiceRevision::kU2f11,
131                         FidoBleConnection::ServiceRevision::kU2f12,
132                         FidoBleConnection::ServiceRevision::kFido2}) {
133     if (bitset & static_cast<uint8_t>(revision)) {
134       FIDO_LOG(DEBUG) << "Detected Support for " << revision << ".";
135       service_revisions.push_back(revision);
136     }
137   }
138 
139   std::move(callback).Run(std::move(service_revisions));
140 }
141 
OnReadServiceRevisionBitfieldError(ServiceRevisionsCallback callback,BluetoothGattService::GattErrorCode error_code)142 void OnReadServiceRevisionBitfieldError(
143     ServiceRevisionsCallback callback,
144     BluetoothGattService::GattErrorCode error_code) {
145   FIDO_LOG(ERROR) << "Error while reading Service Revision Bitfield: "
146                   << ToString(error_code);
147   std::move(callback).Run({});
148 }
149 
150 }  // namespace
151 
FidoBleConnection(BluetoothAdapter * adapter,std::string device_address,BluetoothUUID service_uuid,ReadCallback read_callback)152 FidoBleConnection::FidoBleConnection(BluetoothAdapter* adapter,
153                                      std::string device_address,
154                                      BluetoothUUID service_uuid,
155                                      ReadCallback read_callback)
156     : adapter_(adapter),
157       address_(std::move(device_address)),
158       read_callback_(std::move(read_callback)),
159       service_uuid_(service_uuid) {
160   DCHECK(adapter_);
161   adapter_->AddObserver(this);
162   DCHECK(!address_.empty());
163 }
164 
~FidoBleConnection()165 FidoBleConnection::~FidoBleConnection() {
166   adapter_->RemoveObserver(this);
167 }
168 
GetBleDevice()169 BluetoothDevice* FidoBleConnection::GetBleDevice() {
170   return adapter_->GetDevice(address());
171 }
172 
GetBleDevice() const173 const BluetoothDevice* FidoBleConnection::GetBleDevice() const {
174   return adapter_->GetDevice(address());
175 }
176 
Connect(ConnectionCallback callback)177 void FidoBleConnection::Connect(ConnectionCallback callback) {
178   auto* device = GetBleDevice();
179   if (!device) {
180     FIDO_LOG(ERROR) << "Failed to get Device.";
181     base::ThreadTaskRunnerHandle::Get()->PostTask(
182         FROM_HERE, base::BindOnce(std::move(callback), false));
183     return;
184   }
185 
186   pending_connection_callback_ = std::move(callback);
187   FIDO_LOG(DEBUG) << "Creating a GATT connection...";
188   // TODO(crbug.com/1007780): This function should take OnceCallbacks.
189   device->CreateGattConnection(
190       base::BindOnce(&FidoBleConnection::OnCreateGattConnection,
191                      weak_factory_.GetWeakPtr()),
192       base::BindOnce(&FidoBleConnection::OnCreateGattConnectionError,
193                      weak_factory_.GetWeakPtr()),
194       BluetoothUUID(kCableAdvertisementUUID128));
195 }
196 
ReadControlPointLength(ControlPointLengthCallback callback)197 void FidoBleConnection::ReadControlPointLength(
198     ControlPointLengthCallback callback) {
199   const auto* fido_service = GetFidoService();
200   if (!fido_service) {
201     base::ThreadTaskRunnerHandle::Get()->PostTask(
202         FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
203     return;
204   }
205 
206   if (!control_point_length_id_) {
207     FIDO_LOG(ERROR) << "Failed to get Control Point Length.";
208     base::ThreadTaskRunnerHandle::Get()->PostTask(
209         FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
210     return;
211   }
212 
213   BluetoothRemoteGattCharacteristic* control_point_length =
214       fido_service->GetCharacteristic(*control_point_length_id_);
215   if (!control_point_length) {
216     FIDO_LOG(ERROR) << "No Control Point Length characteristic present.";
217     base::ThreadTaskRunnerHandle::Get()->PostTask(
218         FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
219     return;
220   }
221 
222   FIDO_LOG(DEBUG) << "Read Control Point Length";
223   // Work around legacy APIs. Only one of the callbacks to
224   // ReadRemoteCharacteristic() gets invoked, but we don't know which one.
225   auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
226   control_point_length->ReadRemoteCharacteristic(
227       base::BindOnce(OnReadControlPointLength, copyable_callback),
228       base::BindOnce(OnReadControlPointLengthError, copyable_callback));
229 }
230 
WriteControlPoint(const std::vector<uint8_t> & data,WriteCallback callback)231 void FidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data,
232                                           WriteCallback callback) {
233   const auto* fido_service = GetFidoService();
234   if (!fido_service) {
235     base::ThreadTaskRunnerHandle::Get()->PostTask(
236         FROM_HERE, base::BindOnce(std::move(callback), false));
237     return;
238   }
239 
240   if (!control_point_id_) {
241     FIDO_LOG(ERROR) << "Failed to get Control Point.";
242     base::ThreadTaskRunnerHandle::Get()->PostTask(
243         FROM_HERE, base::BindOnce(std::move(callback), false));
244     return;
245   }
246 
247   BluetoothRemoteGattCharacteristic* control_point =
248       fido_service->GetCharacteristic(*control_point_id_);
249   if (!control_point) {
250     FIDO_LOG(ERROR) << "Control Point characteristic not present.";
251     base::ThreadTaskRunnerHandle::Get()->PostTask(
252         FROM_HERE, base::BindOnce(std::move(callback), false));
253     return;
254   }
255 
256   // Attempt a write without response for performance reasons. Fall back to a
257   // confirmed write when the characteristic does not provide the required
258   // property.
259   BluetoothRemoteGattCharacteristic::WriteType write_type =
260       (control_point->GetProperties() &
261        BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE)
262           ? BluetoothRemoteGattCharacteristic::WriteType::kWithoutResponse
263           : BluetoothRemoteGattCharacteristic::WriteType::kWithResponse;
264 
265   FIDO_LOG(DEBUG) << "Wrote Control Point.";
266   auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
267   control_point->WriteRemoteCharacteristic(
268       data, write_type,
269       base::BindOnce(OnWriteRemoteCharacteristic, copyable_callback),
270       base::BindOnce(OnWriteRemoteCharacteristicError, copyable_callback));
271 }
272 
OnCreateGattConnection(std::unique_ptr<BluetoothGattConnection> connection)273 void FidoBleConnection::OnCreateGattConnection(
274     std::unique_ptr<BluetoothGattConnection> connection) {
275   FIDO_LOG(DEBUG) << "GATT connection created";
276   DCHECK(pending_connection_callback_);
277   connection_ = std::move(connection);
278 
279   BluetoothDevice* device = adapter_->GetDevice(address_);
280   if (!device) {
281     FIDO_LOG(ERROR) << "Failed to get Device.";
282     base::ThreadTaskRunnerHandle::Get()->PostTask(
283         FROM_HERE,
284         base::BindOnce(std::move(pending_connection_callback_), false));
285     return;
286   }
287 
288   if (!device->IsGattServicesDiscoveryComplete()) {
289     FIDO_LOG(DEBUG) << "Waiting for GATT service discovery to complete";
290     waiting_for_gatt_discovery_ = true;
291     return;
292   }
293 
294   ConnectToFidoService();
295 }
296 
OnCreateGattConnectionError(BluetoothDevice::ConnectErrorCode error_code)297 void FidoBleConnection::OnCreateGattConnectionError(
298     BluetoothDevice::ConnectErrorCode error_code) {
299   DCHECK(pending_connection_callback_);
300   FIDO_LOG(ERROR) << "CreateGattConnection() failed: " << ToString(error_code);
301   base::ThreadTaskRunnerHandle::Get()->PostTask(
302       FROM_HERE,
303       base::BindOnce(std::move(pending_connection_callback_), false));
304 }
305 
ConnectToFidoService()306 void FidoBleConnection::ConnectToFidoService() {
307   FIDO_LOG(EVENT) << "Attempting to connect to a Fido service.";
308   DCHECK(pending_connection_callback_);
309   const auto* fido_service = GetFidoService();
310   if (!fido_service) {
311     FIDO_LOG(ERROR) << "Failed to get Fido Service.";
312     base::ThreadTaskRunnerHandle::Get()->PostTask(
313         FROM_HERE,
314         base::BindOnce(std::move(pending_connection_callback_), false));
315     return;
316   }
317 
318   for (const auto* characteristic : fido_service->GetCharacteristics()) {
319     std::string uuid = characteristic->GetUUID().canonical_value();
320     if (uuid == kFidoControlPointLengthUUID) {
321       control_point_length_id_ = characteristic->GetIdentifier();
322       FIDO_LOG(DEBUG) << "Got Fido Control Point Length: "
323                       << *control_point_length_id_;
324       continue;
325     }
326 
327     if (uuid == kFidoControlPointUUID) {
328       control_point_id_ = characteristic->GetIdentifier();
329       FIDO_LOG(DEBUG) << "Got Fido Control Point: " << *control_point_id_;
330       continue;
331     }
332 
333     if (uuid == kFidoStatusUUID) {
334       status_id_ = characteristic->GetIdentifier();
335       FIDO_LOG(DEBUG) << "Got Fido Status: " << *status_id_;
336       continue;
337     }
338 
339     if (uuid == kFidoServiceRevisionUUID) {
340       service_revision_id_ = characteristic->GetIdentifier();
341       FIDO_LOG(DEBUG) << "Got Fido Service Revision: " << *service_revision_id_;
342       continue;
343     }
344 
345     if (uuid == kFidoServiceRevisionBitfieldUUID) {
346       service_revision_bitfield_id_ = characteristic->GetIdentifier();
347       FIDO_LOG(DEBUG) << "Got Fido Service Revision Bitfield: "
348                       << *service_revision_bitfield_id_;
349       continue;
350     }
351 
352     FIDO_LOG(DEBUG) << "Unknown FIDO service characteristic: " << uuid;
353   }
354 
355   if (!control_point_length_id_ || !control_point_id_ || !status_id_ ||
356       (!service_revision_id_ && !service_revision_bitfield_id_)) {
357     FIDO_LOG(ERROR) << "Fido Characteristics missing.";
358     base::ThreadTaskRunnerHandle::Get()->PostTask(
359         FROM_HERE,
360         base::BindOnce(std::move(pending_connection_callback_), false));
361     return;
362   }
363 
364   // In case the bitfield characteristic is present, the client has to select a
365   // supported version by writing the corresponding bit. Reference:
366   // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#ble-protocol-overview
367   if (service_revision_bitfield_id_) {
368     // This callback is only repeating so that it can be bound to two different
369     // callbacks.
370     auto callback = base::BindRepeating(
371         &FidoBleConnection::OnReadServiceRevisions, weak_factory_.GetWeakPtr());
372     fido_service->GetCharacteristic(*service_revision_bitfield_id_)
373         ->ReadRemoteCharacteristic(
374             base::BindOnce(OnReadServiceRevisionBitfield, callback),
375             base::BindOnce(OnReadServiceRevisionBitfieldError, callback));
376     return;
377   }
378 
379   StartNotifySession();
380 }
381 
OnReadServiceRevisions(std::vector<ServiceRevision> service_revisions)382 void FidoBleConnection::OnReadServiceRevisions(
383     std::vector<ServiceRevision> service_revisions) {
384   DCHECK(pending_connection_callback_);
385   if (service_revisions.empty()) {
386     FIDO_LOG(ERROR) << "Could not obtain Service Revisions.";
387     std::move(pending_connection_callback_).Run(false);
388     return;
389   }
390 
391   // Write the most recent supported service revision back to the
392   // characteristic. Note that this information is currently not used in another
393   // way, as we will still attempt a CTAP GetInfo() command, even if only U2F is
394   // supported.
395   // TODO(https://crbug.com/780078): Consider short circuiting to the
396   // U2F logic if FIDO2 is not supported.
397   DCHECK_EQ(
398       *std::min_element(service_revisions.begin(), service_revisions.end()),
399       service_revisions.back());
400   WriteServiceRevision(service_revisions.back());
401 }
402 
WriteServiceRevision(ServiceRevision service_revision)403 void FidoBleConnection::WriteServiceRevision(ServiceRevision service_revision) {
404   auto callback = base::BindOnce(&FidoBleConnection::OnServiceRevisionWritten,
405                                  weak_factory_.GetWeakPtr());
406 
407   const auto* fido_service = GetFidoService();
408   if (!fido_service) {
409     base::ThreadTaskRunnerHandle::Get()->PostTask(
410         FROM_HERE, base::BindOnce(std::move(callback), false));
411     return;
412   }
413 
414   auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
415   DCHECK(service_revision_bitfield_id_);
416   fido_service->GetCharacteristic(*service_revision_bitfield_id_)
417       ->WriteRemoteCharacteristic(
418           {static_cast<uint8_t>(service_revision)},
419           BluetoothRemoteGattCharacteristic::WriteType::kWithResponse,
420           base::BindOnce(OnWriteRemoteCharacteristic, copyable_callback),
421           base::BindOnce(OnWriteRemoteCharacteristicError, copyable_callback));
422 }
423 
OnServiceRevisionWritten(bool success)424 void FidoBleConnection::OnServiceRevisionWritten(bool success) {
425   DCHECK(pending_connection_callback_);
426   if (success) {
427     FIDO_LOG(DEBUG) << "Service Revision successfully written.";
428     StartNotifySession();
429     return;
430   }
431 
432   FIDO_LOG(ERROR) << "Failed to write Service Revision.";
433   std::move(pending_connection_callback_).Run(false);
434 }
435 
StartNotifySession()436 void FidoBleConnection::StartNotifySession() {
437   DCHECK(pending_connection_callback_);
438   const auto* fido_service = GetFidoService();
439   if (!fido_service) {
440     base::ThreadTaskRunnerHandle::Get()->PostTask(
441         FROM_HERE,
442         base::BindOnce(std::move(pending_connection_callback_), false));
443     return;
444   }
445 
446   DCHECK(status_id_);
447   fido_service->GetCharacteristic(*status_id_)
448       ->StartNotifySession(
449           base::BindOnce(&FidoBleConnection::OnStartNotifySession,
450                          weak_factory_.GetWeakPtr()),
451           base::BindOnce(&FidoBleConnection::OnStartNotifySessionError,
452                          weak_factory_.GetWeakPtr()));
453 }
454 
OnStartNotifySession(std::unique_ptr<BluetoothGattNotifySession> notify_session)455 void FidoBleConnection::OnStartNotifySession(
456     std::unique_ptr<BluetoothGattNotifySession> notify_session) {
457   notify_session_ = std::move(notify_session);
458   FIDO_LOG(DEBUG) << "Created notification session. Connection established.";
459   std::move(pending_connection_callback_).Run(true);
460 }
461 
OnStartNotifySessionError(BluetoothGattService::GattErrorCode error_code)462 void FidoBleConnection::OnStartNotifySessionError(
463     BluetoothGattService::GattErrorCode error_code) {
464   FIDO_LOG(ERROR) << "StartNotifySession() failed: " << ToString(error_code);
465   std::move(pending_connection_callback_).Run(false);
466 }
467 
DeviceAddressChanged(BluetoothAdapter * adapter,BluetoothDevice * device,const std::string & old_address)468 void FidoBleConnection::DeviceAddressChanged(BluetoothAdapter* adapter,
469                                              BluetoothDevice* device,
470                                              const std::string& old_address) {
471   if (address_ == old_address)
472     address_ = device->GetAddress();
473 }
474 
GattCharacteristicValueChanged(BluetoothAdapter * adapter,BluetoothRemoteGattCharacteristic * characteristic,const std::vector<uint8_t> & value)475 void FidoBleConnection::GattCharacteristicValueChanged(
476     BluetoothAdapter* adapter,
477     BluetoothRemoteGattCharacteristic* characteristic,
478     const std::vector<uint8_t>& value) {
479   if (characteristic->GetIdentifier() != status_id_)
480     return;
481   FIDO_LOG(DEBUG) << "Status characteristic value changed.";
482   read_callback_.Run(value);
483 }
484 
GattServicesDiscovered(BluetoothAdapter * adapter,BluetoothDevice * device)485 void FidoBleConnection::GattServicesDiscovered(BluetoothAdapter* adapter,
486                                                BluetoothDevice* device) {
487   if (adapter != adapter_ || device->GetAddress() != address_) {
488     return;
489   }
490 
491   FIDO_LOG(DEBUG) << "GATT services discovered for " << device->GetAddress();
492 
493   if (waiting_for_gatt_discovery_) {
494     waiting_for_gatt_discovery_ = false;
495     ConnectToFidoService();
496   }
497 }
498 
GetFidoService()499 const BluetoothRemoteGattService* FidoBleConnection::GetFidoService() {
500   if (!connection_ || !connection_->IsConnected()) {
501     FIDO_LOG(ERROR) << "No BLE connection.";
502     return nullptr;
503   }
504 
505   DCHECK_EQ(address_, connection_->GetDeviceAddress());
506   BluetoothDevice* device = GetBleDevice();
507 
508   for (const auto* service : device->GetGattServices()) {
509     if (service->GetUUID() == service_uuid_) {
510       return service;
511     }
512   }
513 
514   FIDO_LOG(ERROR) << "No Fido service present.";
515   return nullptr;
516 }
517 
518 // static
OnReadControlPointLength(ControlPointLengthCallback callback,const std::vector<uint8_t> & value)519 void FidoBleConnection::OnReadControlPointLength(
520     ControlPointLengthCallback callback,
521     const std::vector<uint8_t>& value) {
522   if (value.size() != 2) {
523     FIDO_LOG(ERROR) << "Wrong Control Point Length: " << value.size()
524                     << " bytes";
525     std::move(callback).Run(base::nullopt);
526     return;
527   }
528 
529   uint16_t length = (value[0] << 8) | value[1];
530   FIDO_LOG(DEBUG) << "Control Point Length: " << length;
531   std::move(callback).Run(length);
532 }
533 
534 // static
OnReadControlPointLengthError(ControlPointLengthCallback callback,BluetoothGattService::GattErrorCode error_code)535 void FidoBleConnection::OnReadControlPointLengthError(
536     ControlPointLengthCallback callback,
537     BluetoothGattService::GattErrorCode error_code) {
538   FIDO_LOG(ERROR) << "Error reading Control Point Length: "
539                   << ToString(error_code);
540   std::move(callback).Run(base::nullopt);
541 }
542 
543 }  // namespace device
544