2 // Copyright 2015 Google, Inc.
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:
8 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 #define LOG_TAG "bt_gatts"
19 #include "gatt_server_old.h"
25 #include <base/bind.h>
26 #include <base/bind_helpers.h>
27 #include <base/callback.h>
30 #include <condition_variable>
36 #include <unordered_map>
37 #include <unordered_set>
40 #include <hardware/bluetooth.h>
41 #include <hardware/bt_gatt.h>
43 #include "service/hal/bluetooth_interface.h"
44 #include "service/logging_helpers.h"
46 #include "osi/include/log.h"
47 #include "osi/include/osi.h"
51 const size_t kMaxGattAttributeSize = 512;
52 std::vector<btgatt_db_element_t> pending_svc_decl;
53 std::unordered_set<int> blob_index;
55 // TODO(icoolidge): Support multiple instances
56 // TODO(armansito): Remove this variable. No point of having this if
57 // each bluetooth::gatt::Server instance already keeps a pointer to the
58 // ServerInternals that is associated with it (which is much cleaner). It looks
59 // like this variable exists because the btif callbacks don't allow the
60 // upper-layer to pass user data to them. We could:
62 // 1. Fix the btif callbacks so that some sort of continuation can be
63 // attached to a callback. This might be a long shot since the callback
64 // interface doesn't allow more than one caller to register its own callbacks
65 // (which might be what we want though, since this would make the API more
68 // 2. Allow creation of Server objects using a factory method that returns
69 // the result asynchronously in a base::Callback. The RegisterServerCallback
70 // provides an |app_uuid|, which can be used to store callback structures in
71 // a map and lazily instantiate the Server and invoke the correct callback.
72 // This is a general pattern that we should use throughout the daemon, since
73 // all operations can timeout or fail and this is best reported in an
74 // asynchronous base::Callback.
76 static bluetooth::gatt::ServerInternals* g_internal = nullptr;
78 enum { kPipeReadEnd = 0, kPipeWriteEnd = 1, kPipeNumEnds = 2 };
85 struct Characteristic {
88 std::vector<uint8_t> blob;
90 // Support synchronized blob updates by latching under mutex.
91 std::vector<uint8_t> next_blob;
92 bool next_blob_pending;
96 struct ServerInternals {
100 bt_status_t AddCharacteristic(const Uuid& uuid, uint8_t properties,
101 uint16_t permissions);
103 // This maps API attribute Uuids to BlueDroid handles.
104 std::map<Uuid, int> uuid_to_attribute;
106 // The attribute cache, indexed by BlueDroid handles.
107 std::unordered_map<int, Characteristic> characteristics;
109 // Associate a control attribute with its value attribute.
110 std::unordered_map<int, int> controlled_blobs;
112 ScanResults scan_results;
115 const btgatt_interface_t* gatt;
119 std::set<int> connections;
122 std::condition_variable api_synchronize;
123 int pipefd[kPipeNumEnds];
127 } // namespace bluetooth
131 /** Callback invoked in response to register_server */
132 void RegisterServerCallback(int status, int server_if,
133 const bluetooth::Uuid& app_uuid) {
134 LOG_INFO(LOG_TAG, "%s: status:%d server_if:%d app_uuid:%p", __func__, status,
135 server_if, &app_uuid);
137 g_internal->server_if = server_if;
139 pending_svc_decl.push_back(
140 {.type = BTGATT_DB_PRIMARY_SERVICE, .uuid = app_uuid});
143 void ServiceAddedCallback(int status, int server_if,
144 std::vector<btgatt_db_element_t> service) {
145 LOG_INFO(LOG_TAG, "%s: status:%d server_if:%d count:%zu svc_handle:%d",
146 __func__, status, server_if, service.size(),
147 service[0].attribute_handle);
149 std::lock_guard<std::mutex> lock(g_internal->lock);
150 g_internal->server_if = server_if;
152 g_internal->service_handle = service[0].attribute_handle;
154 uint16_t prev_char_handle = 0;
155 uint16_t prev_char_properties = 0;
156 for (size_t i = 1; i < service.size(); i++) {
157 const btgatt_db_element_t& el = service[i];
158 if (el.type == BTGATT_DB_DESCRIPTOR) {
159 LOG_INFO(LOG_TAG, "%s: descr_handle:%d", __func__, el.attribute_handle);
160 } else if (el.type == BTGATT_DB_CHARACTERISTIC) {
161 bluetooth::Uuid id(el.uuid);
162 uint16_t char_handle = el.attribute_handle;
164 LOG_INFO(LOG_TAG, "%s: char_handle:%d", __func__, char_handle);
166 g_internal->uuid_to_attribute[id] = char_handle;
167 g_internal->characteristics[char_handle].uuid = id;
168 g_internal->characteristics[char_handle].blob_section = 0;
170 // If the added characteristic is blob
171 if (blob_index.find(i) != blob_index.end()) {
172 // Finally, associate the control attribute with the value attribute.
173 // Also, initialize the control attribute to a readable zero.
174 const uint16_t control_attribute = char_handle;
175 const uint16_t blob_attribute = prev_char_handle;
176 g_internal->controlled_blobs[control_attribute] = blob_attribute;
177 g_internal->characteristics[blob_attribute].notify =
178 prev_char_properties & bluetooth::gatt::kPropertyNotify;
180 bluetooth::gatt::Characteristic& ctrl =
181 g_internal->characteristics[control_attribute];
182 ctrl.next_blob.clear();
183 ctrl.next_blob.push_back(0);
184 ctrl.next_blob_pending = true;
185 ctrl.blob_section = 0;
188 prev_char_handle = char_handle;
189 prev_char_properties = el.properties;
193 pending_svc_decl.clear();
196 // The Uuid provided here is unimportant, and is only used to satisfy
198 // It must be different than any other registered Uuid.
199 bluetooth::Uuid client_id = bluetooth::Uuid::GetRandom();
201 bt_status_t btstat = g_internal->gatt->client->register_client(client_id);
202 if (btstat != BT_STATUS_SUCCESS) {
203 LOG_ERROR(LOG_TAG, "%s: Failed to register client", __func__);
207 void RequestReadCallback(int conn_id, int trans_id, const RawAddress& bda,
208 int attr_handle, int attribute_offset_octets,
210 std::lock_guard<std::mutex> lock(g_internal->lock);
212 bluetooth::gatt::Characteristic& ch =
213 g_internal->characteristics[attr_handle];
215 // Latch next_blob to blob on a 'fresh' read.
216 if (ch.next_blob_pending && attribute_offset_octets == 0 &&
217 ch.blob_section == 0) {
218 std::swap(ch.blob, ch.next_blob);
219 ch.next_blob_pending = false;
222 const size_t blob_offset_octets =
223 std::min(ch.blob.size(), ch.blob_section * kMaxGattAttributeSize);
224 const size_t blob_remaining = ch.blob.size() - blob_offset_octets;
225 const size_t attribute_size = std::min(kMaxGattAttributeSize, blob_remaining);
227 std::string addr(BtAddrString(&bda));
229 "%s: connection:%d (%s) reading attr:%d attribute_offset_octets:%d "
230 "blob_section:%u (is_long:%u)",
231 __func__, conn_id, addr.c_str(), attr_handle,
232 attribute_offset_octets, ch.blob_section, is_long);
234 btgatt_response_t response;
235 response.attr_value.len = 0;
237 if (attribute_offset_octets < static_cast<int>(attribute_size)) {
238 std::copy(ch.blob.begin() + blob_offset_octets + attribute_offset_octets,
239 ch.blob.begin() + blob_offset_octets + attribute_size,
240 response.attr_value.value);
241 response.attr_value.len = attribute_size - attribute_offset_octets;
244 response.attr_value.handle = attr_handle;
245 response.attr_value.offset = attribute_offset_octets;
246 response.attr_value.auth_req = 0;
247 g_internal->gatt->server->send_response(conn_id, trans_id, 0, response);
250 void RequestWriteCallback(int conn_id, int trans_id, const RawAddress& bda,
251 int attr_handle, int attribute_offset, bool need_rsp,
252 bool is_prep, std::vector<uint8_t> value) {
253 std::string addr(BtAddrString(&bda));
255 "%s: connection:%d (%s:trans:%d) write attr:%d attribute_offset:%d "
257 "need_resp:%u is_prep:%u",
258 __func__, conn_id, addr.c_str(), trans_id, attr_handle,
259 attribute_offset, value.size(), need_rsp, is_prep);
261 std::lock_guard<std::mutex> lock(g_internal->lock);
263 bluetooth::gatt::Characteristic& ch =
264 g_internal->characteristics[attr_handle];
266 ch.blob.resize(attribute_offset + value.size());
268 std::copy(value.begin(), value.end(), ch.blob.begin() + attribute_offset);
270 auto target_blob = g_internal->controlled_blobs.find(attr_handle);
271 // If this is a control attribute, adjust offset of the target blob.
272 if (target_blob != g_internal->controlled_blobs.end() &&
273 ch.blob.size() == 1u) {
274 g_internal->characteristics[target_blob->second].blob_section = ch.blob[0];
275 LOG_INFO(LOG_TAG, "%s: updating attribute %d blob_section to %u", __func__,
276 target_blob->second, ch.blob[0]);
277 } else if (!is_prep) {
278 // This is a single frame characteristic write.
279 // Notify upwards because we're done now.
280 const bluetooth::Uuid::UUID128Bit& attr_uuid = ch.uuid.To128BitBE();
282 OSI_NO_INTR(status = write(g_internal->pipefd[kPipeWriteEnd],
283 attr_uuid.data(), attr_uuid.size()));
285 LOG_ERROR(LOG_TAG, "%s: write failed: %s", __func__, strerror(errno));
287 // This is a multi-frame characteristic write.
288 // Wait for an 'RequestExecWriteCallback' to notify completion.
289 g_internal->last_write = ch.uuid;
292 // Respond only if needed.
293 if (!need_rsp) return;
295 btgatt_response_t response;
296 response.attr_value.handle = attr_handle;
297 response.attr_value.offset = attribute_offset;
298 response.attr_value.len = value.size();
299 response.attr_value.auth_req = 0;
300 // Provide written data back to sender for the response.
301 // Remote stacks use this to validate the success of the write.
302 std::copy(value.begin(), value.end(), response.attr_value.value);
303 g_internal->gatt->server->send_response(conn_id, trans_id, 0, response);
306 void RequestExecWriteCallback(int conn_id, int trans_id, const RawAddress& bda,
308 std::string addr(BtAddrString(&bda));
309 LOG_INFO(LOG_TAG, "%s: connection:%d (%s:trans:%d) exec_write:%d", __func__,
310 conn_id, addr.c_str(), trans_id, exec_write);
312 // This 'response' data is unused for ExecWriteResponses.
313 // It is only used to pass BlueDroid argument validation.
314 btgatt_response_t response = {};
315 g_internal->gatt->server->send_response(conn_id, trans_id, 0, response);
317 if (!exec_write) return;
319 std::lock_guard<std::mutex> lock(g_internal->lock);
320 // Communicate the attribute Uuid as notification of a write update.
321 const bluetooth::Uuid::UUID128Bit uuid = g_internal->last_write.To128BitBE();
323 OSI_NO_INTR(status = write(g_internal->pipefd[kPipeWriteEnd], uuid.data(),
326 LOG_ERROR(LOG_TAG, "%s: write failed: %s", __func__, strerror(errno));
329 void ConnectionCallback(int conn_id, int server_if, int connected,
330 const RawAddress& bda) {
331 std::string addr(BtAddrString(&bda));
332 LOG_INFO(LOG_TAG, "%s: connection:%d server_if:%d connected:%d addr:%s",
333 __func__, conn_id, server_if, connected, addr.c_str());
334 if (connected == 1) {
335 g_internal->connections.insert(conn_id);
336 } else if (connected == 0) {
337 g_internal->connections.erase(conn_id);
341 void EnableAdvertisingCallback(uint8_t status) {
342 LOG_INFO(LOG_TAG, "%s: status:%d", __func__, status);
343 // This terminates a Start call.
344 std::lock_guard<std::mutex> lock(g_internal->lock);
345 g_internal->api_synchronize.notify_one();
348 void RegisterClientCallback(int status, int client_if,
349 const bluetooth::Uuid& app_uuid) {
350 LOG_INFO(LOG_TAG, "%s: status:%d client_if:%d uuid[0]:%s", __func__, status,
351 client_if, app_uuid.ToString().c_str());
352 g_internal->client_if = client_if;
354 // Setup our advertisement. This has no callback.
355 g_internal->gatt->advertiser->SetData(0 /* std_inst */, false,
356 {/*TODO: put inverval 2,2 here*/},
359 g_internal->gatt->advertiser->Enable(
360 0 /* std_inst */, true, base::Bind(&EnableAdvertisingCallback),
361 0 /* no duration */, 0 /* no maxExtAdvEvent*/, base::DoNothing());
364 void ServiceStoppedCallback(int status, int server_if, int srvc_handle) {
365 LOG_INFO(LOG_TAG, "%s: status:%d server_if:%d srvc_handle:%d", __func__,
366 status, server_if, srvc_handle);
367 // This terminates a Stop call.
368 // TODO(icoolidge): make this symmetric with start
369 std::lock_guard<std::mutex> lock(g_internal->lock);
370 g_internal->api_synchronize.notify_one();
373 void ScanResultCallback(uint16_t ble_evt_type, uint8_t addr_type,
374 RawAddress* bda, uint8_t ble_primary_phy,
375 uint8_t ble_secondary_phy, uint8_t ble_advertising_sid,
376 int8_t ble_tx_power, int8_t rssi,
377 uint16_t ble_periodic_adv_int,
378 std::vector<uint8_t> adv_data) {
379 std::string addr(BtAddrString(bda));
380 std::lock_guard<std::mutex> lock(g_internal->lock);
381 g_internal->scan_results[addr] = rssi;
384 void ClientConnectCallback(int conn_id, int status, int client_if,
385 const RawAddress& bda) {
386 std::string addr(BtAddrString(&bda));
387 LOG_INFO(LOG_TAG, "%s: conn_id:%d status:%d client_if:%d %s", __func__,
388 conn_id, status, client_if, addr.c_str());
391 void ClientDisconnectCallback(int conn_id, int status, int client_if,
392 const RawAddress& bda) {
393 std::string addr(BtAddrString(&bda));
394 LOG_INFO(LOG_TAG, "%s: conn_id:%d status:%d client_if:%d %s", __func__,
395 conn_id, status, client_if, addr.c_str());
398 void IndicationSentCallback(UNUSED_ATTR int conn_id, UNUSED_ATTR int status) {
399 // TODO(icoolidge): what to do
402 void ResponseConfirmationCallback(UNUSED_ATTR int status,
403 UNUSED_ATTR int handle) {
404 // TODO(icoolidge): what to do
407 const btgatt_server_callbacks_t gatt_server_callbacks = {
408 RegisterServerCallback,
410 ServiceAddedCallback,
411 ServiceStoppedCallback,
412 nullptr, /* service_deleted_cb */
415 RequestWriteCallback,
416 RequestWriteCallback,
417 RequestExecWriteCallback,
418 ResponseConfirmationCallback,
419 IndicationSentCallback,
420 nullptr, /* congestion_cb*/
421 nullptr, /* mtu_changed_cb */
422 nullptr, /* phy_update_cb */
423 nullptr, /* conn_update_cb */
426 // TODO(eisenbach): Refactor GATT interface to not require servers
427 // to refer to the client interface.
428 const btgatt_client_callbacks_t gatt_client_callbacks = {
429 RegisterClientCallback,
430 ClientConnectCallback,
431 ClientDisconnectCallback,
432 nullptr, /* search_complete_cb; */
433 nullptr, /* register_for_notification_cb; */
434 nullptr, /* notify_cb; */
435 nullptr, /* read_characteristic_cb; */
436 nullptr, /* write_characteristic_cb; */
437 nullptr, /* read_descriptor_cb; */
438 nullptr, /* write_descriptor_cb; */
439 nullptr, /* execute_write_cb; */
440 nullptr, /* read_remote_rssi_cb; */
441 nullptr, /* configure_mtu_cb; */
442 nullptr, /* congestion_cb; */
443 nullptr, /* get_gatt_db_cb; */
444 nullptr, /* services_removed_cb */
445 nullptr, /* services_added_cb */
446 nullptr, /* phy_update_cb */
447 nullptr, /* conn_update_cb */
450 const btgatt_scanner_callbacks_t gatt_scanner_callbacks = {
452 nullptr, /* batchscan_reports_cb; */
453 nullptr, /* batchscan_threshold_cb; */
454 nullptr, /* track_adv_event_cb; */
457 const btgatt_callbacks_t gatt_callbacks = {
458 /** Set to sizeof(btgatt_callbacks_t) */
459 sizeof(btgatt_callbacks_t),
461 /** GATT Client callbacks */
462 &gatt_client_callbacks,
464 /** GATT Server callbacks */
465 &gatt_server_callbacks,
467 /** GATT Server callbacks */
468 &gatt_scanner_callbacks,
473 namespace bluetooth {
476 int ServerInternals::Initialize() {
477 // Get the interface to the GATT profile.
478 const bt_interface_t* bt_iface =
479 hal::BluetoothInterface::Get()->GetHALInterface();
480 gatt = reinterpret_cast<const btgatt_interface_t*>(
481 bt_iface->get_profile_interface(BT_PROFILE_GATT_ID));
483 LOG_ERROR(LOG_TAG, "Error getting GATT interface");
487 bt_status_t btstat = gatt->init(&gatt_callbacks);
488 if (btstat != BT_STATUS_SUCCESS) {
489 LOG_ERROR(LOG_TAG, "Failed to initialize gatt interface");
493 int status = pipe(pipefd);
495 LOG_ERROR(LOG_TAG, "pipe creation failed: %s", strerror(errno));
502 bt_status_t ServerInternals::AddCharacteristic(const Uuid& uuid,
504 uint16_t permissions) {
505 pending_svc_decl.push_back({.type = BTGATT_DB_CHARACTERISTIC,
507 .properties = properties,
508 .permissions = permissions});
509 return BT_STATUS_SUCCESS;
512 ServerInternals::ServerInternals()
517 pipefd{INVALID_FD, INVALID_FD} {}
519 ServerInternals::~ServerInternals() {
520 if (pipefd[0] != INVALID_FD) close(pipefd[0]);
521 if (pipefd[1] != INVALID_FD) close(pipefd[1]);
523 gatt->server->delete_service(server_if, service_handle);
524 gatt->server->unregister_server(server_if);
525 gatt->client->unregister_client(client_if);
528 Server::Server() : internal_(nullptr) {}
532 bool Server::Initialize(const Uuid& service_id, int* gatt_pipe) {
533 internal_.reset(new ServerInternals);
535 LOG_ERROR(LOG_TAG, "Error creating internals");
538 g_internal = internal_.get();
540 std::unique_lock<std::mutex> lock(internal_->lock);
541 int status = internal_->Initialize();
543 LOG_ERROR(LOG_TAG, "Error initializing internals");
547 bt_status_t btstat = internal_->gatt->server->register_server(service_id);
548 if (btstat != BT_STATUS_SUCCESS) {
549 LOG_ERROR(LOG_TAG, "Failed to register server");
553 internal_->api_synchronize.wait(lock);
554 // TODO(icoolidge): Better error handling.
555 if (internal_->server_if == 0) {
556 LOG_ERROR(LOG_TAG, "Initialization of server failed");
560 *gatt_pipe = internal_->pipefd[kPipeReadEnd];
561 LOG_INFO(LOG_TAG, "Server Initialize succeeded");
565 bool Server::SetAdvertisement(const std::vector<Uuid>& ids,
566 const std::vector<uint8_t>& service_data,
567 const std::vector<uint8_t>& manufacturer_data,
568 bool transmit_name) {
569 // std::vector<uint8_t> id_data;
570 // const auto& mutable_manufacturer_data = manufacturer_data;
571 // const auto& mutable_service_data = service_data;
573 // for (const Uuid &id : ids) {
574 // const auto le_id = id.To128BitLE();
575 // id_data.insert(id_data.end(), le_id.begin(), le_id.end());
578 std::lock_guard<std::mutex> lock(internal_->lock);
580 // Setup our advertisement. This has no callback.
581 internal_->gatt->advertiser->SetData(0, false, /* beacon, not scan response */
582 {}, base::DoNothing());
583 // transmit_name, /* name */
585 // mutable_manufacturer_data,
586 // mutable_service_data,
591 bool Server::SetScanResponse(const std::vector<Uuid>& ids,
592 const std::vector<uint8_t>& service_data,
593 const std::vector<uint8_t>& manufacturer_data,
594 bool transmit_name) {
595 // std::vector<uint8_t> id_data;
596 // const auto& mutable_manufacturer_data = manufacturer_data;
597 // const auto& mutable_service_data = service_data;
599 // for (const Uuid &id : ids) {
600 // const auto le_id = id.To128BitLE();
601 // id_data.insert(id_data.end(), le_id.begin(), le_id.end());
604 std::lock_guard<std::mutex> lock(internal_->lock);
606 // Setup our advertisement. This has no callback.
607 internal_->gatt->advertiser->SetData(0, true, /* scan response */
608 {}, base::DoNothing());
609 // transmit_name, /* name */
610 // false, /* no txpower */
612 // 0, /* appearance */
613 // mutable_manufacturer_data,
614 // mutable_service_data,
619 bool Server::AddCharacteristic(const Uuid& id, int properties,
621 std::unique_lock<std::mutex> lock(internal_->lock);
623 internal_->AddCharacteristic(id, properties, permissions);
624 if (btstat != BT_STATUS_SUCCESS) {
625 LOG_ERROR(LOG_TAG, "Failed to add characteristic to service: 0x%04x",
626 internal_->service_handle);
629 internal_->api_synchronize.wait(lock);
630 const int handle = internal_->uuid_to_attribute[id];
631 internal_->characteristics[handle].notify = properties & kPropertyNotify;
635 bool Server::AddBlob(const Uuid& id, const Uuid& control_id, int properties,
637 std::unique_lock<std::mutex> lock(internal_->lock);
639 // First, add the primary attribute (characteristic value)
641 internal_->AddCharacteristic(id, properties, permissions);
642 if (btstat != BT_STATUS_SUCCESS) {
643 LOG_ERROR(LOG_TAG, "Failed to set scan response data");
647 // Next, add the secondary attribute (blob control).
648 // Control attributes have fixed permissions/properties.
649 // Remember position at which blob was added.
650 blob_index.insert(pending_svc_decl.size());
652 internal_->AddCharacteristic(control_id, kPropertyRead | kPropertyWrite,
653 kPermissionRead | kPermissionWrite);
658 bool Server::Start() {
659 std::unique_lock<std::mutex> lock(internal_->lock);
660 bt_status_t btstat = internal_->gatt->server->add_service(
661 internal_->server_if, pending_svc_decl);
662 if (btstat != BT_STATUS_SUCCESS) {
663 LOG_ERROR(LOG_TAG, "Failed to start service with handle: 0x%04x",
664 internal_->service_handle);
667 internal_->api_synchronize.wait(lock);
671 bool Server::Stop() {
672 std::unique_lock<std::mutex> lock(internal_->lock);
673 bt_status_t btstat = internal_->gatt->server->stop_service(
674 internal_->server_if, internal_->service_handle);
675 if (btstat != BT_STATUS_SUCCESS) {
676 LOG_ERROR(LOG_TAG, "Failed to stop service with handle: 0x%04x",
677 internal_->service_handle);
680 internal_->api_synchronize.wait(lock);
684 bool Server::ScanEnable() {
685 internal_->gatt->scanner->Scan(true);
689 bool Server::ScanDisable() {
690 internal_->gatt->scanner->Scan(false);
694 bool Server::GetScanResults(ScanResults* results) {
695 std::lock_guard<std::mutex> lock(internal_->lock);
696 *results = internal_->scan_results;
700 bool Server::SetCharacteristicValue(const Uuid& id,
701 const std::vector<uint8_t>& value) {
702 std::lock_guard<std::mutex> lock(internal_->lock);
703 const int attribute_id = internal_->uuid_to_attribute[id];
704 Characteristic& ch = internal_->characteristics[attribute_id];
705 ch.next_blob = value;
706 ch.next_blob_pending = true;
708 if (!ch.notify) return true;
710 for (auto connection : internal_->connections) {
711 internal_->gatt->server->send_indication(internal_->server_if, attribute_id,
712 connection, true, {0});
717 bool Server::GetCharacteristicValue(const Uuid& id,
718 std::vector<uint8_t>* value) {
719 std::lock_guard<std::mutex> lock(internal_->lock);
720 const int attribute_id = internal_->uuid_to_attribute[id];
721 *value = internal_->characteristics[attribute_id].blob;
726 } // namespace bluetooth