Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
dbt_client01.hpp
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2022 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25#ifndef DBT_CLIENT01_HPP_
26#define DBT_CLIENT01_HPP_
27
28#include "dbt_constants.hpp"
29
30#include "dbt_client_test.hpp"
31
32#include <jau/latch.hpp>
33
34class DBTClient01;
35typedef std::shared_ptr<DBTClient01> DBTClient01Ref;
36
37using namespace jau;
38using namespace jau::fractions_i64_literals;
39
40/**
41 * This central BTRole::Master participant works with DBTServer00.
42 */
43class DBTClient01 : public DBTClientTest {
44 private:
45 bool do_disconnect = true;
46 bool do_disconnect_randomly = false;
47
48 bool do_remove_device = false;
49
50 DiscoveryPolicy discoveryPolicy = DiscoveryPolicy::PAUSE_CONNECTED_UNTIL_READY; // default value
51
52 static const bool GATT_VERBOSE = false;
53 static const bool SHOW_UPDATE_EVENTS = false;
54
55 jau::sc_atomic_int deviceReadyCount = 0;
56
57 jau::latch running_threads = jau::latch(0);
58 jau::sc_atomic_int disconnectCount = 0;
59 jau::sc_atomic_int notificationsReceived = 0;
60 jau::sc_atomic_int indicationsReceived = 0;
61 jau::sc_atomic_int completedGATTCommands = 0;
62 jau::sc_atomic_int completedMeasurementsTotal = 0;
63 jau::sc_atomic_int completedMeasurementsSuccess = 0;
64 jau::sc_atomic_int measurementsLeft = 0;
65
66 const uint64_t timestamp_t0 = getCurrentMilliseconds();
67 // const fraction_i64 timestamp_t0 = jau::getMonotonicMicroseconds();
68
69 const uint8_t cmd_arg = 0x44;
70
72 public:
73 DBTClient01& parent;
74
75 MyAdapterStatusListener(DBTClient01& p) : parent(p) {}
76
77 void adapterSettingsChanged(BTAdapter &a, const AdapterSetting oldmask, const AdapterSetting newmask,
78 const AdapterSetting changedmask, const uint64_t timestamp) override {
79 const bool initialSetting = AdapterSetting::NONE == oldmask;
80 if( initialSetting ) {
81 fprintf_td(stderr, "****** Client SETTINGS_INITIAL: %s -> %s, changed %s\n", to_string(oldmask).c_str(),
82 to_string(newmask).c_str(), to_string(changedmask).c_str());
83 } else {
84 fprintf_td(stderr, "****** Client SETTINGS_CHANGED: %s -> %s, changed %s\n", to_string(oldmask).c_str(),
85 to_string(newmask).c_str(), to_string(changedmask).c_str());
86
87 const bool justPoweredOn = isAdapterSettingBitSet(changedmask, AdapterSetting::POWERED) &&
88 isAdapterSettingBitSet(newmask, AdapterSetting::POWERED);
89 if( justPoweredOn && DiscoveryPolicy::AUTO_OFF != parent.discoveryPolicy && *parent.clientAdapter == a ) {
90 std::thread dc(&DBTClient01::startDiscovery, &parent, "powered_on"); // @suppress("Invalid arguments")
91 dc.detach();
92 }
93 }
94 fprintf_td(stderr, "Client Status BTAdapter:\n");
95 fprintf_td(stderr, "%s\n", a.toString().c_str());
96 (void)timestamp;
97 }
98
99 void discoveringChanged(BTAdapter &a, const ScanType currentMeta, const ScanType changedType, const bool changedEnabled, const DiscoveryPolicy policy, const uint64_t timestamp) override {
100 fprintf_td(stderr, "****** Client DISCOVERING: meta %s, changed[%s, enabled %d, policy %s]: %s\n",
101 to_string(currentMeta).c_str(), to_string(changedType).c_str(), changedEnabled, to_string(policy).c_str(), a.toString().c_str());
102 (void)timestamp;
103 }
104
105 bool deviceFound(const BTDeviceRef& device, const uint64_t timestamp) override {
106 (void)timestamp;
107 if( BTDeviceRegistry::isWaitingForDevice(device->getAddressAndType().address, device->getName()) &&
108 0 < parent.measurementsLeft
109 )
110 {
111 fprintf_td(stderr, "****** Client FOUND__-0: Connecting %s\n", device->toString(true).c_str());
112 {
113 const uint64_t td = jau::getCurrentMilliseconds() - parent.timestamp_t0; // adapter-init -> now
114 fprintf_td(stderr, "PERF: adapter-init -> FOUND__-0 %" PRIu64 " ms\n", td);
115 }
116 parent.running_threads.count_up();
117 std::thread dc(&DBTClient01::connectDiscoveredDevice, &parent, device); // @suppress("Invalid arguments")
118 dc.detach();
119 return true;
120 } else {
121 fprintf_td(stderr, "****** Client FOUND__-1: NOP %s\n", device->toString(true).c_str());
122 return false;
123 }
124 }
125
126 void deviceUpdated(const BTDeviceRef& device, const EIRDataType updateMask, const uint64_t timestamp) override {
127 (void)device;
128 (void)updateMask;
129 (void)timestamp;
130 }
131
132 void deviceConnected(const BTDeviceRef& device, const bool discovered, const uint64_t timestamp) override {
133 fprintf_td(stderr, "****** Client CONNECTED (discovered %d): %s\n", discovered, device->toString(true).c_str());
134 (void)discovered;
135 (void)timestamp;
136 }
137
138 void devicePairingState(const BTDeviceRef& device, const SMPPairingState state, const PairingMode mode, const uint64_t timestamp) override {
139 fprintf_td(stderr, "****** Client PAIRING STATE: state %s, mode %s, %s\n",
140 to_string(state).c_str(), to_string(mode).c_str(), device->toString().c_str());
141 (void)timestamp;
142 switch( state ) {
143 case SMPPairingState::NONE:
144 // next: deviceReady(..)
145 break;
146 case SMPPairingState::FAILED: {
147 const bool res = SMPKeyBin::remove(DBTConstants::CLIENT_KEY_PATH, *device);
148 fprintf_td(stderr, "****** PAIRING_STATE: state %s; Remove key file %s, res %d\n",
149 to_string(state).c_str(), SMPKeyBin::getFilename(DBTConstants::CLIENT_KEY_PATH, *device).c_str(), res);
150 // next: deviceReady() or deviceDisconnected(..)
151 } break;
152 case SMPPairingState::REQUESTED_BY_RESPONDER:
153 // next: FEATURE_EXCHANGE_STARTED
154 break;
155 case SMPPairingState::FEATURE_EXCHANGE_STARTED:
156 // next: FEATURE_EXCHANGE_COMPLETED
157 break;
158 case SMPPairingState::FEATURE_EXCHANGE_COMPLETED:
159 // next: PASSKEY_EXPECTED... or KEY_DISTRIBUTION
160 break;
161 case SMPPairingState::PASSKEY_EXPECTED: {
162 const BTSecurityRegistry::Entry* sec = BTSecurityRegistry::getStartOf(device->getAddressAndType().address, device->getName());
163 if( nullptr != sec && sec->getPairingPasskey() != BTSecurityRegistry::Entry::NO_PASSKEY ) {
164 std::thread dc(&BTDevice::setPairingPasskey, device, static_cast<uint32_t>( sec->getPairingPasskey() ));
165 dc.detach();
166 } else {
167 std::thread dc(&BTDevice::setPairingPasskey, device, 0);
168 // 3s disconnect: std::thread dc(&BTDevice::setPairingPasskeyNegative, device);
169 dc.detach();
170 }
171 // next: KEY_DISTRIBUTION or FAILED
172 } break;
173 case SMPPairingState::NUMERIC_COMPARE_EXPECTED: {
174 const BTSecurityRegistry::Entry* sec = BTSecurityRegistry::getStartOf(device->getAddressAndType().address, device->getName());
175 if( nullptr != sec ) {
176 std::thread dc(&BTDevice::setPairingNumericComparison, device, sec->getPairingNumericComparison());
177 dc.detach();
178 } else {
179 std::thread dc(&BTDevice::setPairingNumericComparison, device, false);
180 dc.detach();
181 }
182 // next: KEY_DISTRIBUTION or FAILED
183 } break;
184 case SMPPairingState::OOB_EXPECTED:
185 // FIXME: ABORT
186 break;
187 case SMPPairingState::KEY_DISTRIBUTION:
188 // next: COMPLETED or FAILED
189 break;
190 case SMPPairingState::COMPLETED:
191 // next: deviceReady(..)
192 break;
193 default: // nop
194 break;
195 }
196 }
197
198 void disconnectDeviceRandomly(BTDeviceRef device) { // NOLINT(performance-unnecessary-value-param):
199 // sleep range: 100 - 1500 ms
200 // sleep range: 100 - 1500 ms
201 static const int sleep_min = 100;
202 static const int sleep_max = 1500;
203 static std::random_device rng_hw;;
204 static std::uniform_int_distribution<int> rng_dist(sleep_min, sleep_max);
205 const int64_t sleep_dur = rng_dist(rng_hw);
206 jau::sleep_for( sleep_dur * 1_ms );
207 if( nullptr != device ) {
208 fprintf_td(stderr, "****** Client i470 disconnectDevice(delayed %d ms): client %s\n", sleep_dur, device->toString().c_str());
209 device->disconnect();
210 } else {
211 fprintf_td(stderr, "****** Client i470 disconnectDevice(delayed %d ms): client null\n", sleep_dur);
212 }
213 parent.running_threads.count_down();
214 }
215
216 void deviceReady(const BTDeviceRef& device, const uint64_t timestamp) override {
217 (void)timestamp;
218 {
219 parent.deviceReadyCount++;
220 fprintf_td(stderr, "****** Client READY-0: Processing[%d] %s\n", parent.deviceReadyCount.load(), device->toString(true).c_str());
221
222 // Be nice to Test* case, allowing to reach its own listener.deviceReady() added later
223 parent.running_threads.count_up();
224 std::thread dc(&DBTClient01::processReadyDevice, &parent, device);
225 dc.detach();
226 // processReadyDevice(device); // AdapterStatusListener::deviceReady() explicitly allows prolonged and complex code execution!
227
228 if( parent.do_disconnect_randomly ) {
229 parent.running_threads.count_up();
230 std::thread disconnectThread(&MyAdapterStatusListener::disconnectDeviceRandomly, this, device);
231 disconnectThread.detach();
232 }
233 }
234 }
235
236 void deviceDisconnected(const BTDeviceRef& device, const HCIStatusCode reason, const uint16_t handle, const uint64_t timestamp) override {
237 fprintf_td(stderr, "****** Client DISCONNECTED: Reason 0x%X (%s), old handle %s: %s\n",
238 static_cast<uint8_t>(reason), to_string(reason).c_str(),
239 to_hexstring(handle).c_str(), device->toString(true).c_str());
240 (void)timestamp;
241
242 parent.disconnectCount++;
243
244 parent.running_threads.count_up();
245 std::thread dc(&DBTClient01::removeDevice, &parent, device); // @suppress("Invalid arguments")
246 dc.detach();
247 }
248
249 std::string toString() const noexcept override {
250 return "Client MyAdapterStatusListener[this "+to_hexstring(this)+"]";
251 }
252
253 };
254
256 private:
257 DBTClient01& parent;
258
259 public:
260 MyGATTEventListener(DBTClient01& p) : parent(p) {}
261
262 void notificationReceived(BTGattCharRef charDecl, const TROOctets& char_value, const uint64_t timestamp) override {
263 if( GATT_VERBOSE ) {
264 const uint64_t tR = jau::getCurrentMilliseconds();
265 fprintf_td(stderr, "** Characteristic-Notify: UUID %s, td %" PRIu64 " ******\n",
266 charDecl->value_type->toUUID128String().c_str(), (tR-timestamp));
267 fprintf_td(stderr, "** Characteristic: %s ******\n", charDecl->toString().c_str());
268 fprintf_td(stderr, "** Value R: %s ******\n", char_value.toString().c_str());
269 fprintf_td(stderr, "** Value S: %s ******\n", jau::dfa_utf8_decode(char_value.get_ptr(), char_value.size()).c_str());
270 }
271 parent.notificationsReceived++;
272 }
273
274 void indicationReceived(BTGattCharRef charDecl,
275 const TROOctets& char_value, const uint64_t timestamp,
276 const bool confirmationSent) override
277 {
278 if( GATT_VERBOSE ) {
279 const uint64_t tR = jau::getCurrentMilliseconds();
280 fprintf_td(stderr, "** Characteristic-Indication: UUID %s, td %" PRIu64 ", confirmed %d ******\n",
281 charDecl->value_type->toUUID128String().c_str(), (tR-timestamp), confirmationSent);
282 fprintf_td(stderr, "** Characteristic: %s ******\n", charDecl->toString().c_str());
283 fprintf_td(stderr, "** Value R: %s ******\n", char_value.toString().c_str());
284 fprintf_td(stderr, "** Value S: %s ******\n", jau::dfa_utf8_decode(char_value.get_ptr(), char_value.size()).c_str());
285 }
286 parent.indicationsReceived++;
287 }
288 };
289
290 const std::string adapterShortName = "TDev2Clt";
291 std::string adapterName = "TestDev2_Clt";
292 EUI48 useAdapter = EUI48::ALL_DEVICE;
293 BTMode btMode = BTMode::DUAL;
294 BTAdapterRef clientAdapter = nullptr;
295 std::shared_ptr<AdapterStatusListener> myAdapterStatusListener = std::make_shared<MyAdapterStatusListener>(*this);
296
297 public:
298 DBTClient01(const std::string& adapterName_, const EUI48 useAdapter_, const BTMode btMode_, const bool do_disconnect_randomly_=false) {
299 this->adapterName = adapterName_;
300 this->useAdapter = useAdapter_;
301 this->btMode = btMode_;
302 this->do_disconnect_randomly = do_disconnect_randomly_;
303 }
304
305 ~DBTClient01() override {
306 fprintf_td(stderr, "****** Client dtor: running_threads %zu\n", running_threads.value());
307 running_threads.wait_for( 10_s );
308 }
309
310 std::string getName() override { return adapterName; }
311
312 void setAdapter(BTAdapterRef clientAdapter_) override {
313 this->clientAdapter = clientAdapter_;
314 }
315
316 BTAdapterRef getAdapter() override { return clientAdapter; }
317
318 void setProtocolSessionsLeft(const int v) override {
319 measurementsLeft = v;
320 }
321 int getProtocolSessionsLeft() override {
322 return measurementsLeft;
323 }
325 return completedMeasurementsTotal;
326 }
328 return completedMeasurementsSuccess;
329 }
330 int getDisconnectCount() override {
331 return disconnectCount;
332 }
333
334 void setDiscoveryPolicy(const DiscoveryPolicy v) override {
335 discoveryPolicy = v;
336 }
337 void setDisconnectDevice(const bool v) override {
338 do_disconnect = v;
339 }
340 void setRemoveDevice(const bool v) override {
341 do_remove_device = v;
342 }
343
344 private:
345 void resetLastProcessingStats() {
346 completedGATTCommands = 0;
347 notificationsReceived = 0;
348 indicationsReceived = 0;
349 }
350
351 void connectDiscoveredDevice(BTDeviceRef device) { // NOLINT(performance-unnecessary-value-param): Pass-by-value out-of-thread
352 fprintf_td(stderr, "****** Client Connecting Device: Start %s\n", device->toString().c_str());
353
354 resetLastProcessingStats();
355
356 const BTSecurityRegistry::Entry* sec = BTSecurityRegistry::getStartOf(device->getAddressAndType().address, device->getName());
357 if( nullptr != sec ) {
358 fprintf_td(stderr, "****** Client Connecting Device: Found SecurityDetail %s for %s\n", sec->toString().c_str(), device->toString().c_str());
359 } else {
360 fprintf_td(stderr, "****** Client Connecting Device: No SecurityDetail for %s\n", device->toString().c_str());
361 }
362 const BTSecurityLevel req_sec_level = nullptr != sec ? sec->getSecLevel() : BTSecurityLevel::UNSET;
363 HCIStatusCode res = device->uploadKeys(DBTConstants::CLIENT_KEY_PATH, req_sec_level, true /* verbose_ */);
364 fprintf_td(stderr, "****** Client Connecting Device: BTDevice::uploadKeys(...) result %s\n", to_string(res).c_str());
365 if( HCIStatusCode::SUCCESS != res ) {
366 if( nullptr != sec ) {
367 if( sec->isSecurityAutoEnabled() ) {
368 bool r = device->setConnSecurityAuto( sec->getSecurityAutoIOCap() );
369 fprintf_td(stderr, "****** Client Connecting Device: Using SecurityDetail.SEC AUTO %s, set OK %d\n", sec->toString().c_str(), r);
370 } else if( sec->isSecLevelOrIOCapSet() ) {
371 bool r = device->setConnSecurity( sec->getSecLevel(), sec->getIOCap() );
372 fprintf_td(stderr, "****** Client Connecting Device: Using SecurityDetail.Level+IOCap %s, set OK %d\n", sec->toString().c_str(), r);
373 } else {
374 bool r = device->setConnSecurityAuto( SMPIOCapability::KEYBOARD_ONLY );
375 fprintf_td(stderr, "****** Client Connecting Device: Setting SEC AUTO security detail w/ KEYBOARD_ONLY (%s) -> set OK %d\n", sec->toString().c_str(), r);
376 }
377 } else {
378 bool r = device->setConnSecurityAuto( SMPIOCapability::KEYBOARD_ONLY );
379 fprintf_td(stderr, "****** Client Connecting Device: Setting SEC AUTO security detail w/ KEYBOARD_ONLY -> set OK %d\n", r);
380 }
381 }
382 std::shared_ptr<const EInfoReport> eir = device->getEIR();
383 fprintf_td(stderr, "Client EIR-1 %s\n", device->getEIRInd()->toString().c_str());
384 fprintf_td(stderr, "Client EIR-2 %s\n", device->getEIRScanRsp()->toString().c_str());
385 fprintf_td(stderr, "Client EIR-+ %s\n", eir->toString().c_str());
386
387 uint16_t conn_interval_min = (uint16_t)8; // 10ms
388 uint16_t conn_interval_max = (uint16_t)12; // 15ms
389 const uint16_t conn_latency = (uint16_t)0;
390 if( eir->isSet(EIRDataType::CONN_IVAL) ) {
391 eir->getConnInterval(conn_interval_min, conn_interval_max);
392 }
393 const uint16_t supervision_timeout = (uint16_t) getHCIConnSupervisorTimeout(conn_latency, (int) ( conn_interval_max * 1.25 ) /* ms */);
394 res = device->connectLE(le_scan_interval, le_scan_window, conn_interval_min, conn_interval_max, conn_latency, supervision_timeout);
395 fprintf_td(stderr, "****** Client Connecting Device: End result %s of %s\n", to_string(res).c_str(), device->toString().c_str());
396 running_threads.count_down();
397 }
398
399 void processReadyDevice(BTDeviceRef device) { // NOLINT(performance-unnecessary-value-param): Pass-by-value out-of-thread
400 fprintf_td(stderr, "****** Client Processing Ready Device: Start %s\n", device->toString().c_str());
401
402 const uint64_t t1 = jau::getCurrentMilliseconds();
403
404 SMPKeyBin::createAndWrite(*device, DBTConstants::CLIENT_KEY_PATH, true /* verbose */);
405
406 const uint64_t t2 = jau::getCurrentMilliseconds();
407
408 bool success = false;
409
410 {
411 LE_PHYs resTx, resRx;
412 HCIStatusCode res = device->getConnectedLE_PHY(resTx, resRx);
413 fprintf_td(stderr, "****** Client Got Connected LE PHY: status %s: Tx %s, Rx %s\n",
414 to_string(res).c_str(), to_string(resTx).c_str(), to_string(resRx).c_str());
415 }
416
417 const uint64_t t3 = jau::getCurrentMilliseconds();
418
419 //
420 // GATT Service Processing
421 //
422 try {
423 jau::darray<BTGattServiceRef> primServices = device->getGattServices();
424 if( 0 == primServices.size() ) {
425 fprintf_td(stderr, "****** Client Processing Ready Device: getServices() failed %s\n", device->toString().c_str());
426 goto exit;
427 }
428
429 const uint64_t t5 = jau::getCurrentMilliseconds();
430 {
431 const uint64_t td00 = device->getLastDiscoveryTimestamp() - timestamp_t0; // adapter-init to discovered
432 const uint64_t td01 = t1 - timestamp_t0; // adapter-init to processing-start
433 const uint64_t td05 = t5 - timestamp_t0; // adapter-init -> gatt-complete
434 const uint64_t tdc1 = t1 - device->getLastDiscoveryTimestamp(); // discovered to processing-start
435 const uint64_t tdc5 = t5 - device->getLastDiscoveryTimestamp(); // discovered to gatt-complete
436 const uint64_t td12 = t2 - t1; // SMPKeyBin
437 const uint64_t td23 = t3 - t2; // LE_PHY
438 const uint64_t td13 = t3 - t1; // SMPKeyBin + LE_PHY
439 const uint64_t td35 = t5 - t3; // get-gatt-services
440 fprintf_td(stderr, "\n\n\n");
441 fprintf_td(stderr, "PERF: GATT primary-services completed\n"
442 "PERF: adapter-init to discovered %" PRIu64 " ms,\n"
443 "PERF: adapter-init to processing-start %" PRIu64 " ms,\n"
444 "PERF: adapter-init to gatt-complete %" PRIu64 " ms\n"
445 "PERF: discovered to processing-start %" PRIu64 " ms,\n"
446 "PERF: discovered to gatt-complete %" PRIu64 " ms,\n"
447 "PERF: SMPKeyBin + LE_PHY %" PRIu64 " ms (SMPKeyBin %" PRIu64 " ms, LE_PHY %" PRIu64 " ms),\n"
448 "PERF: get-gatt-services %" PRIu64 " ms,\n\n",
449 td00, td01, td05,
450 tdc1, tdc5,
451 td13, td12, td23, td35);
452 }
453
454 {
456 cmd.setVerbose(true);
457 const bool cmd_resolved = cmd.isResolved();
458 fprintf_td(stderr, "Command test: %s, resolved %d\n", cmd.toString().c_str(), cmd_resolved);
459 POctets cmd_data(1, lb_endian_t::little);
460 cmd_data.put_uint8_nc(0, cmd_arg);
461 const HCIStatusCode cmd_res = cmd.send(true /* prefNoAck */, cmd_data, 3_s);
462 if( HCIStatusCode::SUCCESS == cmd_res ) {
463 const jau::TROOctets& resp = cmd.getResponse();
464 if( 1 == resp.size() && resp.get_uint8_nc(0) == cmd_arg ) {
465 fprintf_td(stderr, "Client Success: %s -> %s (echo response)\n", cmd.toString().c_str(), resp.toString().c_str());
466 completedGATTCommands++;
467 } else {
468 fprintf_td(stderr, "Client Failure: %s -> %s (different response)\n", cmd.toString().c_str(), resp.toString().c_str());
469 }
470 } else {
471 fprintf_td(stderr, "Client Failure: %s -> %s\n", cmd.toString().c_str(), to_string(cmd_res).c_str());
472 }
473 // cmd.close(); // done via dtor
474 }
475
476 bool gattListenerError = false;
477 std::vector<BTGattCharListenerRef> gattListener;
478 int loop = 0;
479 do {
480 for(size_t i=0; i<primServices.size(); i++) {
481 BTGattService & primService = *primServices.at(i);
482 if( GATT_VERBOSE ) {
483 fprintf_td(stderr, " [%2.2d] Service UUID %s (%s)\n", i,
484 primService.type->toUUID128String().c_str(),
485 primService.type->getTypeSizeString().c_str());
486 fprintf_td(stderr, " [%2.2d] %s\n", i, primService.toString().c_str());
487 }
488 jau::darray<BTGattCharRef> & serviceCharacteristics = primService.characteristicList;
489 for(size_t j=0; j<serviceCharacteristics.size(); j++) {
490 BTGattCharRef & serviceChar = serviceCharacteristics.at(j);
491 if( GATT_VERBOSE ) {
492 fprintf_td(stderr, " [%2.2d.%2.2d] Characteristic: UUID %s (%s)\n", i, j,
493 serviceChar->value_type->toUUID128String().c_str(),
494 serviceChar->value_type->getTypeSizeString().c_str());
495 fprintf_td(stderr, " [%2.2d.%2.2d] %s\n", i, j, serviceChar->toString().c_str());
496 }
497 if( serviceChar->hasProperties(BTGattChar::PropertyBitVal::Read) ) {
498 POctets value(BTGattHandler::number(BTGattHandler::Defaults::MAX_ATT_MTU), 0, jau::lb_endian_t::little);
499 if( serviceChar->readValue(value) ) {
500 std::string sval = dfa_utf8_decode(value.get_ptr(), value.size());
501 if( GATT_VERBOSE ) {
502 fprintf_td(stderr, " [%2.2d.%2.2d] value: %s ('%s')\n", (int)i, (int)j, value.toString().c_str(), sval.c_str());
503 }
504 }
505 }
506 jau::darray<BTGattDescRef> & charDescList = serviceChar->descriptorList;
507 for(size_t k=0; k<charDescList.size(); k++) {
508 BTGattDesc & charDesc = *charDescList.at(k);
509 if( GATT_VERBOSE ) {
510 fprintf_td(stderr, " [%2.2d.%2.2d.%2.2d] Descriptor: UUID %s (%s)\n", i, j, k,
511 charDesc.type->toUUID128String().c_str(),
512 charDesc.type->getTypeSizeString().c_str());
513 fprintf_td(stderr, " [%2.2d.%2.2d.%2.2d] %s\n", i, j, k, charDesc.toString().c_str());
514 }
515 }
516 if( 0 == loop ) {
517 bool cccdEnableResult[2];
518 if( serviceChar->enableNotificationOrIndication( cccdEnableResult ) ) {
519 // ClientCharConfigDescriptor (CCD) is available
520 std::shared_ptr<BTGattCharListener> gattEventListener = std::make_shared<MyGATTEventListener>(*this);
521 bool clAdded = serviceChar->addCharListener( gattEventListener );
522 if( clAdded ) {
523 gattListener.push_back(gattEventListener);
524 } else {
525 gattListenerError = true;
526 fprintf_td(stderr, "Client Error: Failed to add GattListener: %s @ %s, gattListener %zu\n",
527 gattEventListener->toString().c_str(), serviceChar->toString().c_str(), gattListener.size());
528 }
529 if( GATT_VERBOSE ) {
530 fprintf_td(stderr, " [%2.2d.%2.2d] Characteristic-Listener: Notification(%d), Indication(%d): Added %d\n",
531 (int)i, (int)j, cccdEnableResult[0], cccdEnableResult[1], clAdded);
532 fprintf_td(stderr, "\n");
533 }
534 }
535 }
536 }
537 if( GATT_VERBOSE ) {
538 fprintf_td(stderr, "\n");
539 }
540 }
541 success = notificationsReceived >= 2 || indicationsReceived >= 2;
542 ++loop;
543 } while( !success && device->getConnected() && !gattListenerError );
544
545 success = success && completedGATTCommands >= 1;
546
547 if( gattListenerError ) {
548 success = false;
549 }
550 {
551 int i = 0;
552 for(const BTGattCharListenerRef& gcl : gattListener) {
553 if( !device->removeCharListener(gcl) ) {
554 fprintf_td(stderr, "Client Error: Failed to remove GattListener[%d/%zu]: %s @ %s\n",
555 i, gattListener.size(), gcl->toString().c_str(), device->toString().c_str());
556 success = false;
557 }
558 ++i;
559 }
560 }
561
562 if( device->getConnected() ) {
563 // Tell server we have successfully completed the test.
564 BTGattCmd cmd = BTGattCmd(*device, "FinalHandshake", DBTConstants::CommandUUID, DBTConstants::ResponseUUID, 256);
565 cmd.setVerbose(true);
566 const bool cmd_resolved = cmd.isResolved();
567 fprintf_td(stderr, "FinalCommand test: %s, resolved %d\n", cmd.toString().c_str(), cmd_resolved);
568 const size_t data_sz = DBTConstants::FailHandshakeCommandData.size();
569 POctets cmd_data(data_sz, lb_endian_t::little);
570 if( success ) {
571 cmd_data.put_bytes_nc(0, DBTConstants::SuccessHandshakeCommandData.data(), data_sz);
572 } else {
573 cmd_data.put_bytes_nc(0, DBTConstants::FailHandshakeCommandData.data(), data_sz);
574 }
575 const HCIStatusCode cmd_res = cmd.send(true /* prefNoAck */, cmd_data, 3_s);
576 if( HCIStatusCode::SUCCESS == cmd_res ) {
577 const jau::TROOctets& resp = cmd.getResponse();
578
579 if( cmd_data.size() == resp.size() &&
580 0 == ::memcmp(cmd_data.get_ptr(), resp.get_ptr(), resp.size()) )
581 {
582 fprintf_td(stderr, "Client Success: %s -> %s (echo response)\n", cmd.toString().c_str(), resp.toString().c_str());
583 } else {
584 fprintf_td(stderr, "Client Failure: %s -> %s (different response)\n", cmd.toString().c_str(), resp.toString().c_str());
585 }
586 } else {
587 fprintf_td(stderr, "Client Failure: %s -> %s\n", cmd.toString().c_str(), to_string(cmd_res).c_str());
588 }
589 // cmd.close(); // done via dtor
590 }
591 } catch ( std::exception & e ) {
592 fprintf_td(stderr, "****** Client Processing Ready Device: Exception.2 caught for %s: %s\n", device->toString().c_str(), e.what());
593 }
594
595 exit:
596 fprintf_td(stderr, "****** Client Processing Ready Device: End-1: Success %d on %s\n", success, device->toString().c_str());
597
598 if( DiscoveryPolicy::PAUSE_CONNECTED_UNTIL_DISCONNECTED == discoveryPolicy ) {
599 device->getAdapter().removeDevicePausingDiscovery(*device);
600 }
601
602 fprintf_td(stderr, "****** Client Processing Ready Device: End-2: Success %d on %s\n", success, device->toString().c_str());
603
604 device->removeAllCharListener();
605
606 if( do_disconnect ) {
607 if( do_remove_device ) {
608 device->remove();
609 } else {
610 device->disconnect();
611 }
612 }
613
614 completedMeasurementsTotal++;
615 if( success ) {
616 completedMeasurementsSuccess++;
617 if( 0 < measurementsLeft ) {
618 measurementsLeft--;
619 }
620 }
621 fprintf_td(stderr, "****** Client Processing Ready Device: Success %d; Measurements completed %d"
622 ", left %d; Received notitifications %d, indications %d"
623 "; Completed GATT commands %d: %s\n",
624 success, completedMeasurementsSuccess.load(),
625 measurementsLeft.load(), notificationsReceived.load(), indicationsReceived.load(),
626 completedGATTCommands.load(), device->getAddressAndType().toString().c_str());
627 running_threads.count_down();
628 }
629
630 void removeDevice(BTDeviceRef device) { // NOLINT(performance-unnecessary-value-param): Pass-by-value out-of-thread
631 fprintf_td(stderr, "****** Client Remove Device: removing: %s\n", device->getAddressAndType().toString().c_str());
632
633 if( do_remove_device ) {
634 device->remove();
635 }
636 running_threads.count_down();
637 }
638
639 static const bool le_scan_active = true; // default value
640 static const uint16_t le_scan_interval = 24; // default value
641 static const uint16_t le_scan_window = 24; // default value
642 static const uint8_t filter_policy = 0; // default value
643 static const bool filter_dup = true; // default value
644
645 public:
646
647 HCIStatusCode startDiscovery(const std::string& msg) override {
648 HCIStatusCode status = clientAdapter->startDiscovery( nullptr, discoveryPolicy, le_scan_active, le_scan_interval, le_scan_window, filter_policy, filter_dup );
649 fprintf_td(stderr, "****** Client Start discovery (%s) result: %s: %s\n", msg.c_str(), to_string(status).c_str(), clientAdapter->toString().c_str());
650 return status;
651 }
652
653 HCIStatusCode stopDiscovery(const std::string& msg) override {
654 HCIStatusCode status = clientAdapter->stopDiscovery();
655 fprintf_td(stderr, "****** Client Stop discovery (%s) result: %s\n", msg.c_str(), to_string(status).c_str());
656 return status;
657 }
658
659 void close(const std::string& msg) override {
660 fprintf_td(stderr, "****** Client Close: %s\n", msg.c_str());
661 clientAdapter->stopDiscovery();
662 REQUIRE( true == clientAdapter->removeStatusListener( myAdapterStatusListener ) );
663 fprintf_td(stderr, "****** Client close: running_threads %zu\n", running_threads.value());
664 running_threads.wait_for( 10_s );
665 }
666
667 bool initAdapter(BTAdapterRef adapter) override {
668 if( useAdapter != EUI48::ALL_DEVICE && useAdapter != adapter->getAddressAndType().address ) {
669 fprintf_td(stderr, "initClientAdapter: Adapter not selected: %s\n", adapter->toString().c_str());
670 return false;
671 }
672 adapterName = adapterName + "-" + adapter->getAddressAndType().address.toString();
673 {
674 auto it = std::remove( adapterName.begin(), adapterName.end(), ':');
675 adapterName.erase(it, adapterName.end());
676 }
677
678 // Initialize with defaults and power-on
679 if( !adapter->isInitialized() ) {
680 HCIStatusCode status = adapter->initialize( btMode, false );
681 if( HCIStatusCode::SUCCESS != status ) {
682 fprintf_td(stderr, "initClientAdapter: Adapter initialization failed: %s: %s\n",
683 to_string(status).c_str(), adapter->toString().c_str());
684 return false;
685 }
686 } else if( !adapter->setPowered( false ) ) {
687 fprintf_td(stderr, "initClientAdapter: Already initialized adapter power-off failed:: %s\n", adapter->toString().c_str());
688 return false;
689 }
690 // adapter is powered-off
691 fprintf_td(stderr, "initClientAdapter.1: %s\n", adapter->toString().c_str());
692 {
693 const LE_Features le_feats = adapter->getLEFeatures();
694 fprintf_td(stderr, "initClientAdapter: LE_Features %s\n", to_string(le_feats).c_str());
695 }
696
697 {
698 HCIStatusCode status = adapter->setName(adapterName, adapterShortName);
699 if( HCIStatusCode::SUCCESS == status ) {
700 fprintf_td(stderr, "initClientAdapter: setLocalName OK: %s\n", adapter->toString().c_str());
701 } else {
702 fprintf_td(stderr, "initClientAdapter: setLocalName failed: %s\n", adapter->toString().c_str());
703 return false;
704 }
705
706 if( !adapter->setPowered( true ) ) {
707 fprintf_td(stderr, "initClientAdapter: setPower.2 on failed: %s\n", adapter->toString().c_str());
708 return false;
709 }
710 }
711 // adapter is powered-on
712 fprintf_td(stderr, "initClientAdapter.2: %s\n", adapter->toString().c_str());
713
714 {
715 const LE_Features le_feats = adapter->getLEFeatures();
716 fprintf_td(stderr, "initClientAdapter: LE_Features %s\n", to_string(le_feats).c_str());
717 }
718 if( adapter->getBTMajorVersion() > 4 ) {
719 LE_PHYs Tx { LE_PHYs::LE_2M }, Rx { LE_PHYs::LE_2M };
720 HCIStatusCode res = adapter->setDefaultLE_PHY(Tx, Rx);
721 fprintf_td(stderr, "initClientAdapter: Set Default LE PHY: status %s: Tx %s, Rx %s\n",
722 to_string(res).c_str(), to_string(Tx).c_str(), to_string(Rx).c_str());
723 }
724 REQUIRE( true == adapter->addStatusListener( myAdapterStatusListener ) );
725
726 return true;
727 }
728};
729
730#endif /* DBT_CLIENT01_HPP_ */
This central BTRole::Master participant works with DBTServer00.
void setAdapter(BTAdapterRef clientAdapter_) override
Set the server adapter for this endpoint.
HCIStatusCode startDiscovery(const std::string &msg) override
~DBTClient01() override
void setProtocolSessionsLeft(const int v) override
void setDisconnectDevice(const bool v) override
Set disconnect after processing.
BTAdapterRef getAdapter() override
Return the adapter for this endpoint.
bool initAdapter(BTAdapterRef adapter) override
Initialize the given adapter for this endpoint.
DBTClient01(const std::string &adapterName_, const EUI48 useAdapter_, const BTMode btMode_, const bool do_disconnect_randomly_=false)
HCIStatusCode stopDiscovery(const std::string &msg) override
std::string getName() override
Return name of this endpoint, which becomes the adapter's name.
int getProtocolSessionsDoneSuccess() override
int getProtocolSessionsDoneTotal() override
void setRemoveDevice(const bool v) override
Set remove device when disconnecting.
int getProtocolSessionsLeft() override
int getDisconnectCount() override
void setDiscoveryPolicy(const DiscoveryPolicy v) override
Set DiscoveryPolicy.
void close(const std::string &msg) override
static const jau::uuid128_t ResponseUUID
static const jau::uuid128_t CommandUUID
static constexpr const char CLIENT_KEY_PATH[]
C++20 we could use constexpr std::string
static const std::vector< uint8_t > FailHandshakeCommandData
Fail handshake command data, where client is signaling unsuccessful completion of test to server.
static const std::vector< uint8_t > SuccessHandshakeCommandData
Success handshake command data, where client is signaling successful completion of test to server.
BTAdapter status listener for remote BTDevice discovery events: Added, updated and removed; as well a...
Definition: BTAdapter.hpp:127
BTAdapter represents one local Bluetooth Controller.
Definition: BTAdapter.hpp:324
std::string toString() const noexcept override
Definition: BTAdapter.hpp:1331
BTGattChar event listener for notification and indication events.
Definition: BTGattChar.hpp:450
Class maps a GATT command and optionally its asynchronous response to a synchronous atomic operation.
Definition: BTGattCmd.hpp:67
void setVerbose(const bool v) noexcept
Set verbosity for UUID resolution.
Definition: BTGattCmd.hpp:274
HCIStatusCode send(const bool prefNoAck, const jau::TROOctets &cmd_data, const jau::fraction_i64 &timeout) noexcept
Send the command to the remote BTDevice.
Definition: BTGattCmd.cpp:176
bool isResolved() noexcept
Query whether all UUIDs of this commands have been resolved.
Definition: BTGattCmd.cpp:167
const jau::TROOctets & getResponse() const noexcept
Returns the read-only response data object for configured commands with response notification or indi...
Definition: BTGattCmd.hpp:283
std::string toString() const noexcept
Definition: BTGattCmd.cpp:262
Representing a Gatt Characteristic Descriptor object from the GATTRole::Client perspective.
Definition: BTGattDesc.hpp:74
std::unique_ptr< const jau::uuid_t > type
Type of descriptor.
Definition: BTGattDesc.hpp:110
std::string toString() const noexcept override
Definition: BTGattDesc.cpp:99
Representing a Gatt Service object from the GATTRole::Client perspective.
std::unique_ptr< const jau::uuid_t > type
Service type UUID.
std::string toString() const noexcept override
jau::darray< BTGattCharRef > characteristicList
List of Characteristic Declarations as shared reference.
Persistent endian aware octet data, i.e.
Definition: octets.hpp:560
Transient read only and endian aware octet data, i.e.
Definition: octets.hpp:67
std::string toString() const noexcept
Definition: octets.hpp:288
constexpr nsize_t size() const noexcept
Returns the used memory size for read and write operations, may be zero.
Definition: octets.hpp:162
constexpr uint8_t get_uint8_nc(const nsize_t i) const noexcept
Definition: octets.hpp:168
constexpr uint8_t const * get_ptr() const noexcept
Definition: octets.hpp:272
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
Inspired by std::latch of C++20.
Definition: latch.hpp:50
size_t value() const noexcept
Return the current atomic internal counter.
Definition: latch.hpp:94
bool wait_for(const fraction_i64 &timeout_duration) const noexcept
Blocks the calling thread until the internal counter reaches 0 or the given timeout duration has expi...
Definition: latch.hpp:210
void count_down(const size_t n=1) noexcept
Atomically decrements the internal counter by n and notifies all blocked wait() threads if zero is re...
Definition: latch.hpp:108
void count_up(const size_t n=1) noexcept
Atomically increments the internal counter by n, i.e.
Definition: latch.hpp:136
std::shared_ptr< DBTClient01 > DBTClient01Ref
uint32_t dfa_utf8_decode(uint32_t &state, uint32_t &codep, const uint32_t byte_value)
@ little
Identifier for little endian, equivalent to endian::little.
std::string to_string(const alphabet &v) noexcept
Definition: base_codec.hpp:97
SMPPairingState
SMP Pairing Process state definition.
Definition: SMPTypes.hpp:107
Entry * getStartOf(const EUI48 &addr, const std::string &name) noexcept
Returns a matching Entry,.
BTMode
Bluetooth adapter operating mode.
Definition: BTTypes0.hpp:112
LE_Features
HCI Supported Commands.
Definition: BTTypes0.hpp:162
DiscoveryPolicy
Discovery policy defines the BTAdapter discovery mode after connecting a remote BTDevice:
Definition: BTAdapter.hpp:82
std::shared_ptr< BTDevice > BTDeviceRef
Definition: BTDevice.hpp:1347
std::shared_ptr< BTAdapter > BTAdapterRef
Definition: BTAdapter.hpp:1354
LE_PHYs
LE Transport PHY bit values.
Definition: BTTypes0.hpp:231
ScanType
Meta ScanType as derived from BTMode, with defined value mask consisting of BDAddressType bits.
Definition: BTTypes0.hpp:350
AdapterSetting
Adapter Setting Bits.
Definition: BTTypes1.hpp:144
constexpr bool isAdapterSettingBitSet(const AdapterSetting mask, const AdapterSetting bit) noexcept
Definition: BTTypes1.hpp:185
BTSecurityLevel
Bluetooth Security Level.
Definition: BTTypes0.hpp:267
PairingMode
Bluetooth secure pairing mode.
Definition: BTTypes0.hpp:317
constexpr uint16_t getHCIConnSupervisorTimeout(const uint16_t conn_latency, const uint16_t conn_interval_max_ms, const uint16_t min_result_ms=number(HCIConstInt::LE_CONN_MIN_TIMEOUT_MS), const uint16_t multiplier=10) noexcept
Defining the supervising timeout for LE connections to be a multiple of the maximum connection interv...
Definition: HCITypes.hpp:102
HCIStatusCode
BT Core Spec v5.2: Vol 1, Part F Controller Error Codes: 1.3 List of Error Codes.
Definition: HCITypes.hpp:138
EIRDataType
Bit mask of 'Extended Inquiry Response' (EIR) data fields, indicating a set of related data.
Definition: BTTypes0.hpp:838
std::shared_ptr< BTGattCharListener > BTGattCharListenerRef
Definition: BTGattChar.hpp:70
std::shared_ptr< BTGattChar > BTGattCharRef
Definition: BTGattChar.hpp:410
bool remove(const std::string &path, const traverse_options topts=traverse_options::none) noexcept
Remove the given path.
Definition: file_util.cpp:1173
constexpr uint32_t number(const iostate rhs) noexcept
Definition: byte_stream.hpp:72
std::string to_hexstring(value_type const &v) noexcept
Produce a lower-case hexadecimal string representation of the given pointer.
bool isWaitingForDevice(const EUI48 &address, const std::string &name, DeviceQueryMatchFunc m) noexcept
Returns true if the given address and/or name matches any of the BTDeviceRegistry::addToWaitForDevice...
__pack(...): Produces MSVC, clang and gcc compatible lead-in and -out macros.
Definition: backtrace.hpp:32
int fprintf_td(const uint64_t elapsed_ms, FILE *stream, const char *format,...) noexcept
Convenient fprintf() invocation, prepending the given elapsed_ms timestamp.
Definition: debug.cpp:270
uint64_t getCurrentMilliseconds() noexcept
Returns current monotonic time in milliseconds.
Definition: basic_types.cpp:64
bool sleep_for(const fraction_timespec &relative_time, const bool monotonic=true, const bool ignore_irq=true) noexcept
sleep_for causes the current thread to block until a specific amount of time has passed.
constexpr bool isSecLevelOrIOCapSet() const noexcept
constexpr const BTSecurityLevel & getSecLevel() const noexcept
constexpr int getPairingPasskey() const noexcept
constexpr bool getPairingNumericComparison() const noexcept
constexpr const SMPIOCapability & getSecurityAutoIOCap() const noexcept
constexpr const SMPIOCapability & getIOCap() const noexcept
constexpr bool isSecurityAutoEnabled() const noexcept
std::string toString() const noexcept
A packed 48 bit EUI-48 identifier, formerly known as MAC-48 or simply network device MAC address (Med...
Definition: eui48.hpp:324
CXX_ALWAYS_INLINE _Tp load() const noexcept
@ Read