Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
BTGattHandler.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2020 Gothel Software e.K.
4 * Copyright (c) 2020 ZAFENA AB
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25
26#include <cstring>
27#include <string>
28#include <memory>
29#include <cstdint>
30#include <cstdio>
31
32#include <algorithm>
33
34extern "C" {
35 #include <unistd.h>
36 #include <sys/socket.h>
37 #include <poll.h>
38 #include <signal.h>
39}
40
41// #define PERF_PRINT_ON 1
42// PERF2_PRINT_ON for read/write single values
43// #define PERF2_PRINT_ON 1
44// PERF3_PRINT_ON for disconnect
45// #define PERF3_PRINT_ON 1
46#include <jau/debug.hpp>
47
48#include <jau/basic_algos.hpp>
49
50#include "L2CAPIoctl.hpp"
51#include "GattNumbers.hpp"
52
53#include "BTGattHandler.hpp"
54
55#include "BTDevice.hpp"
56
57#include "BTManager.hpp"
58#include "BTAdapter.hpp"
59#include "BTManager.hpp"
60#include "DBTConst.hpp"
61
62#include "BTGattService.hpp"
63#include "BTGattChar.hpp"
64#include "BTGattDesc.hpp"
65
66using namespace direct_bt;
67
68BTGattEnv::BTGattEnv() noexcept
69: exploding( jau::environment::getExplodingProperties("direct_bt.gatt") ),
70 GATT_READ_COMMAND_REPLY_TIMEOUT( jau::environment::getFractionProperty("direct_bt.gatt.cmd.read.timeout", 550_ms, 550_ms /* min */, 365_d /* max */) ),
71 GATT_WRITE_COMMAND_REPLY_TIMEOUT( jau::environment::getFractionProperty("direct_bt.gatt.cmd.write.timeout", 550_ms, 550_ms /* min */, 365_d /* max */) ),
72 GATT_INITIAL_COMMAND_REPLY_TIMEOUT( jau::environment::getFractionProperty("direct_bt.gatt.cmd.init.timeout", 2500_ms, 2000_ms /* min */, 365_d /* max */) ),
73 ATTPDU_RING_CAPACITY( jau::environment::getInt32Property("direct_bt.gatt.ringsize", 128, 64 /* min */, 1024 /* max */) ),
74 DEBUG_DATA( jau::environment::getBooleanProperty("direct_bt.debug.gatt.data", false) )
75{
76}
77
79 BTDeviceRef ref = wbr_device.lock();
80 if( nullptr == ref ) {
81 throw jau::IllegalStateException("GATTHandler's device already destructed: "+toString(), E_FILE_LINE);
82 }
83 return ref;
84}
85
86bool BTGattHandler::validateConnected() noexcept {
87 const bool l2capIsConnected = l2cap.is_open();
88 const bool l2capHasIOError = l2cap.hasIOError();
89
90 if( has_ioerror || l2capHasIOError ) {
91 DBG_PRINT("ioerr state: GattHandler %s, l2cap %s: %s",
92 getStateString().c_str(), l2cap.getStateString().c_str(), toString().c_str());
93 has_ioerror = true; // propagate l2capHasIOError -> has_ioerror
94 return false;
95 }
96
97 if( !is_connected || !l2capIsConnected ) {
98 DBG_PRINT("Disconnected state: GattHandler %s, l2cap %s: %s",
99 getStateString().c_str(), l2cap.getStateString().c_str(), toString().c_str());
100 return false;
101 }
102 return true;
103}
104
105BTGattHandler::gattCharListenerList_t::equal_comparator BTGattHandler::gattCharListenerRefEqComparator =
106 [](const GattCharListenerPair& a, const GattCharListenerPair& b) -> bool { return *a.listener == *b.listener; };
107
109 if( nullptr == l ) {
110 ERR_PRINT("GATTCharacteristicListener ref is null");
111 return false;
112 }
113 return gattCharListenerList.push_back_unique(GattCharListenerPair{l, std::weak_ptr<BTGattChar>{} },
114 gattCharListenerRefEqComparator);
115}
116
118 if( nullptr == l ) {
119 ERR_PRINT("GATTCharacteristicListener ref is null");
120 return false;
121 }
122 if( nullptr == d ) {
123 ERR_PRINT("BTGattChar ref is null");
124 return false;
125 }
126 return gattCharListenerList.push_back_unique(GattCharListenerPair{l, d},
127 gattCharListenerRefEqComparator);
128}
129
131 if( nullptr == l ) {
132 ERR_PRINT("GATTCharacteristicListener ref is null");
133 return false;
134 }
135 const size_type count = gattCharListenerList.erase_matching(GattCharListenerPair{l, std::weak_ptr<BTGattChar>{}},
136 false /* all_matching */,
137 gattCharListenerRefEqComparator);
138 return count > 0;
139}
140
142 if( nullptr == l ) {
143 ERR_PRINT("GATTCharacteristicListener ref is null");
144 return false;
145 }
146 auto it = gattCharListenerList.begin(); // lock mutex and copy_store
147 for (; !it.is_end(); ++it ) {
148 if ( *it->listener == *l ) {
149 it.erase();
150 it.write_back();
151 return true;
152 }
153 }
154 return false;
155}
156
158 [](const BTGattHandler::NativeGattCharListenerRef& a, const BTGattHandler::NativeGattCharListenerRef& b) -> bool { return *a == *b; };
159
161 if( nullptr == l ) {
162 ERR_PRINT("NativeGattCharListener ref is null");
163 return false;
164 }
165 return nativeGattCharListenerList.push_back_unique(l, _nativeGattCharListenerRefEqComparator);
166}
167
169 if( nullptr == l ) {
170 ERR_PRINT("NativeGattCharListener ref is null");
171 return false;
172 }
173 const size_type count = nativeGattCharListenerList.erase_matching(l, false /* all_matching */, _nativeGattCharListenerRefEqComparator);
174 return count > 0;
175}
176
178 jau::INFO_PRINT("BTGattHandler: BTGattChar %u listener", gattCharListenerList.size());
179 {
180 int i=0;
181 auto it = gattCharListenerList.begin(); // lock mutex and copy_store
182 for (; !it.is_end(); ++it, ++i ) {
183 jau::INFO_PRINT("[%d]: %s", i, it->listener->toString().c_str());
184 }
185 }
186 jau::INFO_PRINT("BTGattHandler: NativeGattChar %u listener", nativeGattCharListenerList.size());
187 {
188 int i=0;
189 auto it = nativeGattCharListenerList.begin(); // lock mutex and copy_store
190 for (; !it.is_end(); ++it, ++i ) {
191 jau::INFO_PRINT("[%d]: %s", i, (*it)->toString().c_str());
192 }
193 }
194}
195
197 if( nullptr == associatedCharacteristic ) {
198 ERR_PRINT("Given GATTCharacteristic ref is null");
199 return false;
200 }
201 return removeAllAssociatedCharListener( associatedCharacteristic.get() );
202}
203
205 if( nullptr == associatedCharacteristic ) {
206 ERR_PRINT("Given GATTCharacteristic ref is null");
207 return false;
208 }
209 size_type count = 0;
210 auto it = gattCharListenerList.begin(); // lock mutex and copy_store
211 while( !it.is_end() ) {
212 if ( it->match(*associatedCharacteristic) ) {
213 it.erase();
214 ++count;
215 } else {
216 ++it;
217 }
218 }
219 if( 0 < count ) {
220 it.write_back();
221 }
222 return count;
223}
224
226 size_type count = gattCharListenerList.size();
227 gattCharListenerList.clear();
228 count += nativeGattCharListenerList.size();
229 nativeGattCharListenerList.clear();
230 return count;
231}
232
233void BTGattHandler::notifyNativeRequestSent(const AttPDUMsg& pduRequest, const BTDeviceRef& clientSource) noexcept {
234 BTDeviceRef serverDest = getDeviceUnchecked();
235 if( nullptr != serverDest ) {
236 int i=0;
237 jau::for_each_fidelity(nativeGattCharListenerList, [&](std::shared_ptr<BTGattHandler::NativeGattCharListener> &l) {
238 try {
239 l->requestSent(pduRequest, serverDest, clientSource);
240 } catch (std::exception &e) {
241 ERR_PRINT("GATTHandler::requestSent-CBs %d/%zd: NativeGattCharListener %s: Caught exception %s",
242 i+1, nativeGattCharListenerList.size(),
243 jau::to_hexstring((void*)l.get()).c_str(), e.what());
244 }
245 i++;
246 });
247 }
248}
249
250void BTGattHandler::notifyNativeReplyReceived(const AttPDUMsg& pduReply, const BTDeviceRef& clientDest) noexcept {
251 BTDeviceRef serverSource = getDeviceUnchecked();
252 if( nullptr != serverSource ) {
253 int i=0;
254 jau::for_each_fidelity(nativeGattCharListenerList, [&](std::shared_ptr<BTGattHandler::NativeGattCharListener> &l) {
255 try {
256 l->replyReceived(pduReply, serverSource, clientDest);
257 } catch (std::exception &e) {
258 ERR_PRINT("GATTHandler::replyReceived-CBs %d/%zd: NativeGattCharListener %s: Caught exception %s",
259 i+1, nativeGattCharListenerList.size(),
260 jau::to_hexstring((void*)l.get()).c_str(), e.what());
261 }
262 i++;
263 });
264 }
265}
266
267void BTGattHandler::notifyNativeMTUResponse(const uint16_t clientMTU_,
268 const AttPDUMsg& pduReply, const AttErrorRsp::ErrorCode error_reply,
269 const uint16_t serverMTU_, const uint16_t usedMTU_,
270 const BTDeviceRef& clientRequester) noexcept
271{
272 BTDeviceRef serverReplier = getDeviceUnchecked();
273 if( nullptr != serverReplier ) {
274 int i=0;
275 jau::for_each_fidelity(nativeGattCharListenerList, [&](std::shared_ptr<BTGattHandler::NativeGattCharListener> &l) {
276 try {
277 l->mtuResponse(clientMTU_, pduReply, error_reply, serverMTU_, usedMTU_, serverReplier, clientRequester);
278 } catch (std::exception &e) {
279 ERR_PRINT("GATTHandler::mtuResponse-CBs %d/%zd: NativeGattCharListener %s: Caught exception %s",
280 i+1, nativeGattCharListenerList.size(),
281 jau::to_hexstring((void*)l.get()).c_str(), e.what());
282 }
283 i++;
284 });
285 }
286}
287
288void BTGattHandler::notifyNativeWriteRequest(const uint16_t handle, const jau::TROOctets& data, const NativeGattCharSections_t& sections,
289 const bool with_response, const BTDeviceRef& clientSource) noexcept
290{
291 BTDeviceRef serverDest = getDeviceUnchecked();
292 if( nullptr != serverDest ) {
293 int i=0;
294 jau::for_each_fidelity(nativeGattCharListenerList, [&](std::shared_ptr<BTGattHandler::NativeGattCharListener> &l) {
295 try {
296 l->writeRequest(handle, data, sections, with_response, serverDest, clientSource);
297 } catch (std::exception &e) {
298 ERR_PRINT("GATTHandler::writeRequest-CBs %d/%zd: NativeGattCharListener %s: Caught exception %s",
299 i+1, nativeGattCharListenerList.size(),
300 jau::to_hexstring((void*)l.get()).c_str(), e.what());
301 }
302 i++;
303 });
304 }
305}
306
307void BTGattHandler::notifyNativeWriteResponse(const AttPDUMsg& pduReply, const AttErrorRsp::ErrorCode error_code, const BTDeviceRef& clientDest) noexcept {
308 BTDeviceRef serverSource = getDeviceUnchecked();
309 if( nullptr != serverSource ) {
310 int i=0;
311 jau::for_each_fidelity(nativeGattCharListenerList, [&](std::shared_ptr<BTGattHandler::NativeGattCharListener> &l) {
312 try {
313 l->writeResponse(pduReply, error_code, serverSource, clientDest);
314 } catch (std::exception &e) {
315 ERR_PRINT("GATTHandler::writeResponse-CBs %d/%zd: NativeGattCharListener %s: Caught exception %s",
316 i+1, nativeGattCharListenerList.size(),
317 jau::to_hexstring((void*)l.get()).c_str(), e.what());
318 }
319 i++;
320 });
321 }
322}
323
324void BTGattHandler::notifyNativeReadResponse(const uint16_t handle, const uint16_t value_offset,
325 const AttPDUMsg& pduReply, const AttErrorRsp::ErrorCode error_code, const jau::TROOctets& data_reply,
326 const BTDeviceRef& clientRequester) noexcept {
327 BTDeviceRef serverReplier = getDeviceUnchecked();
328 if( nullptr != serverReplier ) {
329 int i=0;
330 jau::for_each_fidelity(nativeGattCharListenerList, [&](std::shared_ptr<BTGattHandler::NativeGattCharListener> &l) {
331 try {
332 l->readResponse(handle, value_offset, pduReply, error_code, data_reply, serverReplier, clientRequester);
333 } catch (std::exception &e) {
334 ERR_PRINT("GATTHandler::readResponse-CBs %d/%zd: NativeGattCharListener %s: Caught exception %s",
335 i+1, nativeGattCharListenerList.size(),
336 jau::to_hexstring((void*)l.get()).c_str(), e.what());
337 }
338 i++;
339 });
340 }
341}
342
344 sendIndicationConfirmation = v;
345}
346
348 return sendIndicationConfirmation;
349}
350
351bool BTGattHandler::replyAttPDUReq(std::unique_ptr<const AttPDUMsg> && pdu) noexcept {
352 if( !validateConnected() ) { // shall not happen
353 DBG_PRINT("GATT-Req: disconnected: req %s from %s",
354 pdu->toString().c_str(), toString().c_str());
355 return false;
356 }
357 switch( pdu->getOpcode() ) {
359 return gattServerHandler->replyExchangeMTUReq( static_cast<const AttExchangeMTU*>( pdu.get() ) );
360 }
361
363 return gattServerHandler->replyFindInfoReq( static_cast<const AttFindInfoReq*>( pdu.get() ) );
364 }
365
367 return gattServerHandler->replyFindByTypeValueReq( static_cast<const AttFindByTypeValueReq*>( pdu.get() ) );
368 }
369
371 return gattServerHandler->replyReadByTypeReq( static_cast<const AttReadByNTypeReq*>( pdu.get() ) );
372 }
373
375 [[fallthrough]];
377 return gattServerHandler->replyReadReq( pdu.get() );
378 }
379
381 return gattServerHandler->replyReadByGroupTypeReq( static_cast<const AttReadByNTypeReq*>( pdu.get() ) );
382 }
383
385 [[fallthrough]];
386 case AttPDUMsg::Opcode::WRITE_CMD: // 18 + 64 = 82
387 [[fallthrough]];
389 [[fallthrough]];
391 return gattServerHandler->replyWriteReq( pdu.get() );
392 }
393
394 // TODO: Add support for the following requests
395
397 [[fallthrough]];
399 [[fallthrough]];
400 case AttPDUMsg::Opcode::SIGNED_WRITE_CMD: { // 18 + 64 + 128 = 210
402 WARN_PRINT("GATT Req: Ignored: %s -> %s from %s", pdu->toString().c_str(), rsp.toString().c_str(), toString().c_str());
403 if( !send(rsp) ) {
404 ERR_PRINT2("l2cap send: Error req %s; %s", rsp.toString().c_str(), toString().c_str());
405 return false;
406 }
407 return true;
408 }
409
410 default:
412 ERR_PRINT("GATT Req: Unhandled: %s -> %s from %s", pdu->toString().c_str(), rsp.toString().c_str(), toString().c_str());
413 if( !send(rsp) ) {
414 ERR_PRINT2("l2cap send: Error req %s; %s", rsp.toString().c_str(), toString().c_str());
415 return false;
416 }
417 return true;
418 }
419}
420
421void BTGattHandler::l2capReaderWork(jau::service_runner& sr) noexcept {
422 jau::snsize_t len;
423 if( !validateConnected() ) {
424 DBG_PRINT("GATTHandler::reader: Invalid IO state -> Stop");
425 sr.set_shall_stop();
426 return;
427 }
428
429 len = l2cap.read(rbuffer.get_wptr(), rbuffer.size());
430 if( 0 < len ) {
431 std::unique_ptr<const AttPDUMsg> attPDU = AttPDUMsg::getSpecialized(rbuffer.get_ptr(), static_cast<jau::nsize_t>(len));
432 COND_PRINT(env.DEBUG_DATA, "GATTHandler::reader: Got %s", attPDU->toString().c_str());
433
434 const AttPDUMsg::Opcode opc = attPDU->getOpcode();
435 const AttPDUMsg::OpcodeType opc_type = AttPDUMsg::get_type(opc);
436
437 if( AttPDUMsg::Opcode::MULTIPLE_HANDLE_VALUE_NTF == opc ) { // AttPDUMsg::OpcodeType::NOTIFICATION
438 // FIXME TODO ..
439 ERR_PRINT("MULTI-NTF not implemented: %s", attPDU->toString().c_str());
440 } else if( AttPDUMsg::Opcode::HANDLE_VALUE_NTF == opc ) { // AttPDUMsg::OpcodeType::NOTIFICATION
441 const AttHandleValueRcv * a = static_cast<const AttHandleValueRcv*>(attPDU.get());
442 COND_PRINT(env.DEBUG_DATA, "GATTHandler::reader: NTF: %s, listener [native %zd, bt %zd]",
443 a->toString().c_str(), nativeGattCharListenerList.size(), gattCharListenerList.size());
444 const uint64_t a_timestamp = a->ts_creation;
445 const uint16_t a_handle = a->getHandle();
446 const jau::TOctetSlice& a_value = a->getValue();
447 const jau::TROOctets a_data_view(a_value.get_ptr_nc(0), a_value.size(), a_value.byte_order()); // just a view, still owned by attPDU
448 BTDeviceRef device = getDeviceUnchecked();
449 if( nullptr != device ) {
450 int i=0;
451 jau::for_each_fidelity(nativeGattCharListenerList, [&](std::shared_ptr<NativeGattCharListener> &l) {
452 try {
453 l->notificationReceived(device, a_handle, a_data_view, a_timestamp);
454 } catch (std::exception &e) {
455 ERR_PRINT("GATTHandler::notificationReceived-CBs %d/%zd: NativeGattCharListener %s: Caught exception %s",
456 i+1, nativeGattCharListenerList.size(),
457 jau::to_hexstring((void*)l.get()).c_str(), e.what());
458 }
459 i++;
460 });
461 }
462 BTGattCharRef characteristic = findCharacterisicsByValueHandle(services, a_handle);
463 if( nullptr != characteristic ) {
464 int i=0;
465 jau::for_each_fidelity(gattCharListenerList, [&](GattCharListenerPair &p) {
466 try {
467 if( p.match(*characteristic) ) {
468 p.listener->notificationReceived(characteristic, a_data_view, a_timestamp);
469 }
470 } catch (std::exception &e) {
471 ERR_PRINT("GATTHandler::notificationReceived-CBs %d/%zd: BTGattCharListener %s: Caught exception %s",
472 i+1, gattCharListenerList.size(),
473 jau::to_hexstring((void*)p.listener.get()).c_str(), e.what());
474 }
475 i++;
476 });
477 }
478 } else if( AttPDUMsg::Opcode::HANDLE_VALUE_IND == opc ) { // AttPDUMsg::OpcodeType::INDICATION
479 const AttHandleValueRcv * a = static_cast<const AttHandleValueRcv*>(attPDU.get());
480 COND_PRINT(env.DEBUG_DATA, "GATTHandler::reader: IND: %s, sendIndicationConfirmation %d, listener [native %zd, bt %zd]",
481 a->toString().c_str(), sendIndicationConfirmation.load(), nativeGattCharListenerList.size(), gattCharListenerList.size());
482 bool cfmSent = false;
483 if( sendIndicationConfirmation ) {
485 if( !send(cfm) ) {
486 ERR_PRINT2("Indication Confirmation: Error req %s; %s", cfm.toString().c_str(), toString().c_str());
487 sr.set_shall_stop();
488 has_ioerror = true;
489 return;
490 }
491 cfmSent = true;
492 }
493 const uint64_t a_timestamp = a->ts_creation;
494 const uint16_t a_handle = a->getHandle();
495 const jau::TOctetSlice& a_value = a->getValue();
496 const jau::TROOctets a_data_view(a_value.get_ptr_nc(0), a_value.size(), a_value.byte_order()); // just a view, still owned by attPDU
497 BTDeviceRef device = getDeviceUnchecked();
498 if( nullptr != device ) {
499 int i=0;
500 jau::for_each_fidelity(nativeGattCharListenerList, [&](std::shared_ptr<NativeGattCharListener> &l) {
501 try {
502 l->indicationReceived(device, a_handle, a_data_view, a_timestamp, cfmSent);
503 } catch (std::exception &e) {
504 ERR_PRINT("GATTHandler::indicationReceived-CBs %d/%zd: NativeGattCharListener %s: Caught exception %s",
505 i+1, nativeGattCharListenerList.size(),
506 jau::to_hexstring((void*)l.get()).c_str(), e.what());
507 }
508 i++;
509 });
510 }
511 BTGattCharRef characteristic = findCharacterisicsByValueHandle(services, a_handle);
512 if( nullptr != characteristic ) {
513 int i=0;
514 jau::for_each_fidelity(gattCharListenerList, [&](GattCharListenerPair &p) {
515 try {
516 if( p.match(*characteristic) ) {
517 p.listener->indicationReceived(characteristic, a_data_view, a_timestamp, cfmSent);
518 }
519 } catch (std::exception &e) {
520 ERR_PRINT("GATTHandler::indicationReceived-CBs %d/%zd: BTGattCharListener %s, cfmSent %d: Caught exception %s",
521 i+1, gattCharListenerList.size(),
522 jau::to_hexstring((void*)p.listener.get()).c_str(), cfmSent, e.what());
523 }
524 i++;
525 });
526 }
527 } else if( AttPDUMsg::OpcodeType::RESPONSE == opc_type ) {
528 COND_PRINT(env.DEBUG_DATA, "GATTHandler::reader: Ring: %s", attPDU->toString().c_str());
529 if( !attPDURing.putBlocking( std::move(attPDU), 0_s ) ) {
530 ERR_PRINT2("attPDURing put: %s", attPDURing.toString().c_str());
531 sr.set_shall_stop();
532 return;
533 }
534 } else if( AttPDUMsg::OpcodeType::REQUEST == opc_type ) {
535 if( !replyAttPDUReq( std::move( attPDU ) ) ) {
536 ERR_PRINT2("ATT Reply: %s", toString().c_str());
537 sr.set_shall_stop();
538 has_ioerror = true;
539 return;
540 }
541 } else {
542 ERR_PRINT("Unhandled: %s", attPDU->toString().c_str());
543 }
545 WORDY_PRINT("GATTHandler::reader: l2cap read: IRQed res %d (%s); %s",
546 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str());
547 if( !sr.shall_stop() ) {
548 // need to stop service_runner if interrupted externally
549 sr.set_shall_stop();
550 }
552 len != L2CAPClient::number(L2CAPClient::RWExitCode::READ_TIMEOUT) ) { // expected TIMEOUT if idle
553 if( 0 > len ) { // actual error case
554 IRQ_PRINT("GATTHandler::reader: l2cap read: Error res %d (%s); %s",
555 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str());
556 sr.set_shall_stop();
557 has_ioerror = true;
558 } else { // zero size
559 WORDY_PRINT("GATTHandler::reader: l2cap read: Zero res %d (%s); %s",
560 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str());
561 }
562 }
563}
564
565void BTGattHandler::l2capReaderEndLocked(jau::service_runner& sr) noexcept {
566 (void)sr;
567 WORDY_PRINT("GATTHandler::reader: EndLocked. Ring has %u entries flushed: %s", attPDURing.size(), toString().c_str());
568 attPDURing.clear();
569#if 0
570 // Disabled: BT host is sending out disconnect -> simplify tear down
571 if( has_ioerror ) {
572 // Don't rely on receiving a disconnect
573 BTDeviceRef device = getDeviceUnchecked();
574 if( nullptr != device ) {
576 dc.detach();
577 }
578 }
579#endif
580}
581
582bool BTGattHandler::l2capReaderInterrupted(int dummy) /* const */ noexcept {
583 (void)dummy;
584 if( l2cap_reader_service.shall_stop() || !is_connected ) {
585 return true;
586 }
587 BTDeviceRef device = getDeviceUnchecked();
588 if( nullptr == device ) {
589 return true;
590 }
591 return !device->getConnected();
592}
593
594BTGattHandler::BTGattHandler(const BTDeviceRef &device, L2CAPClient& l2cap_att, const int32_t supervision_timeout_) noexcept
595: supervision_timeout(supervision_timeout_),
596 env(BTGattEnv::get()),
597 read_cmd_reply_timeout(jau::max(env.GATT_READ_COMMAND_REPLY_TIMEOUT, 1_ms*(supervision_timeout+50_i64))),
598 write_cmd_reply_timeout(jau::max(env.GATT_WRITE_COMMAND_REPLY_TIMEOUT, 1_ms*(supervision_timeout+50_i64))),
599 wbr_device(device),
600 role(device->getLocalGATTRole()),
601 l2cap(l2cap_att),
602 deviceString(device->getAddressAndType().address.toString()),
603 rbuffer(number(Defaults::MAX_ATT_MTU), jau::lb_endian_t::little),
604 is_connected(l2cap.is_open()), has_ioerror(false),
605 l2cap_reader_service("GATTHandler::reader_"+deviceString, THREAD_SHUTDOWN_TIMEOUT_MS,
606 jau::bind_member(this, &BTGattHandler::l2capReaderWork),
608 jau::bind_member(this, &BTGattHandler::l2capReaderEndLocked)),
609 attPDURing(env.ATTPDU_RING_CAPACITY),
610 serverMTU(number(Defaults::MIN_ATT_MTU)), usedMTU(number(Defaults::MIN_ATT_MTU)), clientMTUExchanged(false),
611 gattServerData( device->getAdapter().getGATTServerData() ),
612 gattServerHandler( selectGattServerHandler(*this, gattServerData) )
613{
614 if( !validateConnected() ) {
615 ERR_PRINT("L2CAP could not connect");
616 is_connected = false;
617 return;
618 }
619
620 /**
621 * We utilize DBTManager's mgmthandler_sigaction SIGALRM handler,
622 * as we only can install one handler.
623 */
624 // l2cap.set_interrupted_query( jau::bind_member(&l2cap_reader_service, &jau::service_runner::shall_stop2) );
625 l2cap.set_interrupted_query( jau::bind_member(this, &BTGattHandler::l2capReaderInterrupted) );
626 l2cap_reader_service.start();
627
628 DBG_PRINT("GATTHandler::ctor: Started: GattHandler[%s], l2cap[%s]: %s",
629 getStateString().c_str(), l2cap.getStateString().c_str(), toString().c_str());
630
631 if( GATTRole::Client == getRole() ) {
632 // MTU to be negotiated via initClientGatt() from this GATT client later
633 serverMTU = number(Defaults::MAX_ATT_MTU);
634 usedMTU = number(Defaults::MIN_ATT_MTU);
635 } else {
636 // MTU to be negotiated via client request on this GATT server
637 if( nullptr != gattServerData ) {
638 serverMTU = std::max( std::min( gattServerData->getMaxAttMTU(), number(Defaults::MAX_ATT_MTU) ), number(Defaults::MIN_ATT_MTU) );
639 } else {
640 serverMTU = number(Defaults::MAX_ATT_MTU);
641 }
642 usedMTU = number(Defaults::MIN_ATT_MTU);
643
644 if( nullptr != gattServerData ) {
645 int i=0;
646 jau::for_each_fidelity(gattServerData->listener(), [&](DBGattServer::ListenerRef &l) {
647 try {
648 l->connected(device, usedMTU);
649 } catch (std::exception &e) {
650 ERR_PRINT("GATTHandler::connected: %d/%zd: %s: Caught exception %s",
651 i+1, gattServerData->listener().size(),
652 toString().c_str(), e.what());
653 }
654 i++;
655 });
656 }
657 }
658}
659
661 DBG_PRINT("GATTHandler::dtor: Start: %s", toString().c_str());
662 disconnect(false /* disconnect_device */, false /* ioerr_cause */);
663 gattCharListenerList.clear();
664 nativeGattCharListenerList.clear();
665 services.clear();
666 genericAccess = nullptr;
667 DBG_PRINT("GATTHandler::dtor: End: %s", toString().c_str());
668}
669
670std::string BTGattHandler::getStateString() const noexcept {
671 return L2CAPComm::getStateString(is_connected, has_ioerror);
672}
673
674bool BTGattHandler::disconnect(const bool disconnect_device, const bool ioerr_cause) noexcept {
675 BTDeviceRef device = getDeviceUnchecked();
676 if( nullptr == device ) {
677 // If the device has been pulled already, so its l2cap instance.
678 ERR_PRINT("BTDevice null");
679 return false;
680 }
681 PERF3_TS_T0();
682
683 // Avoid disconnect re-entry -> potential deadlock
684 bool expConn = true; // C++11, exp as value since C++20
685 if( !is_connected.compare_exchange_strong(expConn, false) ) {
686 // not connected
687 const bool l2cap_service_stopped = l2cap_reader_service.join(); // [data] race: wait until disconnecting thread has stopped service
688 l2cap.close(); // owned by BTDevice.
689 DBG_PRINT("GATTHandler::disconnect: Not connected: disconnect_device %d, ioerr %d: GattHandler[%s], l2cap[%s], stopped %d: %s",
690 disconnect_device, ioerr_cause, getStateString().c_str(), l2cap.getStateString().c_str(),
691 l2cap_service_stopped, toString().c_str());
692 gattCharListenerList.clear();
693 nativeGattCharListenerList.clear();
694 return false;
695 }
696
697 PERF3_TS_TD("GATTHandler::disconnect.1");
698 const bool l2cap_service_stop_res = l2cap_reader_service.stop();
699 l2cap.close(); // owned by BTDevice.
700 PERF3_TS_TD("GATTHandler::disconnect.X");
701
702 gattServerHandler->close();
703
704 // Lock to avoid other threads using instance while disconnecting
705 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
706 DBG_PRINT("GATTHandler::disconnect: Start: disconnect_device %d, ioerr %d: GattHandler[%s], l2cap[%s]: %s",
707 disconnect_device, ioerr_cause, getStateString().c_str(), l2cap.getStateString().c_str(), toString().c_str());
708 gattCharListenerList.clear();
709 nativeGattCharListenerList.clear();
710
711 clientMTUExchanged = false;
712
713 DBG_PRINT("GATTHandler::disconnect: End: stopped %d, disconnect_device %d, %s",
714 l2cap_service_stop_res, disconnect_device, toString().c_str());
715
716 if( disconnect_device ) {
717 // Cleanup device resources, proper connection state
718 // Intentionally giving the POWER_OFF reason for the device in case of ioerr_cause!
719 const HCIStatusCode reason = ioerr_cause ?
722 device->disconnect(reason);
723 }
724 return true;
725}
726
727bool BTGattHandler::send(const AttPDUMsg & msg) noexcept {
728 if( !validateConnected() ) {
729 if( !l2capReaderInterrupted() ) {
730 ERR_PRINT("Invalid IO State: req %s to %s", msg.toString().c_str(), toString().c_str());
731 }
732 return false;
733 }
734 // [1 .. ATT_MTU-1] BT Core Spec v5.2: Vol 3, Part F 3.2.9 Long attribute values
735 if( msg.pdu.size() > usedMTU ) {
736 ERR_PRINT("Msg PDU size %zu >= used MTU %u, req %s to $s",
737 msg.pdu.size(), usedMTU.load(), msg.toString().c_str(), toString().c_str());
738 return false;
739 }
740
741 // Thread safe l2cap.write(..) operation..
742 const jau::snsize_t len = l2cap.write(msg.pdu.get_ptr(), msg.pdu.size());
743 if( 0 > len ) {
744 if( len == L2CAPClient::number(L2CAPClient::RWExitCode::INTERRUPTED) ) { // expected exits
745 WORDY_PRINT("GATTHandler::reader: l2cap read: IRQed res %d (%s); %s",
746 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str());
747 } else {
748 ERR_PRINT("l2cap write: Error res %d (%s); %s; %s -> disconnect: %s",
749 len, L2CAPClient::getRWExitCodeString(len).c_str(), getStateString().c_str(),
750 msg.toString().c_str(), toString().c_str());
751 has_ioerror = true;
752 disconnect(true /* disconnect_device */, true /* ioerr_cause */); // state -> Disconnected
753 }
754 return false;
755 }
756 if( static_cast<size_t>(len) != msg.pdu.size() ) {
757 ERR_PRINT("l2cap write: Error: Message size has %d != exp %zu: %s -> disconnect: %s",
758 len, msg.pdu.size(), msg.toString().c_str(), toString().c_str());
759 has_ioerror = true;
760 disconnect(true /* disconnect_device */, true /* ioerr_cause */); // state -> Disconnected
761 return false;
762 }
763 return true;
764}
765
766std::unique_ptr<const AttPDUMsg> BTGattHandler::sendWithReply(const AttPDUMsg & msg, const jau::fraction_i64& timeout) noexcept {
767 if( !send( msg ) ) {
768 return nullptr;
769 }
770
771 // Ringbuffer read is thread safe
772 std::unique_ptr<const AttPDUMsg> res;
773 if( !attPDURing.getBlocking(res, timeout) || nullptr == res ) {
774 errno = ETIMEDOUT;
775 ERR_PRINT("GATTHandler::sendWithReply: nullptr result (timeout %" PRIi64 " ms): req %s to %s", timeout.to_ms(), msg.toString().c_str(), toString().c_str());
776 has_ioerror = true;
777 disconnect(true /* disconnect_device */, true /* ioerr_cause */);
778 return nullptr;
779 }
780 return res;
781}
782
783uint16_t BTGattHandler::clientMTUExchange(const jau::fraction_i64& timeout) noexcept {
784 if( GATTRole::Client != getRole() ) {
785 ERR_PRINT("GATT MTU exchange only allowed in client mode");
786 return usedMTU;
787 }
788 /***
789 * BT Core Spec v5.2: Vol 3, Part G GATT: 4.3.1 Exchange MTU (Server configuration)
790 */
791 const AttExchangeMTU req(AttPDUMsg::ReqRespType::REQUEST, number(Defaults::MAX_ATT_MTU));
792 const std::lock_guard<std::recursive_mutex> lock(mtx_command);
793 PERF_TS_T0();
794
795 uint16_t mtu = 0;
796 DBG_PRINT("GATT MTU-REQ send: %s to %s", req.toString().c_str(), toString().c_str());
797
798 std::unique_ptr<const AttPDUMsg> pdu = sendWithReply(req, timeout);
799
800 if( nullptr == pdu ) {
801 ERR_PRINT2("No reply; req %s from %s", req.toString().c_str(), toString().c_str());
802 } else if( pdu->getOpcode() == AttPDUMsg::Opcode::EXCHANGE_MTU_RSP ) {
803 const AttExchangeMTU * p = static_cast<const AttExchangeMTU*>(pdu.get());
804 mtu = p->getMTUSize();
805 DBG_PRINT("GATT MTU-RSP recv: %u, %s from %s", mtu, pdu->toString().c_str(), toString().c_str());
806 } else if( pdu->getOpcode() == AttPDUMsg::Opcode::ERROR_RSP ) {
807 /**
808 * If the ATT_ERROR_RSP PDU is sent by the server
809 * with the error code set to 'Request Not Supported',
810 * the Attribute Opcode is not supported and the default MTU shall be used.
811 */
812 const AttErrorRsp * p = static_cast<const AttErrorRsp *>(pdu.get());
814 mtu = number(Defaults::MIN_ATT_MTU); // OK by spec: Use default MTU
815 DBG_PRINT("GATT MTU handled error -> ATT_MTU %u, %s from %s", mtu, pdu->toString().c_str(), toString().c_str());
816 } else {
817 WORDY_PRINT("GATT MTU unexpected error %s; req %s from %s", pdu->toString().c_str(), req.toString().c_str(), toString().c_str());
818 }
819 } else {
820 ERR_PRINT("GATT MTU unexpected reply %s; req %s from %s", pdu->toString().c_str(), req.toString().c_str(), toString().c_str());
821 }
822 PERF_TS_TD("GATT exchangeMTU");
823
824 return mtu;
825}
826
827DBGattCharRef BTGattHandler::findServerGattCharByValueHandle(const uint16_t char_value_handle) noexcept {
828 if( nullptr != gattServerData ) {
829 return gattServerData->findGattCharByValueHandle(char_value_handle);
830 } else {
831 return nullptr;
832 }
833}
834
835bool BTGattHandler::sendNotification(const uint16_t char_value_handle, const jau::TROOctets & value) noexcept {
836 if( GATTRole::Server != role ) {
837 ERR_PRINT("GATTRole not server");
838 return false;
839 }
840 if( DBGattServer::Mode::DB == gattServerHandler->getMode() &&
841 nullptr == findServerGattCharByValueHandle(char_value_handle) )
842 {
843 ERR_PRINT("Invalid char handle %s", jau::to_hexstring(char_value_handle).c_str());
844 return false;
845 }
846 if( 0 == value.size() ) {
847 COND_PRINT(env.DEBUG_DATA, "GATT SEND NTF: Zero size, skipped sending to %s", toString().c_str());
848 return true;
849 }
850 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
851 AttHandleValueRcv data(true /* isNotify */, char_value_handle, value, usedMTU);
852 COND_PRINT(env.DEBUG_DATA, "GATT SEND NTF: %s to %s", data.toString().c_str(), toString().c_str());
853 return send(data);
854}
855
856bool BTGattHandler::sendIndication(const uint16_t char_value_handle, const jau::TROOctets & value) noexcept {
857 if( GATTRole::Server != role ) {
858 ERR_PRINT("GATTRole not server");
859 return false;
860 }
861 if( DBGattServer::Mode::DB == gattServerHandler->getMode() &&
862 nullptr == findServerGattCharByValueHandle(char_value_handle) )
863 {
864 ERR_PRINT("Invalid char handle %s", jau::to_hexstring(char_value_handle).c_str());
865 return false;
866 }
867 if( 0 == value.size() ) {
868 COND_PRINT(env.DEBUG_DATA, "GATT SEND IND: Zero size, skipped sending to %s", toString().c_str());
869 return true;
870 }
871 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
872 AttHandleValueRcv req(false /* isNotify */, char_value_handle, value, usedMTU);
873 std::unique_ptr<const AttPDUMsg> pdu = sendWithReply(req, write_cmd_reply_timeout);
874 if( nullptr == pdu ) {
875 ERR_PRINT2("No reply; req %s from %s", req.toString().c_str(), toString().c_str());
876 return false;
877 }
878 if( pdu->getOpcode() == AttPDUMsg::Opcode::HANDLE_VALUE_CFM ) {
879 COND_PRINT(env.DEBUG_DATA, "GATT SENT IND: %s -> %s to/from %s",
880 req.toString().c_str(), pdu->toString().c_str(), toString().c_str());
881 return true;
882 } else {
883 WARN_PRINT("GATT SENT IND: Failed, no CFM reply: %s -> %s to/from %s",
884 req.toString().c_str(), pdu->toString().c_str(), toString().c_str());
885 return false;
886 }
887}
888
889BTGattCharRef BTGattHandler::findCharacterisicsByValueHandle(const jau::darray<BTGattServiceRef> &services_, const uint16_t charValueHandle) noexcept {
890 for(const auto & service : services_) {
891 BTGattCharRef decl = findCharacterisicsByValueHandle(service, charValueHandle);
892 if( nullptr != decl ) {
893 return decl;
894 }
895 }
896 return nullptr;
897}
898
899BTGattCharRef BTGattHandler::findCharacterisicsByValueHandle(const BTGattServiceRef& service, const uint16_t charValueHandle) noexcept {
900 for(auto decl : service->characteristicList) {
901 if( charValueHandle == decl->value_handle ) {
902 return decl;
903 }
904 }
905 return nullptr;
906}
907
908bool BTGattHandler::initClientGatt(const std::shared_ptr<BTGattHandler>& shared_this, bool& already_init) noexcept {
909 const std::lock_guard<std::recursive_mutex> lock(mtx_command);
910 already_init = clientMTUExchanged && services.size() > 0 && nullptr != genericAccess;
911 if( already_init ) {
912 return true;
913 }
914 if( !isConnected() ) {
915 DBG_PRINT("GATTHandler::initClientGatt: Not connected: %s", toString().c_str());
916 return false;
917 }
918 if( !clientMTUExchanged) {
919 // First point of failure if remote device exposes no GATT functionality. Allow a longer timeout!
920 const jau::fraction_i64 initial_command_reply_timeout = jau::min(10_s, jau::max(env.GATT_INITIAL_COMMAND_REPLY_TIMEOUT, 1_ms*(2_i64*supervision_timeout)));
921 DBG_PRINT("GATTHandler::initClientGatt: Local GATT Client: MTU Exchange Start: %s", toString().c_str());
922 uint16_t mtu = clientMTUExchange(initial_command_reply_timeout);
923 if( 0 == mtu ) {
924 ERR_PRINT2("Local GATT Client: Zero serverMTU -> disconnect: %s", toString().c_str());
925 disconnect(true /* disconnect_device */, false /* ioerr_cause */);
926 return false;
927 }
928 serverMTU = mtu;
929 usedMTU = std::min(number(Defaults::MAX_ATT_MTU), serverMTU.load());
930 clientMTUExchanged = true;
931 DBG_PRINT("GATTHandler::initClientGatt: Local GATT Client: MTU Exchanged: server %u -> used %u, %s", serverMTU.load(), usedMTU.load(), toString().c_str());
932 }
933
934 if( services.size() > 0 && nullptr != genericAccess ) {
935 // already initialized
936 return true;
937 }
938 services.clear();
939
940 // Service discovery may consume 500ms - 2000ms, depending on bandwidth
941 DBG_PRINT("GATTHandler::initClientGatt: Local GATT Client: Service Discovery Start: %s", toString().c_str());
942 if( !discoverCompletePrimaryServices(shared_this) ) {
943 ERR_PRINT2("Failed service discovery");
944 services.clear();
945 disconnect(true /* disconnect_device */, true /* ioerr_cause */);
946 return false;
947 }
948 if( services.size() == 0 ) { // nothing discovered
949 ERR_PRINT2("No services discovered");
950 services.clear();
951 disconnect(true /* disconnect_device */, false /* ioerr_cause */);
952 return false;
953 }
954 genericAccess = getGenericAccess(services);
955 if( nullptr == genericAccess ) {
956 ERR_PRINT2("No GenericAccess discovered");
957 services.clear();
958 disconnect(true /* disconnect_device */, false /* ioerr_cause */);
959 return false;
960 }
961 DBG_PRINT("GATTHandler::initClientGatt: End: %zu services discovered: %s, %s",
962 services.size(), genericAccess->toString().c_str(), toString().c_str());
963 return true;
964}
965
966bool BTGattHandler::discoverCompletePrimaryServices(const std::shared_ptr<BTGattHandler>& shared_this) noexcept {
967 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
968 if( !discoverPrimaryServices(shared_this, services) ) {
969 return false;
970 }
971 for(auto primSrv : services) {
972 if( !discoverCharacteristics(primSrv) ) {
973 return false;
974 }
975 if( primSrv->characteristicList.size() > 0 ) {
976 if( !discoverDescriptors(primSrv) ) {
977 return false;
978 }
979 }
980 }
981 return true;
982}
983
984bool BTGattHandler::discoverPrimaryServices(const std::shared_ptr<BTGattHandler>& shared_this, jau::darray<BTGattServiceRef> & result) noexcept {
985 {
986 // validate shared_this first!
987 BTGattHandler *given_this = shared_this.get();
988 if( given_this != this ) {
989 ABORT("Given shared GATTHandler reference %s not matching this %s, %s",
990 jau::to_hexstring(given_this).c_str(), jau::to_hexstring(this).c_str(), toString().c_str());
991 }
992 }
993 /***
994 * BT Core Spec v5.2: Vol 3, Part G GATT: 4.4.1 Discover All Primary Services
995 *
996 * This sub-procedure is complete when the ATT_ERROR_RSP PDU is received
997 * and the error code is set to Attribute Not Found or when the End Group Handle
998 * in the Read by Type Group Response is 0xFFFF.
999 */
1001 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
1002 PERF_TS_T0();
1003
1004 bool done=false;
1005 uint16_t startHandle=0x0001;
1006 result.clear();
1007 while(!done) {
1008 const AttReadByNTypeReq req(true /* group */, startHandle, 0xffff, groupType);
1009 COND_PRINT(env.DEBUG_DATA, "GATT PRIM SRV discover send: %s to %s", req.toString().c_str(), toString().c_str());
1010
1011 std::unique_ptr<const AttPDUMsg> pdu = sendWithReply(req, read_cmd_reply_timeout);
1012 if( nullptr == pdu ) {
1013 ERR_PRINT2("No reply; req %s from %s", req.toString().c_str(), toString().c_str());
1014 return false;
1015 }
1016 COND_PRINT(env.DEBUG_DATA, "GATT PRIM SRV discover recv: %s on %s", pdu->toString().c_str(), toString().c_str());
1017
1018 if( pdu->getOpcode() == AttPDUMsg::Opcode::READ_BY_GROUP_TYPE_RSP ) {
1019 const AttReadByGroupTypeRsp * p = static_cast<const AttReadByGroupTypeRsp*>(pdu.get());
1020 const size_type esz = p->getElementSize();
1021 const size_type count = p->getElementCount();
1022
1023 for(size_type i=0; i<count; ++i) {
1024 const size_type ePDUOffset = p->getElementPDUOffset(i);
1025 try {
1026 result.push_back( std::make_shared<BTGattService>( shared_this, true,
1027 p->pdu.get_uint16(ePDUOffset), // start-handle
1028 p->pdu.get_uint16(ePDUOffset + 2), // end-handle
1029 p->pdu.get_uuid( ePDUOffset + 2 + 2, jau::uuid_t::toTypeSize(esz-2-2) ) // uuid
1030 ) );
1031 } catch (const std::bad_alloc &e) {
1032 ABORT("Error: bad_alloc: BTGattServiceRef allocation failed");
1033 return false; // unreachable
1034 }
1035 COND_PRINT(env.DEBUG_DATA, "GATT PRIM SRV discovered[%d/%d]: %s on %s", i,
1036 count, result.at(result.size()-1)->toString().c_str(), toString().c_str());
1037 }
1038 startHandle = p->getElementEndHandle(count-1);
1039 if( startHandle < 0xffff ) {
1040 startHandle++;
1041 } else {
1042 done = true; // OK by spec: End of communication
1043 }
1044 } else if( pdu->getOpcode() == AttPDUMsg::Opcode::ERROR_RSP ) {
1045 done = true; // OK by spec: End of communication
1046 } else {
1047 ERR_PRINT("GATT discoverPrimary unexpected reply %s, req %s from %s",
1048 pdu->toString().c_str(), req.toString().c_str(), toString().c_str());
1049 done = true;
1050 }
1051 }
1052 PERF_TS_TD("GATT discoverPrimaryServices");
1053 return true;
1054}
1055
1056bool BTGattHandler::discoverCharacteristics(BTGattServiceRef & service) noexcept {
1057 /***
1058 * BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service
1059 * <p>
1060 * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characteristic Declaration Attribute Value
1061 * </p>
1062 * <p>
1063 * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration
1064 * </p>
1065 */
1066 const jau::uuid16_t characteristicTypeReq = jau::uuid16_t(GattAttributeType::CHARACTERISTIC);
1067 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
1068 COND_PRINT(env.DEBUG_DATA, "GATT discoverCharacteristics Service: %s on %s", service->toString().c_str(), toString().c_str());
1069
1070 PERF_TS_T0();
1071
1072 bool done=false;
1073 uint16_t handle=service->handle;
1074 service->characteristicList.clear();
1075 while(!done) {
1076 const AttReadByNTypeReq req(false /* group */, handle, service->end_handle, characteristicTypeReq);
1077 COND_PRINT(env.DEBUG_DATA, "GATT C discover send: %s to %s", req.toString().c_str(), toString().c_str());
1078
1079 std::unique_ptr<const AttPDUMsg> pdu = sendWithReply(req, read_cmd_reply_timeout);
1080 if( nullptr == pdu ) {
1081 ERR_PRINT2("No reply; req %s from %s", req.toString().c_str(), toString().c_str());
1082 return false;
1083 }
1084 COND_PRINT(env.DEBUG_DATA, "GATT C discover recv: %s from %s", pdu->toString().c_str(), toString().c_str());
1085
1086 if( pdu->getOpcode() == AttPDUMsg::Opcode::READ_BY_TYPE_RSP ) {
1087 const AttReadByTypeRsp * p = static_cast<const AttReadByTypeRsp*>(pdu.get());
1088 const size_type esz = p->getElementSize();
1089 const size_type e_count = p->getElementCount();
1090
1091 for(size_type e_iter=0; e_iter<e_count; ++e_iter) {
1092 // handle: handle for the Characteristics declaration
1093 // value: Characteristics Property, Characteristics Value Handle _and_ Characteristics UUID
1094 const size_type ePDUOffset = p->getElementPDUOffset(e_iter);
1095 try {
1096 service->characteristicList.push_back( std::make_shared<BTGattChar>(
1097 service,
1098 p->getElementHandle(e_iter), // Characteristic Handle
1099 static_cast<BTGattChar::PropertyBitVal>(p->pdu.get_uint8(ePDUOffset + 2)), // Characteristics Property
1100 p->pdu.get_uint16(ePDUOffset + 2 + 1), // Characteristics Value Handle
1101 p->pdu.get_uuid(ePDUOffset + 2 + 1 + 2, jau::uuid_t::toTypeSize(esz-2-1-2) ) ) ); // Characteristics Value Type UUID
1102 } catch (const std::bad_alloc &e) {
1103 ABORT("Error: bad_alloc: BTGattCharRef allocation failed");
1104 return false; // unreachable
1105 }
1106 COND_PRINT(env.DEBUG_DATA, "GATT C discovered[%d/%d]: char%s on %s", e_iter, e_count,
1107 service->characteristicList.at(service->characteristicList.size()-1)->toString().c_str(), toString().c_str());
1108 }
1109 handle = p->getElementHandle(e_count-1); // Last Characteristic Handle
1110 if( handle < service->end_handle ) {
1111 handle++;
1112 } else {
1113 done = true; // OK by spec: End of communication
1114 }
1115 } else if( pdu->getOpcode() == AttPDUMsg::Opcode::ERROR_RSP ) {
1116 done = true; // OK by spec: End of communication
1117 } else {
1118 ERR_PRINT("GATT discoverCharacteristics unexpected reply %s, req %s within service%s from %s",
1119 pdu->toString().c_str(), req.toString().c_str(), service->toString().c_str(), toString().c_str());
1120 done = true;
1121 }
1122 }
1123
1124 PERF_TS_TD("GATT discoverCharacteristics");
1125 return true;
1126}
1127
1128bool BTGattHandler::discoverDescriptors(BTGattServiceRef & service) noexcept {
1129 /***
1130 * BT Core Spec v5.2: Vol 3, Part G GATT: 4.7.1 Discover All Characteristic Descriptors
1131 * <p>
1132 * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characteristic Declaration Attribute Value
1133 * </p>
1134 */
1135 COND_PRINT(env.DEBUG_DATA, "GATT discoverDescriptors Service: %s on %s", service->toString().c_str(), toString().c_str());
1136 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
1137 PERF_TS_T0();
1138
1139 const size_type charCount = service->characteristicList.size();
1140 for(size_type charIter=0; charIter < charCount; ++charIter ) {
1141 BTGattCharRef charDecl = service->characteristicList[charIter];
1142 charDecl->clearDescriptors();
1143 COND_PRINT(env.DEBUG_DATA, "GATT discoverDescriptors Characteristic[%d/%d]: %s on %s", charIter, charCount, charDecl->toString().c_str(), toString().c_str());
1144
1145 uint16_t cd_handle_iter = charDecl->value_handle + 1; // Start @ Characteristic Value Handle + 1
1146 uint16_t cd_handle_end;
1147 if( charIter+1 < charCount ) {
1148 cd_handle_end = service->characteristicList.at(charIter+1)->handle - 1; // // Next Characteristic Handle (excluding)
1149 } else {
1150 cd_handle_end = service->end_handle; // End of service handle (including)
1151 }
1152
1153 bool done=false;
1154
1155 while( !done && cd_handle_iter <= cd_handle_end ) {
1156 const AttFindInfoReq req(cd_handle_iter, cd_handle_end);
1157 COND_PRINT(env.DEBUG_DATA, "GATT CD discover send: %s", req.toString().c_str());
1158
1159 std::unique_ptr<const AttPDUMsg> pdu = sendWithReply(req, read_cmd_reply_timeout);
1160 if( nullptr == pdu ) {
1161 ERR_PRINT2("No reply; req %s from %s", req.toString().c_str(), toString().c_str());
1162 return false;
1163 }
1164 COND_PRINT(env.DEBUG_DATA, "GATT CD discover recv: %s from ", pdu->toString().c_str(), toString().c_str());
1165
1166 if( pdu->getOpcode() == AttPDUMsg::Opcode::FIND_INFORMATION_RSP ) {
1167 const AttFindInfoRsp * p = static_cast<const AttFindInfoRsp*>(pdu.get());
1168 const size_type e_count = p->getElementCount();
1169
1170 for(size_type e_iter=0; e_iter<e_count; ++e_iter) {
1171 // handle: handle of Characteristic Descriptor.
1172 // value: Characteristic Descriptor UUID.
1173 const uint16_t cd_handle = p->getElementHandle(e_iter);
1174 std::unique_ptr<const jau::uuid_t> cd_uuid = p->getElementValue(e_iter);
1175
1176 std::shared_ptr<BTGattDesc> cd( std::make_shared<BTGattDesc>(charDecl, std::move(cd_uuid), cd_handle) );
1177 if( cd_handle <= charDecl->value_handle || cd_handle > cd_handle_end ) { // should never happen!
1178 ERR_PRINT("GATT discoverDescriptors CD handle %s not in range ]%s..%s]: descr%s within char%s on %s",
1179 jau::to_hexstring(cd_handle).c_str(),
1180 jau::to_hexstring(charDecl->value_handle).c_str(), jau::to_hexstring(cd_handle_end).c_str(),
1181 cd->toString().c_str(), charDecl->toString().c_str(), toString().c_str());
1182 done = true;
1183 break;
1184
1185 }
1186 if( !readDescriptorValue(*cd, 0) ) {
1187 WORDY_PRINT("GATT discoverDescriptors readDescriptorValue failed: req %s, descr%s within char%s on %s",
1188 req.toString().c_str(), cd->toString().c_str(), charDecl->toString().c_str(), toString().c_str());
1189 done = true;
1190 break;
1191 }
1192 if( cd->isClientCharConfig() ) {
1193 charDecl->clientCharConfigIndex = (BTGattChar::ssize_type) charDecl->descriptorList.size();
1194 } else if( cd->isUserDescription() ) {
1195 charDecl->userDescriptionIndex = (BTGattChar::ssize_type) charDecl->descriptorList.size();
1196 }
1197 charDecl->descriptorList.push_back(cd);
1198 COND_PRINT(env.DEBUG_DATA, "GATT CD discovered[%d/%d]: %s", e_iter, e_count, cd->toString().c_str());
1199 }
1200 cd_handle_iter = p->getElementHandle(e_count-1); // Last Descriptor Handle
1201 if( cd_handle_iter < cd_handle_end ) {
1202 cd_handle_iter++;
1203 } else {
1204 done = true; // OK by spec: End of communication
1205 }
1206 } else if( pdu->getOpcode() == AttPDUMsg::Opcode::ERROR_RSP ) {
1207 done = true; // OK by spec: End of communication
1208 } else {
1209 ERR_PRINT("GATT discoverDescriptors unexpected reply %s; req %s within char%s from %s",
1210 pdu->toString().c_str(), req.toString().c_str(), charDecl->toString().c_str(), toString().c_str());
1211 done = true;
1212 }
1213 }
1214 }
1215 PERF_TS_TD("GATT discoverDescriptors");
1216 return true;
1217}
1218
1219bool BTGattHandler::readDescriptorValue(BTGattDesc & desc, ssize_type expectedLength) noexcept {
1220 COND_PRINT(env.DEBUG_DATA, "GATTHandler::readDescriptorValue expLen %zd, desc %s", (size_t)expectedLength, desc.toString().c_str());
1221 const bool res = readValue(desc.handle, desc.value, expectedLength);
1222 if( !res ) {
1223 WORDY_PRINT("GATT readDescriptorValue error on desc%s within char%s from %s",
1224 desc.toString().c_str(), desc.getGattCharChecked()->toString().c_str(), toString().c_str());
1225 }
1226 return res;
1227}
1228
1229bool BTGattHandler::readCharacteristicValue(const BTGattChar & decl, jau::POctets & resValue, ssize_type expectedLength) noexcept {
1230 COND_PRINT(env.DEBUG_DATA, "GATTHandler::readCharacteristicValue expLen %zd, decl %s", (size_t)expectedLength, decl.toString().c_str());
1231 const bool res = readValue(decl.value_handle, resValue, expectedLength);
1232 if( !res ) {
1233 WORDY_PRINT("GATT readCharacteristicValue error on char%s from %s", decl.toString().c_str(), toString().c_str());
1234 }
1235 return res;
1236}
1237
1238bool BTGattHandler::readValue(const uint16_t handle, jau::POctets & res, ssize_type expectedLength) noexcept {
1239 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.1 Read Characteristic Value */
1240 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.3 Read Long Characteristic Value */
1241 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
1242 PERF2_TS_T0();
1243
1244 bool done=false;
1245 size_type offset=0;
1246
1247 COND_PRINT(env.DEBUG_DATA, "GATTHandler::readValue expLen %zd, handle %s from %s", (size_t)expectedLength, jau::to_hexstring(handle).c_str(), toString().c_str());
1248
1249 while(!done) {
1250 if( 0 < expectedLength && (size_type)expectedLength <= offset ) {
1251 break; // done
1252 } else if( 0 == expectedLength && 0 < offset ) {
1253 break; // done w/ only one request
1254 } // else 0 > expectedLength: implicit
1255
1256 std::unique_ptr<const AttPDUMsg> pdu = nullptr;
1257
1258 const AttReadReq req0(handle);
1259 const AttReadBlobReq req1(handle, offset);
1260 const AttPDUMsg & req = ( 0 == offset ) ? static_cast<const AttPDUMsg &>(req0) : static_cast<const AttPDUMsg &>(req1);
1261 COND_PRINT(env.DEBUG_DATA, "GATT RV send: %s", req.toString().c_str());
1262 pdu = sendWithReply(req, read_cmd_reply_timeout);
1263 if( nullptr == pdu ) {
1264 ERR_PRINT2("No reply; req %s from %s", req.toString().c_str(), toString().c_str());
1265 return false;
1266 }
1267
1268 COND_PRINT(env.DEBUG_DATA, "GATT RV recv: %s from %s", pdu->toString().c_str(), toString().c_str());
1269 if( pdu->getOpcode() == AttPDUMsg::Opcode::READ_RSP ) {
1270 const AttReadNRsp * p = static_cast<const AttReadNRsp*>(pdu.get());
1271 const jau::TOctetSlice & v = p->getValue();
1272 res += v;
1273 offset += v.size();
1274 if( p->getPDUValueSize() < p->getMaxPDUValueSize(usedMTU) ) {
1275 done = true; // No full ATT_MTU PDU used - end of communication
1276 }
1277 } else if( pdu->getOpcode() == AttPDUMsg::Opcode::READ_BLOB_RSP ) {
1278 const AttReadNRsp * p = static_cast<const AttReadNRsp*>(pdu.get());
1279 const jau::TOctetSlice & v = p->getValue();
1280 if( 0 == v.size() ) {
1281 done = true; // OK by spec: No more data - end of communication
1282 } else {
1283 res += v;
1284 offset += v.size();
1285 if( p->getPDUValueSize() < p->getMaxPDUValueSize(usedMTU) ) {
1286 done = true; // No full ATT_MTU PDU used - end of communication
1287 }
1288 }
1289 } else if( pdu->getOpcode() == AttPDUMsg::Opcode::ERROR_RSP ) {
1290 /**
1291 * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.3 Read Long Characteristic Value
1292 *
1293 * If the Characteristic Value is not longer than (ATT_MTU – 1)
1294 * an ATT_ERROR_RSP PDU with the error
1295 * code set to Attribute Not Long shall be received on the first
1296 * ATT_READ_BLOB_REQ PDU.
1297 */
1298 const AttErrorRsp * p = static_cast<const AttErrorRsp *>(pdu.get());
1300 done = true; // OK by spec: No more data - end of communication
1301 } else {
1302 WORDY_PRINT("GATT readValue unexpected error %s; req %s from %s", pdu->toString().c_str(), req.toString().c_str(), toString().c_str());
1303 done = true;
1304 }
1305 } else {
1306 ERR_PRINT("GATT readValue unexpected reply %s; req %s from %s", pdu->toString().c_str(), req.toString().c_str(), toString().c_str());
1307 done = true;
1308 }
1309 }
1310 PERF2_TS_TD("GATT readValue");
1311
1312 return offset > 0;
1313}
1314
1316 /* BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration */
1317 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value */
1318 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.11 Characteristic Value Indication */
1319 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.12.3 Write Characteristic Descriptor */
1320 COND_PRINT(env.DEBUG_DATA, "GATTHandler::writeDesccriptorValue desc %s", cd.toString().c_str());
1321 const bool res = writeValue(cd.handle, cd.value, true);
1322 if( !res ) {
1323 WORDY_PRINT("GATT writeDescriptorValue error on desc%s within char%s from %s",
1324 cd.toString().c_str(), cd.getGattCharChecked()->toString().c_str(), toString().c_str());
1325 }
1326 return res;
1327}
1328
1330 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value */
1331 COND_PRINT(env.DEBUG_DATA, "GATTHandler::writeCharacteristicValue desc %s, value %s", c.toString().c_str(), value.toString().c_str());
1332 const bool res = writeValue(c.value_handle, value, true);
1333 if( !res ) {
1334 WORDY_PRINT("GATT writeCharacteristicValue error on char%s from %s", c.toString().c_str(), toString().c_str());
1335 }
1336 return res;
1337}
1338
1340 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.1 Write Characteristic Value Without Response */
1341 COND_PRINT(env.DEBUG_DATA, "GATT writeCharacteristicValueNoResp decl %s, value %s", c.toString().c_str(), value.toString().c_str());
1342 return writeValue(c.value_handle, value, false);
1343}
1344
1345bool BTGattHandler::writeValue(const uint16_t handle, const jau::TROOctets & value, const bool withResponse) noexcept {
1346 /* BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration */
1347 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value */
1348 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.11 Characteristic Value Indication */
1349 /* BT Core Spec v5.2: Vol 3, Part G GATT: 4.12.3 Write Characteristic Descriptor */
1350
1351 if( value.size() <= 0 ) {
1352 WARN_PRINT("GATT writeValue size <= 0, no-op: %s", value.toString().c_str());
1353 return false;
1354 }
1355 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
1356
1357 // FIXME TODO: Long Value if value.size() > ( ATT_MTU - 3 )
1358 PERF2_TS_T0();
1359
1360 if( !withResponse ) {
1361 AttWriteCmd req(handle, value);
1362 COND_PRINT(env.DEBUG_DATA, "GATT WV send(resp %d): %s to %s", withResponse, req.toString().c_str(), toString().c_str());
1363
1364 const bool res = send( req );
1365 PERF2_TS_TD("GATT writeValue (no-resp)");
1366 if( !res ) {
1367 ERR_PRINT2("Send failed; req %s from %s", req.toString().c_str(), toString().c_str());
1368 return false;
1369 } else {
1370 return true;
1371 }
1372 }
1373
1374 AttWriteReq req(handle, value);
1375 COND_PRINT(env.DEBUG_DATA, "GATT WV send(resp %d): %s to %s", withResponse, req.toString().c_str(), toString().c_str());
1376
1377 bool res = false;
1378 std::unique_ptr<const AttPDUMsg> pdu = sendWithReply(req, write_cmd_reply_timeout);
1379 if( nullptr == pdu ) {
1380 ERR_PRINT2("No reply; req %s from %s", req.toString().c_str(), toString().c_str());
1381 return false;
1382 }
1383 COND_PRINT(env.DEBUG_DATA, "GATT WV recv: %s from %s", pdu->toString().c_str(), toString().c_str());
1384
1385 if( pdu->getOpcode() == AttPDUMsg::Opcode::WRITE_RSP ) {
1386 // OK
1387 res = true;
1388 } else if( pdu->getOpcode() == AttPDUMsg::Opcode::ERROR_RSP ) {
1389 WORDY_PRINT("GATT writeValue unexpected error %s; req %s from %s", pdu->toString().c_str(), req.toString().c_str(), toString().c_str());
1390 } else {
1391 ERR_PRINT("GATT writeValue unexpected reply %s; req %s from %s", pdu->toString().c_str(), req.toString().c_str(), toString().c_str());
1392 }
1393 PERF2_TS_TD("GATT writeValue (with-resp)");
1394 return res;
1395}
1396
1397bool BTGattHandler::configNotificationIndication(BTGattDesc & cccd, const bool enableNotification, const bool enableIndication) noexcept {
1398 if( !cccd.isClientCharConfig() ) {
1399 ERR_PRINT("Not a ClientCharacteristicConfiguration: %s", cccd.toString().c_str());
1400 return false;
1401 }
1402 /* BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration */
1403 const uint16_t ccc_value = enableNotification | ( enableIndication << 1 );
1404 COND_PRINT(env.DEBUG_DATA, "GATTHandler::configNotificationIndication decl %s, enableNotification %d, enableIndication %d",
1405 cccd.toString().c_str(), enableNotification, enableIndication);
1406 cccd.value.resize(2, 2);
1407 cccd.value.put_uint16_nc(0, ccc_value);
1408 return writeDescriptorValue(cccd);
1409}
1410
1411/*********************************************************************************************************************/
1412/*********************************************************************************************************************/
1413/*********************************************************************************************************************/
1414
1419
1430
1431std::shared_ptr<GattGenericAccessSvc> BTGattHandler::getGenericAccess(jau::darray<BTGattCharRef> & genericAccessCharDeclList) noexcept {
1432 std::shared_ptr<GattGenericAccessSvc> res = nullptr;
1433 jau::POctets value(number(Defaults::MAX_ATT_MTU), 0, jau::lb_endian_t::little);
1434 std::string deviceName = "";
1436 std::shared_ptr<GattPeriphalPreferredConnectionParameters> prefConnParam = nullptr;
1437
1438 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
1439
1440 for(auto & i : genericAccessCharDeclList) {
1441 const BTGattChar & charDecl = *i;
1442 std::shared_ptr<BTGattService> service = charDecl.getServiceUnchecked();
1443 if( nullptr == service || _GENERIC_ACCESS != *(service->type) ) {
1444 continue;
1445 }
1446 if( _DEVICE_NAME == *charDecl.value_type ) {
1447 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1448 deviceName = GattNameToString(value); // mandatory
1449 }
1450 } else if( _APPEARANCE == *charDecl.value_type ) {
1451 if( readCharacteristicValue(charDecl, value.resize(0)) && value.size() >= 2 ) {
1452 appearance = static_cast<AppearanceCat>(value.get_uint16(0)); // mandatory
1453 }
1455 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1456 prefConnParam = GattPeriphalPreferredConnectionParameters::get(value); // optional
1457 }
1458 }
1459 }
1460 if( deviceName.size() > 0 ) {
1461 res = std::make_shared<GattGenericAccessSvc>(deviceName, appearance, prefConnParam);
1462 }
1463 return res;
1464}
1465
1466std::shared_ptr<GattGenericAccessSvc> BTGattHandler::getGenericAccess(jau::darray<BTGattServiceRef> & primServices) noexcept {
1467 for(auto & primService : primServices) {
1468 BTGattServiceRef service = primService;
1469 if( _GENERIC_ACCESS == *service->type ) {
1470 return getGenericAccess(primService->characteristicList);
1471 }
1472 }
1473 return nullptr;
1474}
1475
1476bool BTGattHandler::ping() noexcept {
1477 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
1478 bool readOK = true;
1479
1480 for(size_t i=0; readOK && i<services.size(); i++) {
1481 jau::darray<BTGattCharRef> & genericAccessCharDeclList = services.at(i)->characteristicList;
1483
1484 for(size_t j=0; readOK && j<genericAccessCharDeclList.size(); j++) {
1485 const BTGattChar & charDecl = *genericAccessCharDeclList.at(j);
1486 std::shared_ptr<BTGattService> service = charDecl.getServiceUnchecked();
1487 if( nullptr == service || _GENERIC_ACCESS != *(service->type) ) {
1488 continue;
1489 }
1490 if( _APPEARANCE == *charDecl.value_type ) {
1491 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1492 return true; // unique success case
1493 }
1494 // read failure, might be disconnected
1495 readOK = false;
1496 }
1497 }
1498 }
1499 if( readOK ) {
1500 jau::INFO_PRINT("GATTHandler::pingGATT: No GENERIC_ACCESS Service with APPEARANCE Characteristic available -> disconnect");
1501 } else {
1502 jau::INFO_PRINT("GATTHandler::pingGATT: Read error -> disconnect");
1503 }
1504 disconnect(true /* disconnect_device */, true /* ioerr_cause */); // state -> Disconnected
1505 return false;
1506}
1507
1508std::shared_ptr<GattDeviceInformationSvc> BTGattHandler::getDeviceInformation(jau::darray<BTGattCharRef> & characteristicDeclList) noexcept {
1509 std::shared_ptr<GattDeviceInformationSvc> res = nullptr;
1510 jau::POctets value(number(Defaults::MAX_ATT_MTU), 0, jau::lb_endian_t::little);
1511
1512 jau::POctets systemID(8, 0, jau::lb_endian_t::little);
1513 std::string modelNumber;
1514 std::string serialNumber;
1515 std::string firmwareRevision;
1516 std::string hardwareRevision;
1517 std::string softwareRevision;
1518 std::string manufacturer;
1519 jau::POctets regulatoryCertDataList(128, 0, jau::lb_endian_t::little);
1520 std::shared_ptr<GattPnP_ID> pnpID = nullptr;
1521 bool found = false;
1522
1523 const std::lock_guard<std::recursive_mutex> lock(mtx_command); // RAII-style acquire and relinquish via destructor
1524
1525 for(auto & i : characteristicDeclList) {
1526 const BTGattChar & charDecl = *i;
1527 std::shared_ptr<BTGattService> service = charDecl.getServiceUnchecked();
1528 if( nullptr == service || _DEVICE_INFORMATION != *(service->type) ) {
1529 continue;
1530 }
1531 found = true;
1532 if( _SYSTEM_ID == *charDecl.value_type ) {
1533 if( readCharacteristicValue(charDecl, systemID.resize(0)) ) {
1534 // nop
1535 }
1536 } else if( _REGULATORY_CERT_DATA_LIST == *charDecl.value_type ) {
1537 if( readCharacteristicValue(charDecl, regulatoryCertDataList.resize(0)) ) {
1538 // nop
1539 }
1540 } else if( _PNP_ID == *charDecl.value_type ) {
1541 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1542 pnpID = GattPnP_ID::get(value);
1543 }
1544 } else if( _MODEL_NUMBER_STRING == *charDecl.value_type ) {
1545 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1546 modelNumber = GattNameToString(value);
1547 }
1548 } else if( _SERIAL_NUMBER_STRING == *charDecl.value_type ) {
1549 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1550 serialNumber = GattNameToString(value);
1551 }
1552 } else if( _FIRMWARE_REVISION_STRING == *charDecl.value_type ) {
1553 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1554 firmwareRevision = GattNameToString(value);
1555 }
1556 } else if( _HARDWARE_REVISION_STRING == *charDecl.value_type ) {
1557 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1558 hardwareRevision = GattNameToString(value);
1559 }
1560 } else if( _SOFTWARE_REVISION_STRING == *charDecl.value_type ) {
1561 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1562 softwareRevision = GattNameToString(value);
1563 }
1564 } else if( _MANUFACTURER_NAME_STRING == *charDecl.value_type ) {
1565 if( readCharacteristicValue(charDecl, value.resize(0)) ) {
1566 manufacturer = GattNameToString(value);
1567 }
1568 }
1569 }
1570 if( found ) {
1571 res = std::make_shared<GattDeviceInformationSvc>(systemID, modelNumber, serialNumber,
1572 firmwareRevision, hardwareRevision, softwareRevision,
1573 manufacturer, regulatoryCertDataList, pnpID);
1574 }
1575 return res;
1576}
1577
1578std::shared_ptr<GattDeviceInformationSvc> BTGattHandler::getDeviceInformation(jau::darray<BTGattServiceRef> & primServices) noexcept {
1579 for(auto & primService : primServices) {
1580 BTGattServiceRef service = primService;
1581 if( _DEVICE_INFORMATION == *service->type ) {
1582 return getDeviceInformation(primService->characteristicList);
1583 }
1584 }
1585 return nullptr;
1586}
1587
1588std::string BTGattHandler::toString() const noexcept {
1589 return "GattHndlr["+to_string(getRole())+", "+deviceString+
1590 ", mode "+to_string(gattServerHandler->getMode())+
1591 ", mtu "+std::to_string(usedMTU.load())+
1592 ", listener[BTGatt "+std::to_string(gattCharListenerList.size())+
1593 ", Native "+std::to_string(nativeGattCharListenerList.size())+
1594 "], l2capWorker[running "+std::to_string(l2cap_reader_service.is_running())+
1595 ", shallStop "+std::to_string(l2cap_reader_service.shall_stop())+
1596 ", thread_id "+jau::to_hexstring((void*)l2cap_reader_service.thread_id())+ // NOLINT(performance-no-int-to-ptr)
1597 "], "+getStateString()+"]";
1598}
static const jau::uuid16_t _SERIAL_NUMBER_STRING(GattCharacteristicType::SERIAL_NUMBER_STRING)
static const jau::uuid16_t _FIRMWARE_REVISION_STRING(GattCharacteristicType::FIRMWARE_REVISION_STRING)
static const jau::uuid16_t _GENERIC_ACCESS(GattServiceType::GENERIC_ACCESS)
static const jau::uuid16_t _APPEARANCE(GattCharacteristicType::APPEARANCE)
static const jau::uuid16_t _HARDWARE_REVISION_STRING(GattCharacteristicType::HARDWARE_REVISION_STRING)
static const jau::uuid16_t _PNP_ID(GattCharacteristicType::PNP_ID)
static const jau::uuid16_t _DEVICE_INFORMATION(GattServiceType::DEVICE_INFORMATION)
static const jau::uuid16_t _DEVICE_NAME(GattCharacteristicType::DEVICE_NAME)
static const jau::uuid16_t _REGULATORY_CERT_DATA_LIST(GattCharacteristicType::REGULATORY_CERT_DATA_LIST)
static const jau::uuid16_t _MANUFACTURER_NAME_STRING(GattCharacteristicType::MANUFACTURER_NAME_STRING)
static const jau::uuid16_t _PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS(GattCharacteristicType::PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS)
static const jau::uuid16_t _SOFTWARE_REVISION_STRING(GattCharacteristicType::SOFTWARE_REVISION_STRING)
static const jau::uuid16_t _SYSTEM_ID(GattCharacteristicType::SYSTEM_ID)
static jau::cow_darray< BTGattHandler::NativeGattCharListenerRef >::equal_comparator _nativeGattCharListenerRefEqComparator
static const jau::uuid16_t _MODEL_NUMBER_STRING(GattCharacteristicType::MODEL_NUMBER_STRING)
#define E_FILE_LINE
constexpr_cxx20 jau::nsize_t getElementCount() const noexcept
Number of elements.
jau::nsize_t getElementPDUOffset(const jau::nsize_t elementIdx) const
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.1.1 ATT_ERROR_RSP.
constexpr ErrorCode getErrorCode() const noexcept
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.2.1 ATT_EXCHANGE_MTU_REQ BT Core Spec v5....
constexpr uint16_t getMTUSize() const noexcept
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.3.3 ATT_FIND_BY_TYPE_VALUE_REQ.
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.3.1 ATT_FIND_INFORMATION_REQ.
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.3.2 ATT_FIND_INFORMATION_RSP.
uint16_t getElementHandle(const jau::nsize_t elementIdx) const
std::unique_ptr< const jau::uuid_t > getElementValue(const jau::nsize_t elementIdx) const
ATT Protocol PDUs Vol 3, Part F 3.4.7.3.
ATT Protocol PDUs Vol 3, Part F 3.4.7.1 and 3.4.7.2.
jau::TOctetSlice const & getValue() const noexcept
constexpr uint16_t getHandle() const noexcept
Handles the Attribute Protocol (ATT) using Protocol Data Unit (PDU) encoded messages over L2CAP chann...
const uint64_t ts_creation
creation timestamp in milliseconds
Opcode
ATT Opcode Summary Vol 3, Part F 3.4.8.
jau::POctets pdu
actual received PDU
static constexpr OpcodeType get_type(const Opcode rhs) noexcept
virtual std::string toString() const noexcept
static std::unique_ptr< const AttPDUMsg > getSpecialized(const uint8_t *buffer, jau::nsize_t const buffer_size) noexcept
Return a newly created specialized instance pointer to base class.
constexpr_cxx20 jau::nsize_t getPDUValueSize() const noexcept
Returns the net octet size of this PDU's attributes value, i.e.
constexpr_cxx20 jau::nsize_t getMaxPDUValueSize(const jau::nsize_t mtu) const noexcept
Returns the theoretical maximum value size of a PDU's attribute value.
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.4.5 ATT_BLOB_READ_REQ.
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.4.10 ATT_READ_BY_GROUP_TYPE_RSP.
constexpr_cxx20 jau::nsize_t getElementSize() const noexcept override
Returns size of each element, i.e.
uint16_t getElementEndHandle(const jau::nsize_t elementIdx) const
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.4.1 ATT_READ_BY_TYPE_REQ.
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.4.2 ATT_READ_BY_TYPE_RSP.
uint16_t getElementHandle(const jau::nsize_t elementIdx) const
constexpr_cxx20 jau::nsize_t getElementSize() const noexcept override
Returns size of each element, i.e.
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.4.4 ATT_READ_RSP BT Core Spec v5.2: Vol 3,...
constexpr jau::TOctetSlice const & getValue() const noexcept
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.4.3 ATT_READ_REQ.
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.5.3 ATT_WRITE_CMD.
BT Core Spec v5.2: Vol 3, Part F ATT: 3.4.5.1 ATT_WRITE_REQ.
HCIStatusCode disconnect(const HCIStatusCode reason=HCIStatusCode::REMOTE_USER_TERMINATED_CONNECTION) noexcept
Disconnect the LE or BREDR peer's GATT and HCI connection.
Definition: BTDevice.cpp:2488
BTGattChar event listener for notification and indication events.
Definition: BTGattChar.hpp:450
Representing a Gatt Characteristic object from the GATTRole::Client perspective.
Definition: BTGattChar.hpp:94
BTGattServiceRef getServiceUnchecked() const noexcept
Definition: BTGattChar.hpp:163
jau::snsize_t ssize_type
Definition: BTGattChar.hpp:118
std::unique_ptr< const jau::uuid_t > value_type
Definition: BTGattChar.hpp:140
PropertyBitVal
BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1.1 Characteristic Properties.
Definition: BTGattChar.hpp:105
Representing a Gatt Characteristic Descriptor object from the GATTRole::Client perspective.
Definition: BTGattDesc.hpp:74
static BTGattEnv & get() noexcept
A thread safe GATT handler associated to one device via one L2CAP connection.
bool readDescriptorValue(BTGattDesc &cd, ssize_type expectedLength=-1) noexcept
BT Core Spec v5.2: Vol 3, Part G GATT: 4.12.1 Read Characteristic Descriptor.
bool disconnect(const bool disconnect_device, const bool ioerr_cause) noexcept
Disconnect this BTGattHandler and optionally the associated device.
bool send(const AttPDUMsg &msg) noexcept
Sends the given AttPDUMsg to the connected device via l2cap.
bool writeValue(const uint16_t handle, const jau::TROOctets &value, const bool withResponse) noexcept
Generic write GATT value and long value.
void notifyNativeReplyReceived(const AttPDUMsg &pduReply, const BTDeviceRef &clientDest) noexcept
Notify all NativeGattCharListener about a low-level AttPDUMsg reply being received from this GATTRole...
bool addCharListener(const BTGattCharListenerRef &l) noexcept
Add the given listener to the list if not already present.
bool getSendIndicationConfirmation() noexcept
Returns whether sending an immediate confirmation for received indication events from the device is e...
void notifyNativeMTUResponse(const uint16_t clientMTU, const AttPDUMsg &pduReply, const AttErrorRsp::ErrorCode error_reply, const uint16_t serverMTU, const uint16_t usedMTU, const BTDeviceRef &clientRequester) noexcept
Notify all NativeGattCharListener about a completed MTU exchange request and response to and from thi...
std::string toString() const noexcept
bool readValue(const uint16_t handle, jau::POctets &res, ssize_type expectedLength=-1) noexcept
Generic read GATT value and long value.
bool writeCharacteristicValueNoResp(const BTGattChar &c, const jau::TROOctets &value) noexcept
BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.1 Write Characteristic Value Without Response.
void setSendIndicationConfirmation(const bool v) noexcept
Enable or disable sending an immediate confirmation for received indication events from the device.
bool readCharacteristicValue(const BTGattChar &c, jau::POctets &res, ssize_type expectedLength=-1) noexcept
BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.1 Read Characteristic Value.
void notifyNativeWriteResponse(const AttPDUMsg &pduReply, const AttErrorRsp::ErrorCode error_code, const BTDeviceRef &clientDest) noexcept
Notify all NativeGattCharListener about a write response received from this GATTRole::Server.
bool sendIndication(const uint16_t char_value_handle, const jau::TROOctets &value) noexcept
Send an indication event consisting out of the given value representing the given characteristic valu...
void notifyNativeReadResponse(const uint16_t handle, const uint16_t value_offset, const AttPDUMsg &pduReply, const AttErrorRsp::ErrorCode error_reply, const jau::TROOctets &data_reply, const BTDeviceRef &clientRequester) noexcept
Notify all NativeGattCharListener about a completed read request and response to and from this GATTRo...
bool configNotificationIndication(BTGattDesc &cd, const bool enableNotification, const bool enableIndication) noexcept
BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration.
bool initClientGatt(const std::shared_ptr< BTGattHandler > &shared_this, bool &already_init) noexcept
Initialize the connection and internal data set for GATT client operations:
bool writeDescriptorValue(const BTGattDesc &cd) noexcept
BT Core Spec v5.2: Vol 3, Part G GATT: 4.12.3 Write Characteristic Descriptors.
std::string getStateString() const noexcept
bool sendNotification(const uint16_t char_value_handle, const jau::TROOctets &value) noexcept
Send a notification event consisting out of the given value representing the given characteristic val...
size_type removeAllAssociatedCharListener(const BTGattCharRef &associatedChar) noexcept
Remove all BTGattCharListener from the list, which are associated to the given BTGattChar when added ...
bool removeCharListener(const BTGattCharListenerRef &l) noexcept
Remove the given listener from the list.
BTGattHandler(const BTDeviceRef &device, L2CAPClient &l2cap_att, const int32_t supervision_timeout) noexcept
Constructing a new BTGattHandler instance with its opened and connected L2CAP channel.
bool writeCharacteristicValue(const BTGattChar &c, const jau::TROOctets &value) noexcept
BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value.
std::unique_ptr< const AttPDUMsg > sendWithReply(const AttPDUMsg &msg, const jau::fraction_i64 &timeout) noexcept
Sends the given AttPDUMsg to the connected device via l2cap using send().
std::shared_ptr< GattGenericAccessSvc > getGenericAccess() noexcept
Returns the internal kept shared GattGenericAccessSvc instance.
BTGattCharRef findCharacterisicsByValueHandle(const GattServiceList_t &services_, const uint16_t charValueHandle) noexcept
Find and return the BTGattChar within given list of primary services via given characteristic value h...
bool ping() noexcept
Issues a ping to the device, validating whether it is still reachable.
std::shared_ptr< NativeGattCharListener > NativeGattCharListenerRef
BTDeviceRef getDeviceChecked() const
size_type removeAllCharListener() noexcept
Remove all event listener from the list.
std::shared_ptr< GattDeviceInformationSvc > getDeviceInformation(GattServiceList_t &primServices) noexcept
~BTGattHandler() noexcept
Destructor closing this instance including L2CAP channel, see disconnect().
void printCharListener() noexcept
Print a list of all BTGattCharListener and NativeGattCharListener.
void notifyNativeRequestSent(const AttPDUMsg &pduRequest, const BTDeviceRef &clientSource) noexcept
Notify all NativeGattCharListener about a low-level AttPDUMsg request being sent to this GATTRole::Se...
void notifyNativeWriteRequest(const uint16_t handle, const jau::TROOctets &data, const NativeGattCharSections_t &sections, const bool with_response, const BTDeviceRef &clientSource) noexcept
Notify all NativeGattCharListener about a completed write request sent to this GATTRole::Server.
GATTRole getRole() const noexcept
Return the local GATTRole to the remote BTDevice.
std::shared_ptr< Listener > ListenerRef
@ DB
Database mode, the default operating on given list of DBGattService.
L2CAP read/write communication channel to remote device.
Definition: L2CAPComm.hpp:195
static std::string getRWExitCodeString(const RWExitCode ec) noexcept
Definition: L2CAPComm.cpp:510
std::string getStateString() const noexcept override
Definition: L2CAPComm.hpp:271
static constexpr int number(const Defaults d) noexcept
Definition: L2CAPComm.hpp:200
bool hasIOError() const noexcept
Definition: L2CAPComm.hpp:270
virtual std::string getStateString() const noexcept=0
bool is_open() const noexcept
Definition: L2CAPComm.hpp:173
Persistent endian aware octet data, i.e.
Definition: octets.hpp:560
POctets & resize(const nsize_t newCapacity, const nsize_t newSize)
Resizes this instance, including its capacity.
Definition: octets.hpp:839
Transient endian aware octet data slice, i.e.
Definition: octets.hpp:495
constexpr lb_endian_t byte_order() const noexcept
Returns byte order of this octet store.
Definition: octets.hpp:519
constexpr nsize_t size() const noexcept
Definition: octets.hpp:521
constexpr uint8_t const * get_ptr_nc(const nsize_t i) const noexcept
Definition: octets.hpp:542
Transient read only and endian aware octet data, i.e.
Definition: octets.hpp:67
std::unique_ptr< const uuid_t > get_uuid(const nsize_t i, const uuid_t::TypeSize tsize) const
Definition: octets.hpp:267
constexpr nsize_t size() const noexcept
Returns the used memory size for read and write operations, may be zero.
Definition: octets.hpp:162
uint16_t get_uint16(const nsize_t i) const
Definition: octets.hpp:180
uint8_t get_uint8(const nsize_t i) const
Definition: octets.hpp:164
Implementation of a Copy-On-Write (CoW) using jau::darray as the underlying storage,...
Definition: cow_darray.hpp:127
constexpr_atomic void clear() noexcept
Like std::vector::clear(), but ending with zero capacity.
Definition: cow_darray.hpp:752
bool(* equal_comparator)(const value_type &a, const value_type &b)
Generic value_type equal comparator to be user defined for e.g.
Definition: cow_darray.hpp:989
constexpr_atomic size_type size() const noexcept
Like std::vector::size().
Definition: cow_darray.hpp:717
constexpr iterator begin()
Returns an jau::cow_rw_iterator to the first element of this CoW storage.
Definition: cow_darray.hpp:660
Implementation of a dynamic linear array storage, aka vector.
Definition: darray.hpp:148
constexpr size_type size() const noexcept
Like std::vector::size().
Definition: darray.hpp:763
const_reference at(size_type i) const
Like std::vector::at(size_type), immutable reference.
Definition: darray.hpp:814
constexpr void clear() noexcept
Like std::vector::clear(), but ending with zero capacity.
Definition: darray.hpp:904
Service runner, a reusable dedicated thread performing custom user services.
bool is_running() const noexcept
Returns true if service is running.
pthread_t thread_id() const noexcept
Return the thread-id of this service service thread, zero if not running.
bool shall_stop() const noexcept
Returns true if service shall stop.
static TypeSize toTypeSize(const jau::nsize_t size)
Definition: uuid.cpp:47
#define ERR_PRINT(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition: debug.hpp:109
#define PERF3_TS_TD(m)
Definition: debug.hpp:94
#define COND_PRINT(C,...)
Use for conditional plain messages, prefix '[elapsed_time] '.
Definition: debug.hpp:151
#define WORDY_PRINT(...)
Use for environment-variable environment::VERBOSE conditional verbose messages, prefix '[elapsed_time...
Definition: debug.hpp:68
#define ABORT(...)
Use for unconditional ::abort() call with given messages, prefix '[elapsed_time] ABORT @ file:line fu...
Definition: debug.hpp:101
#define PERF_TS_T0()
Definition: debug.hpp:79
#define PERF2_TS_TD(m)
Definition: debug.hpp:87
#define PERF2_TS_T0()
Definition: debug.hpp:86
#define DBG_PRINT(...)
Use for environment-variable environment::DEBUG conditional debug messages, prefix '[elapsed_time] De...
Definition: debug.hpp:52
#define PERF3_TS_T0()
Definition: debug.hpp:93
#define ERR_PRINT2(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition: debug.hpp:112
#define WARN_PRINT(...)
Use for unconditional warning messages, prefix '[elapsed_time] Warning @ FILE:LINE FUNC: '.
Definition: debug.hpp:123
#define IRQ_PRINT(...)
Use for unconditional interruption messages, prefix '[elapsed_time] Interrupted @ FILE:LINE FUNC: '.
Definition: debug.hpp:115
#define PERF_TS_TD(m)
Definition: debug.hpp:80
constexpr UnaryFunction for_each_fidelity(InputIt first, InputIt last, UnaryFunction f)
Like jau::for_each(), see above.
@ little
Identifier for little endian, equivalent to endian::little.
std::string to_string(const alphabet &v) noexcept
Definition: base_codec.hpp:97
constexpr const jau::fraction_i64 THREAD_SHUTDOWN_TIMEOUT_MS
Maximum time in fractions of seconds to wait for a thread shutdown.
Definition: DBTConst.hpp:73
std::string GattNameToString(const jau::TROOctets &v) noexcept
Converts a GATT Name (not null-terminated) UTF8 to a null-terminated C++ string.
std::shared_ptr< BTDevice > BTDeviceRef
Definition: BTDevice.hpp:1347
std::string to_string(const DiscoveryPolicy v) noexcept
Definition: BTAdapter.cpp:58
HCIStatusCode
BT Core Spec v5.2: Vol 1, Part F Controller Error Codes: 1.3 List of Error Codes.
Definition: HCITypes.hpp:138
constexpr uint8_t number(const DiscoveryPolicy rhs) noexcept
Definition: BTAdapter.hpp:100
@ Client
Local GATT client role to a remote BTDevice BTRole::Slave running a GATTRole::Server.
@ Server
Local GATT server role to a remote BTDevice BTRole::Master running a GATTRole::Client.
std::shared_ptr< BTGattCharListener > BTGattCharListenerRef
Definition: BTGattChar.hpp:70
std::shared_ptr< BTGattChar > BTGattCharRef
Definition: BTGattChar.hpp:410
std::shared_ptr< BTGattService > BTGattServiceRef
Definition: BTGattChar.hpp:67
std::shared_ptr< DBGattChar > DBGattCharRef
jau::function< R(A...)> bind_member(C1 *base, R(C0::*mfunc)(A...)) noexcept
Bind given class instance and non-void member function to an anonymous function using func_member_tar...
constexpr T min(const T x, const T y) noexcept
Returns the minimum of two integrals (w/ branching) in O(1)
Definition: base_math.hpp:177
uint_fast32_t nsize_t
Natural 'size_t' alternative using uint_fast32_t as its natural sized type.
Definition: int_types.hpp:53
int_fast32_t snsize_t
Natural 'ssize_t' alternative using int_fast32_t as its natural sized type.
Definition: int_types.hpp:65
constexpr T max(const T x, const T y) noexcept
Returns the maximum of two integrals (w/ branching) in O(1)
Definition: base_math.hpp:191
std::string to_hexstring(value_type const &v) noexcept
Produce a lower-case hexadecimal string representation of the given pointer.
__pack(...): Produces MSVC, clang and gcc compatible lead-in and -out macros.
Definition: backtrace.hpp:32
void INFO_PRINT(const char *format,...) noexcept
Use for unconditional informal messages, prefix '[elapsed_time] Info: '.
Definition: debug.cpp:248
static std::shared_ptr< GattPeriphalPreferredConnectionParameters > get(const jau::TROOctets &source) noexcept
static std::shared_ptr< GattPnP_ID > get(const jau::TROOctets &source) noexcept
CXX_ALWAYS_INLINE _Tp load() const noexcept
@ GENERIC_ACCESS
This service contains generic information about the device.
@ DEVICE_INFORMATION
This service exposes manufacturer and/or vendor information about a device.
@ SOFTWARE_REVISION_STRING
@ REGULATORY_CERT_DATA_LIST
@ SERIAL_NUMBER_STRING
@ MODEL_NUMBER_STRING
@ DEVICE_NAME
@ APPEARANCE
@ MANUFACTURER_NAME_STRING
@ PNP_ID
@ PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS
@ FIRMWARE_REVISION_STRING
@ SYSTEM_ID
Mandatory: uint40.
@ HARDWARE_REVISION_STRING