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