Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
DBTServer01.java
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
25package trial.org.direct_bt;
26
27import java.nio.charset.StandardCharsets;
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.concurrent.atomic.AtomicInteger;
31
32import org.direct_bt.AdapterSettings;
33import org.direct_bt.AdapterStatusListener;
34import org.direct_bt.BTMode;
35import org.direct_bt.BTSecurityLevel;
36import org.direct_bt.BTAdapter;
37import org.direct_bt.BTDevice;
38import org.direct_bt.BTSecurityRegistry;
39import org.direct_bt.BTUtils;
40import org.direct_bt.DBGattChar;
41import org.direct_bt.DBGattDesc;
42import org.direct_bt.DBGattServer;
43import org.direct_bt.DBGattService;
44import org.direct_bt.DBGattValue;
45import org.direct_bt.DiscoveryPolicy;
46import org.direct_bt.EIRDataTypeSet;
47import org.direct_bt.EInfoReport;
48import org.direct_bt.GAPFlags;
49import org.direct_bt.GattCharPropertySet;
50import org.direct_bt.HCIStatusCode;
51import org.direct_bt.LE_Features;
52import org.direct_bt.LE_PHYs;
53import org.direct_bt.PairingMode;
54import org.direct_bt.SMPIOCapability;
55import org.direct_bt.SMPPairingState;
56import org.direct_bt.ScanType;
57import org.jau.io.PrintUtil;
58import org.jau.net.EUI48;
59import org.jau.sys.Clock;
60import org.jau.util.BasicTypes;
61import org.junit.Assert;
62
63/**
64 * This peripheral BTRole::Slave test participant works with DBTClient00.
65 */
66public class DBTServer01 implements DBTServerTest {
67 final boolean GATT_VERBOSE = false;
68 private final String adapterShortName = "TDev1Srv";
69
70 private final boolean SHOW_UPDATE_EVENTS = false;
71
72 private String adapterName = "TestDev1_Srv";
73 private EUI48 useAdapter = EUI48.ALL_DEVICE;
74 private BTMode btMode = BTMode.DUAL;
75 private boolean use_SC = true;
76 private BTSecurityLevel adapterSecurityLevel = BTSecurityLevel.UNSET;
77 private final MyAdapterStatusListener myAdapterStatusListener = new MyAdapterStatusListener();
78 private final MyGATTServerListener gattServerListener = new MyGATTServerListener();
79 private BTAdapter serverAdapter = null;
80 private final Object sync_lock = new Object();
81 private volatile BTDevice connectedDevice;
82
83 private final AtomicInteger disconnectCount = new AtomicInteger(0);
84 private final AtomicInteger servedProtocolSessionsTotal = new AtomicInteger(0);
85 private final AtomicInteger servedProtocolSessionsSuccess = new AtomicInteger(0);
86 private final AtomicInteger servingProtocolSessionsLeft = new AtomicInteger(1);
87
88 private final boolean do_disconnect_randomly;
89
90 public DBTServer01(final String adapterName, final EUI48 useAdapter, final BTMode btMode, final boolean use_SC, final BTSecurityLevel adapterSecurityLevel, final boolean do_disconnect_randomly) {
91 this.adapterName = adapterName;
92 this.useAdapter = useAdapter;
93 this.btMode = btMode;
94 this.use_SC = use_SC;
95 this.adapterSecurityLevel = adapterSecurityLevel;
96 this.do_disconnect_randomly = do_disconnect_randomly;
97
98 dbGattServer.addListener( gattServerListener );
99 }
100 public DBTServer01(final String adapterName, final EUI48 useAdapter, final BTMode btMode, final boolean use_SC, final BTSecurityLevel adapterSecurityLevel) {
101 this(adapterName, useAdapter, btMode, use_SC, adapterSecurityLevel, false /* do_disconnect_randomly */);
102 }
103
104 @Override
105 public String getName() { return adapterName; }
106
107 @Override
108 public BTSecurityLevel getSecurityLevel() { return adapterSecurityLevel; }
109
110 @Override
111 public void setAdapter(final BTAdapter serverAdapter) {
112 this.serverAdapter = serverAdapter;
113 }
114 @Override
115 public BTAdapter getAdapter() { return serverAdapter; }
116
117 @Override
118 public void setProtocolSessionsLeft(final int v) {
119 servingProtocolSessionsLeft.set(v);
120 }
121 @Override
123 return servingProtocolSessionsLeft.get();
124 }
125 @Override
127 return servedProtocolSessionsTotal.get();
128 }
129 @Override
131 return servedProtocolSessionsSuccess.get();
132 }
133
134 @Override
135 public int getDisconnectCount() {
136 return this.disconnectCount.get();
137 }
138
139 private final void setDevice(final BTDevice cd) {
140 synchronized( sync_lock ) {
141 connectedDevice = cd;
142 }
143 }
144
145 private final BTDevice getDevice() {
146 synchronized( sync_lock ) {
147 return connectedDevice;
148 }
149 }
150
151 private boolean matches(final BTDevice device) {
152 final BTDevice d = getDevice();
153 return null != d ? d.equals(device) : false;
154 }
155
156 static Thread executeOffThread(final Runnable runobj, final String threadName, final boolean detach) {
157 final Thread t = new Thread( runobj, threadName );
158 t.setDaemon( detach );
159 t.start();
160 return t;
161 }
162 static Thread executeOffThread(final Runnable runobj, final boolean detach) {
163 final Thread t = new Thread( runobj );
164 t.setDaemon( detach );
165 t.start();
166 return t;
167 }
168
169 // DBGattServerRef dbGattServer = std::make_shared<DBGattServer>(
170 private final DBGattServer dbGattServer = new DBGattServer(
171 /* services: */
172 Arrays.asList( // DBGattService
173 new DBGattService ( true /* primary */,
174 DBGattService.UUID16.GENERIC_ACCESS /* type_ */,
175 Arrays.asList( // DBGattChar
176 new DBGattChar( DBGattChar.UUID16.DEVICE_NAME /* value_type_ */,
177 new GattCharPropertySet(GattCharPropertySet.Type.Read),
178 new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
179 DBGattValue.make(adapterName, 128) /* value */ ),
180 new DBGattChar( DBGattChar.UUID16.APPEARANCE /* value_type_ */,
181 new GattCharPropertySet(GattCharPropertySet.Type.Read),
182 new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
183 DBGattValue.make((short)0) /* value */ )
184 ) ),
185 new DBGattService ( true /* primary */,
186 DBGattService.UUID16.DEVICE_INFORMATION /* type_ */,
187 Arrays.asList( // DBGattChar
188 new DBGattChar( DBGattChar.UUID16.MANUFACTURER_NAME_STRING /* value_type_ */,
189 new GattCharPropertySet(GattCharPropertySet.Type.Read),
190 new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
191 DBGattValue.make("Gothel Software") /* value */ ),
192 new DBGattChar( DBGattChar.UUID16.MODEL_NUMBER_STRING /* value_type_ */,
193 new GattCharPropertySet(GattCharPropertySet.Type.Read),
194 new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
195 DBGattValue.make("3.2.0-pre") /* value */ ),
196 new DBGattChar( DBGattChar.UUID16.SERIAL_NUMBER_STRING /* value_type_ */,
197 new GattCharPropertySet(GattCharPropertySet.Type.Read),
198 new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
199 DBGattValue.make("sn:0123456789") /* value */ ),
200 new DBGattChar( DBGattChar.UUID16.HARDWARE_REVISION_STRING /* value_type_ */,
201 new GattCharPropertySet(GattCharPropertySet.Type.Read),
202 new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
203 DBGattValue.make("hw:0123456789") /* value */ ),
204 new DBGattChar( DBGattChar.UUID16.FIRMWARE_REVISION_STRING /* value_type_ */,
205 new GattCharPropertySet(GattCharPropertySet.Type.Read),
206 new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
207 DBGattValue.make("fw:0123456789") /* value */ ),
208 new DBGattChar( DBGattChar.UUID16.SOFTWARE_REVISION_STRING /* value_type_ */,
209 new GattCharPropertySet(GattCharPropertySet.Type.Read),
210 new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
211 DBGattValue.make("sw:0123456789") /* value */ )
212 ) ),
213 new DBGattService ( true /* primary */,
214 DBTConstants.DataServiceUUID /* type_ */,
215 Arrays.asList( // DBGattChar
216 new DBGattChar( DBTConstants.StaticDataUUID /* value_type_ */,
217 new GattCharPropertySet(GattCharPropertySet.Type.Read),
218 Arrays.asList( // DBGattDesc
219 new DBGattDesc( DBGattDesc.UUID16.USER_DESC, DBGattValue.make("DATA_STATIC") )
220 ),
221 DBGattValue.make("Proprietary Static Data 0x00010203") /* value */ ),
222 new DBGattChar( DBTConstants.CommandUUID /* value_type_ */,
223 new GattCharPropertySet(GattCharPropertySet.Type.WriteNoAck).set(GattCharPropertySet.Type.WriteWithAck),
224 Arrays.asList( // DBGattDesc
225 new DBGattDesc( DBGattDesc.UUID16.USER_DESC, DBGattValue.make("COMMAND") )
226 ),
227 DBGattValue.make(128, 64) /* value */ ),
228 new DBGattChar( DBTConstants.ResponseUUID /* value_type_ */,
229 new GattCharPropertySet(GattCharPropertySet.Type.Notify).set(GattCharPropertySet.Type.Indicate),
230 Arrays.asList( // DBGattDesc
231 new DBGattDesc( DBGattDesc.UUID16.USER_DESC, DBGattValue.make("RESPONSE") ),
232 DBGattDesc.createClientCharConfig()
233 ),
234 DBGattValue.make((short)0) /* value */ ),
235 new DBGattChar( DBTConstants.PulseDataUUID /* value_type_ */,
236 new GattCharPropertySet(GattCharPropertySet.Type.Notify).set(GattCharPropertySet.Type.Indicate),
237 Arrays.asList( // DBGattDesc
238 new DBGattDesc( DBGattDesc.UUID16.USER_DESC, DBGattValue.make("DATA_PULSE") ),
239 DBGattDesc.createClientCharConfig()
240 ),
241 DBGattValue.make("Synthethic Sensor 01") /* value */ )
242 ) )
243 ) );
244
245
246 class MyAdapterStatusListener extends AdapterStatusListener {
247 @Override
248 public void adapterSettingsChanged(final BTAdapter adapter, final AdapterSettings oldmask,
249 final AdapterSettings newmask, final AdapterSettings changedmask, final long timestamp) {
250 final boolean initialSetting = oldmask.isEmpty();
251 if( initialSetting ) {
252 PrintUtil.println(System.err, "****** Server SETTINGS_INITIAL: "+oldmask+" -> "+newmask+", changed "+changedmask);
253 } else {
254 PrintUtil.println(System.err, "****** Server SETTINGS_CHANGED: "+oldmask+" -> "+newmask+", changed "+changedmask);
255
256 final boolean justPoweredOn = changedmask.isSet(AdapterSettings.SettingType.POWERED) &&
257 newmask.isSet(AdapterSettings.SettingType.POWERED);
258 if( justPoweredOn && serverAdapter.equals(adapter) ) {
259 executeOffThread( () -> { startAdvertising("powered_on"); },
260 "Server DBT-StartAdvertising-"+adapter.getAddressAndType(), true /* detach */);
261 }
262 }
263 PrintUtil.println(System.err, "Server Status Adapter:");
264 PrintUtil.println(System.err, adapter.toString());
265 }
266
267 @Override
268 public void discoveringChanged(final BTAdapter adapter, final ScanType currentMeta, final ScanType changedType, final boolean changedEnabled, final DiscoveryPolicy policy, final long timestamp) {
269 PrintUtil.println(System.err, "****** Server DISCOVERING: meta "+currentMeta+", changed["+changedType+", enabled "+changedEnabled+", policy "+policy+"] on "+adapter);
270 }
271
272 @Override
273 public boolean deviceFound(final BTDevice device, final long timestamp) {
274 PrintUtil.println(System.err, "****** Server FOUND__-1: NOP "+device.toString());
275 return false;
276 }
277
278 @Override
279 public void deviceUpdated(final BTDevice device, final EIRDataTypeSet updateMask, final long timestamp) {
280 if( SHOW_UPDATE_EVENTS ) {
281 PrintUtil.println(System.err, "****** Server UPDATED: "+updateMask+" of "+device);
282 }
283 }
284
285 @Override
286 public void deviceConnected(final BTDevice device, final boolean discovered, final long timestamp) {
287 PrintUtil.println(System.err, "****** Server CONNECTED (served "+disconnectCount.get()+", left "+servingProtocolSessionsLeft.get()+"): discovered "+discovered+": "+device+" on "+device.getAdapter());
288 final boolean available = null == getDevice();
289 if( available ) {
290 setDevice(device);
291 }
292 }
293
294 @Override
295 public void devicePairingState(final BTDevice device, final SMPPairingState state, final PairingMode mode, final long timestamp) {
296 PrintUtil.println(System.err, "****** Server PAIRING_STATE: state "+state+", mode "+mode+": "+device);
297 switch( state ) {
298 case NONE:
299 // next: deviceReady(..)
300 break;
301 case FAILED: {
302 // next: deviceReady() or deviceDisconnected(..)
303 } break;
304 case REQUESTED_BY_RESPONDER:
305 // next: FEATURE_EXCHANGE_STARTED
306 break;
307 case FEATURE_EXCHANGE_STARTED:
308 // next: FEATURE_EXCHANGE_COMPLETED
309 break;
310 case FEATURE_EXCHANGE_COMPLETED:
311 // next: PASSKEY_EXPECTED... or KEY_DISTRIBUTION
312 break;
313 case PASSKEY_EXPECTED: {
314 final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, "");
315 if( null != sec && sec.getPairingPasskey() != BTSecurityRegistry.NO_PASSKEY ) {
316 executeOffThread( () -> { device.setPairingPasskey( sec.getPairingPasskey() ); }, "DBT-SetPasskey-"+device.getAddressAndType(), true /* detach */);
317 } else {
318 executeOffThread( () -> { device.setPairingPasskey( 0 ); }, "DBT-SetPasskey-"+device.getAddressAndType(), true /* detach */);
319 // 3s disconnect: executeOffThread( () -> { device.setPairingPasskeyNegative(); }, "DBT-SetPasskeyNegative-"+device.getAddressAndType(), true /* detach */);
320 }
321 // next: KEY_DISTRIBUTION or FAILED
322 } break;
323 case NUMERIC_COMPARE_EXPECTED: {
324 final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, "");
325 if( null != sec ) {
326 executeOffThread( () -> { device.setPairingNumericComparison( sec.getPairingNumericComparison() ); }, "DBT-SetNumericComp-"+device.getAddressAndType(), true /* detach */);
327 } else {
328 executeOffThread( () -> { device.setPairingNumericComparison( false ); }, "DBT-SetNumericCompFalse-"+device.getAddressAndType(), true /* detach */);
329 }
330 // next: KEY_DISTRIBUTION or FAILED
331 } break;
332 case OOB_EXPECTED:
333 // FIXME: ABORT
334 break;
335 case KEY_DISTRIBUTION:
336 // next: COMPLETED or FAILED
337 break;
338 case COMPLETED:
339 // next: deviceReady(..)
340 break;
341 default: // nop
342 break;
343 }
344 }
345
346 @Override
347 public void deviceReady(final BTDevice device, final long timestamp) {
348 PrintUtil.println(System.err, "****** Server READY-1: NOP " + device.toString());
349 }
350
351 @Override
352 public void deviceDisconnected(final BTDevice device, final HCIStatusCode reason, final short handle, final long timestamp) {
353 PrintUtil.println(System.err, "****** Server DISCONNECTED (served "+(1+disconnectCount.get())+", left "+servingProtocolSessionsLeft.get()+"): Reason "+reason+", old handle 0x"+Integer.toHexString(handle)+": "+device+" on "+device.getAdapter());
354 final boolean match = matches(device);
355 if( match ) {
356 setDevice(null);
357 }
358 executeOffThread( () -> { processDisconnectedDevice(device); },
359 "Server DBT-Disconnected-"+device.getAdapter().getAddressAndType(), true /* detach */);
360 }
361
362 @Override
363 public String toString() {
364 return "Server AdapterStatusListener[user, per-adapter]";
365 }
366 };
367
368 class MyGATTServerListener extends DBGattServer.Listener implements AutoCloseable {
369 private final Thread pulseSenderThread;
370 private volatile boolean stopPulseSenderFlag = false;
371
372 private volatile short handlePulseDataNotify = 0;
373 private volatile short handlePulseDataIndicate = 0;
374 private volatile short handleResponseDataNotify = 0;
375 private volatile short handleResponseDataIndicate = 0;
376
377 private int usedMTU = 23; // BTGattHandler::number(BTGattHandler::Defaults::MIN_ATT_MTU);
378
379 private void clear() {
380 synchronized( sync_lock ) {
381 handlePulseDataNotify = 0;
382 handlePulseDataIndicate = 0;
383 handleResponseDataNotify = 0;
384 handleResponseDataIndicate = 0;
385
386 dbGattServer.resetGattClientCharConfig(DBTConstants.DataServiceUUID, DBTConstants.PulseDataUUID);
387 dbGattServer.resetGattClientCharConfig(DBTConstants.DataServiceUUID, DBTConstants.ResponseUUID);
388 }
389 }
390
391 private boolean shallStopPulseSender() {
392 synchronized( sync_lock ) {
393 return stopPulseSenderFlag;
394 }
395 }
396 private void pulseSender() {
397 {
398 final BTDevice connectedDevice_ = getDevice();
399 final String connectedDeviceStr = null != connectedDevice_ ? connectedDevice_.toString() : "n/a";
400 PrintUtil.fprintf_td(System.err, "****** Server GATT::PULSE Start %s\n", connectedDeviceStr);
401 }
402 while( !shallStopPulseSender() ) {
403 final BTDevice connectedDevice_ = getDevice();
404 if( null != connectedDevice_ && connectedDevice_.getConnected() ) {
405 if( 0 != handlePulseDataNotify || 0 != handlePulseDataIndicate ) {
406 final String data = String.format("Dynamic Data Example. Elapsed Milliseconds: %,9d", Clock.elapsedTimeMillis());
407 final byte[] v = data.getBytes(StandardCharsets.UTF_8);
408 if( 0 != handlePulseDataNotify ) {
409 if( GATT_VERBOSE ) {
410 PrintUtil.fprintf_td(System.err, "****** Server GATT::sendNotification: PULSE to %s\n", connectedDevice_.toString());
411 }
412 connectedDevice_.sendNotification(handlePulseDataNotify, v);
413 }
414 if( 0 != handlePulseDataIndicate ) {
415 if( GATT_VERBOSE ) {
416 PrintUtil.fprintf_td(System.err, "****** Server GATT::sendIndication: PULSE to %s\n", connectedDevice_.toString());
417 }
418 connectedDevice_.sendIndication(handlePulseDataIndicate, v);
419 }
420 }
421 }
422 if( !shallStopPulseSender() ) {
423 try {
424 Thread.sleep(100); // 100ms
425 } catch (final InterruptedException e) { }
426 }
427 }
428 {
429 final BTDevice connectedDevice_ = getDevice();
430 final String connectedDeviceStr = null != connectedDevice_ ? connectedDevice_.toString() : "n/a";
431 PrintUtil.fprintf_td(System.err, "****** Server GATT::PULSE End %s\n", connectedDeviceStr);
432 }
433 }
434
435 private void sendResponse(final byte[] data) {
436 final BTDevice connectedDevice_ = getDevice();
437 if( null != connectedDevice_ && connectedDevice_.getConnected() ) {
438 if( 0 != handleResponseDataNotify || 0 != handleResponseDataIndicate ) {
439 if( 0 != handleResponseDataNotify ) {
440 if( GATT_VERBOSE ) {
441 PrintUtil.fprintf_td(System.err, "****** Server GATT::sendNotification: %s to %s\n",
442 BasicTypes.bytesHexString(data, 0, data.length, true /* lsb */), connectedDevice_.toString());
443 }
444 connectedDevice_.sendNotification(handleResponseDataNotify, data);
445 }
446 if( 0 != handleResponseDataIndicate ) {
447 if( GATT_VERBOSE ) {
448 PrintUtil.fprintf_td(System.err, "****** Server GATT::sendIndication: %s to %s\n",
449 BasicTypes.bytesHexString(data, 0, data.length, true /* lsb */), connectedDevice_.toString());
450 }
451 connectedDevice_.sendIndication(handleResponseDataIndicate, data);
452 }
453 }
454 }
455 }
456
457 private void disconnectDeviceRandomly() {
458 // sleep range: 100 - 1500 ms
459 final int sleep_min = 100;
460 final int sleep_max = 1500;
461 final int sleep_dur = (int) ( Math.random() * ( sleep_max - sleep_min + 1 ) + sleep_min );
462 try {
463 Thread.sleep(sleep_dur); // wait a little (FIXME: Fast restart of advertising error)
464 } catch (final InterruptedException e) { }
465
466 final BTDevice connectedDevice_ = getDevice();
467 if( null != connectedDevice_ ) {
468 PrintUtil.fprintf_td(System.err, "****** Server i470 disconnectDevice(delayed %d ms): client %s\n", sleep_dur, connectedDevice_.toString());
469 connectedDevice_.disconnect();
470 } else {
471 PrintUtil.fprintf_td(System.err, "****** Server i470 disconnectDevice(delayed %d ms): client null\n", sleep_dur);
472 }
473 }
474
475 public MyGATTServerListener() {
476 pulseSenderThread = executeOffThread( () -> { pulseSender(); }, "GattServer-PulseSender", false /* detach */);
477 }
478
479 @Override
480 public void close() {
481 synchronized( sync_lock ) {
482 clear();
483 stopPulseSenderFlag = true;
484 }
485 try {
486 pulseSenderThread.join(1000);
487 } catch (final InterruptedException e) { }
488 super.close();
489 }
490
491 @Override
492 public void connected(final BTDevice device, final int initialMTU) {
493 final boolean match = matches(device);
494 PrintUtil.fprintf_td(System.err, "****** Server GATT::connected(match %b): initMTU %d, %s\n",
495 match, initialMTU, device.toString());
496 if( match ) {
497 synchronized( sync_lock ) {
498 usedMTU = initialMTU;
499 }
500 }
501 }
502
503 @Override
504 public void disconnected(final BTDevice device) {
505 final boolean match = matches(device);
506 PrintUtil.fprintf_td(System.err, "****** Server GATT::disconnected(match %b): %s\n", match, device.toString());
507 if( match ) {
508 clear();
509 }
510 }
511
512 @Override
513 public void mtuChanged(final BTDevice device, final int mtu) {
514 final boolean match = matches(device);
515 final int usedMTU_old = usedMTU;
516 if( match ) {
517 synchronized( sync_lock ) {
518 usedMTU = mtu;
519 }
520 }
521 PrintUtil.fprintf_td(System.err, "****** Server GATT::mtuChanged(match %b, served %d, left %d): %d -> %d, %s\n",
522 match, servedProtocolSessionsTotal.get(), servingProtocolSessionsLeft.get(),
523 match ? usedMTU_old : 0, mtu, device.toString());
524 if( do_disconnect_randomly ) {
525 executeOffThread( () -> { disconnectDeviceRandomly(); }, "GattServer-DisconnectDevice", true /* detach */);
526 }
527 }
528
529 @Override
530 public boolean readCharValue(final BTDevice device, final DBGattService s, final DBGattChar c) {
531 final boolean match = matches(device);
532 if( GATT_VERBOSE ) {
533 PrintUtil.fprintf_td(System.err, "****** Server GATT::readCharValue(match %b): to %s, from\n %s\n %s\n",
534 match, device.toString(), s.toString(), c.toString());
535 }
536 return match;
537 }
538
539 @Override
540 public boolean readDescValue(final BTDevice device, final DBGattService s, final DBGattChar c, final DBGattDesc d) {
541 final boolean match = matches(device);
542 if( GATT_VERBOSE ) {
543 PrintUtil.fprintf_td(System.err, "****** Server GATT::readDescValue(match %b): to %s, from\n %s\n %s\n %s\n",
544 match, device.toString(), s.toString(), c.toString(), d.toString());
545 }
546 return match;
547 }
548
549 @Override
550 public boolean writeCharValue(final BTDevice device, final DBGattService s, final DBGattChar c, final byte[] value, final int value_offset) {
551 final boolean match = matches(device);
552 if( GATT_VERBOSE ) {
553 final String value_s = BasicTypes.bytesHexString(value, 0, value.length, true /* lsbFirst */)+
554 " '"+BTUtils.decodeUTF8String(value, 0, value.length)+"'";
555 PrintUtil.fprintf_td(System.err, "****** Server GATT::writeCharValue(match %b): %s @ %d from %s, to\n %s\n %s\n",
556 match, value_s, value_offset,
557 device.toString(), s.toString(), c.toString());
558 }
559 return match;
560 }
561
562 @Override
563 public void writeCharValueDone(final BTDevice device, final DBGattService s, final DBGattChar c) {
564 final boolean match = matches(device);
565 final DBGattValue value = c.getValue();
566 final byte[] data = value.data();
567 boolean isFinalHandshake = false;
568 boolean isFinalHandshakeSuccess = false;
569
570 if( match &&
571 c.getValueType().equals( DBTConstants.CommandUUID ) &&
572 ( 0 != handleResponseDataNotify || 0 != handleResponseDataIndicate ) )
573 {
574 isFinalHandshakeSuccess = Arrays.equals(DBTConstants.SuccessHandshakeCommandData, data);
575 isFinalHandshake = isFinalHandshakeSuccess ||
576 Arrays.equals(DBTConstants.FailHandshakeCommandData, data);
577
578 if( isFinalHandshake ) {
579 if( isFinalHandshakeSuccess ) {
580 servedProtocolSessionsSuccess.addAndGet(1);
581 }
582 servedProtocolSessionsTotal.addAndGet(1); // we assume this to be server, i.e. connected, SMP done and GATT action ..
583 if( servingProtocolSessionsLeft.get() > 0 ) {
584 servingProtocolSessionsLeft.decrementAndGet();
585 }
586 }
587 executeOffThread( () -> { sendResponse( data ); }, true /* detach */);
588 }
589 if( GATT_VERBOSE || isFinalHandshake ) {
590 PrintUtil.fprintf_td(System.err, "****** Server GATT::writeCharValueDone(match %b, finalCmd %b, sessions [%d ok / %d total], left %d): From %s, to\n %s\n %s\n Char-Value: %s\n",
591 match, isFinalHandshake, servedProtocolSessionsSuccess.get(), servedProtocolSessionsTotal.get(), servingProtocolSessionsLeft.get(),
592 device.toString(), s.toString(), c.toString(), value.toString());
593 }
594 }
595
596 @Override
597 public boolean writeDescValue(final BTDevice device, final DBGattService s, final DBGattChar c, final DBGattDesc d,
598 final byte[] value, final int value_offset)
599 {
600 final boolean match = matches(device);
601 if( GATT_VERBOSE ) {
602 final String value_s = BasicTypes.bytesHexString(value, 0, value.length, true /* lsbFirst */)+
603 " '"+BTUtils.decodeUTF8String(value, 0, value.length)+"'";
604 PrintUtil.fprintf_td(System.err, "****** Server GATT::writeDescValue(match %b): %s @ %d from %s\n %s\n %s\n %s\n",
605 match, value_s, value_offset,
606 device.toString(), s.toString(), c.toString(), d.toString());
607 }
608 return match;
609 }
610
611 @Override
612 public void writeDescValueDone(final BTDevice device, final DBGattService s, final DBGattChar c, final DBGattDesc d) {
613 if( GATT_VERBOSE ) {
614 final boolean match = matches(device);
615 final DBGattValue value = d.getValue();
616 PrintUtil.fprintf_td(System.err, "****** Server GATT::writeDescValueDone(match %b): From %s\n %s\n %s\n %s\n Desc-Value: %s\n",
617 match, device.toString(), s.toString(), c.toString(), d.toString(), value.toString());
618 }
619 }
620
621 @Override
622 public void clientCharConfigChanged(final BTDevice device, final DBGattService s, final DBGattChar c, final DBGattDesc d,
623 final boolean notificationEnabled, final boolean indicationEnabled)
624 {
625 final boolean match = matches(device);
626 if( GATT_VERBOSE ) {
627 final DBGattValue value = d.getValue();
628 PrintUtil.fprintf_td(System.err, "****** Server GATT::clientCharConfigChanged(match %b): notify %b, indicate %b from %s\n %s\n %s\n %s\n Desc-Value: %s\n",
629 match, notificationEnabled, indicationEnabled,
630 device.toString(), s.toString(), c.toString(), d.toString(), value.toString());
631 }
632 if( match ) {
633 final String value_type = c.getValueType();
634 final short value_handle = c.getValueHandle();
635 if( value_type.equals( DBTConstants.PulseDataUUID ) ) {
636 synchronized( sync_lock ) {
637 handlePulseDataNotify = notificationEnabled ? value_handle : 0;
638 handlePulseDataIndicate = indicationEnabled ? value_handle : 0;
639 }
640 } else if( value_type.equals( DBTConstants.ResponseUUID ) ) {
641 synchronized( sync_lock ) {
642 handleResponseDataNotify = notificationEnabled ? value_handle : 0;
643 handleResponseDataIndicate = indicationEnabled ? value_handle : 0;
644 }
645 }
646 }
647 }
648 }
649 static final short adv_interval_min=(short)160; // x0.625 = 100ms
650 static final short adv_interval_max=(short)480; // x0.625 = 300ms
651 static final byte adv_type=(byte)0; // AD_PDU_Type::ADV_IND;
652 static final byte adv_chan_map=(byte)0x07;
653 static final byte filter_policy=(byte)0x00;
654
655 @Override
656 public void close(final String msg) {
657 PrintUtil.println(System.err, "****** Server Close.0: "+msg);
658 Assert.assertTrue( serverAdapter.removeStatusListener( myAdapterStatusListener ) );
659 {
660 stopAdvertising(msg);
661 final BTDevice connectedDevice_ = getDevice();
662 if( null != connectedDevice_ ) {
663 setDevice(null);
664 connectedDevice_.disconnect();
665 }
666 }
667 gattServerListener.close();
668 // dbGattServer.close(); // keep alive
669 stopAdvertising(msg); // try once more in case of already started AdapterStatusListener
670 PrintUtil.println(System.err, "****** Server Close.X: "+msg);
671 }
672
673 private HCIStatusCode stopAdvertising(final String msg) {
674 final HCIStatusCode status = serverAdapter.stopAdvertising();
675 PrintUtil.println(System.err, "****** Server Stop advertising ("+msg+") result: "+status+": "+serverAdapter.toString());
676 return status;
677 }
678
679 @Override
680 public HCIStatusCode startAdvertising(final String msg) {
681 final EInfoReport eir = new EInfoReport();
682 final EIRDataTypeSet adv_mask = new EIRDataTypeSet();
683 final EIRDataTypeSet scanrsp_mask = new EIRDataTypeSet();
684
687
688 scanrsp_mask.set(EIRDataTypeSet.DataType.NAME);
689 scanrsp_mask.set(EIRDataTypeSet.DataType.CONN_IVAL);
690
693
695 eir.setServicesComplete(false);
696
697 eir.setName(serverAdapter.getName());
698 eir.setConnInterval((short)8, (short)12); // 10ms - 15ms
699
700 final DBGattChar gattDevNameChar = dbGattServer.findGattChar(DBGattService.UUID16.GENERIC_ACCESS, DBGattChar.UUID16.DEVICE_NAME);
701 if( null != gattDevNameChar ) {
702 final byte[] aname_bytes = serverAdapter.getName().getBytes(StandardCharsets.UTF_8);
703 gattDevNameChar.setValue(aname_bytes, 0, aname_bytes.length, 0);
704 }
705
706 PrintUtil.println(System.err, "****** Server Start advertising ("+msg+"): EIR "+eir.toString());
707 PrintUtil.println(System.err, "****** Server Start advertising ("+msg+"): adv "+adv_mask.toString()+", scanrsp "+scanrsp_mask.toString());
708
709 final HCIStatusCode status = serverAdapter.startAdvertising(dbGattServer, eir, adv_mask, scanrsp_mask,
710 adv_interval_min, adv_interval_max,
711 adv_type, adv_chan_map, filter_policy);
712 PrintUtil.println(System.err, "****** Server Start advertising ("+msg+") result: "+status+": "+serverAdapter.toString());
713 if( GATT_VERBOSE ) {
714 PrintUtil.println(System.err, dbGattServer.toFullString());
715 }
716 return status;
717 }
718
719 private void processDisconnectedDevice(final BTDevice device) {
720 PrintUtil.println(System.err, "****** Server Disconnected Device (count "+(1+disconnectCount.get())+
721 ", served "+servedProtocolSessionsTotal.get()+", left "+servingProtocolSessionsLeft.get()+"): Start "+device.toString());
722
723 // already unpaired
724 stopAdvertising("device-disconnected");
725 device.remove();
726
727 disconnectCount.addAndGet(1);
728
729 try {
730 Thread.sleep(100); // wait a little (FIXME: Fast restart of advertising error)
731 } catch (final InterruptedException e) { }
732
733 if( servingProtocolSessionsLeft.get() > 0 ) {
734 startAdvertising("device-disconnected");
735 }
736
737 PrintUtil.println(System.err, "****** Server Disonnected Device: End "+device.toString());
738 }
739
740 @Override
741 public boolean initAdapter(final BTAdapter adapter) {
742 if( !useAdapter.equals(EUI48.ALL_DEVICE) && !useAdapter.equals(adapter.getAddressAndType().address) ) {
743 PrintUtil.fprintf_td(System.err, "initServerAdapter: Adapter not selected: %s\n", adapter.toString());
744 return false;
745 }
746 adapterName = adapterName + "-" + adapter.getAddressAndType().address.toString().replace(":", "");
747
748 if( !adapter.isInitialized() ) {
749 // Initialize with defaults and power-on
750 final HCIStatusCode status = adapter.initialize( btMode, false );
751 if( HCIStatusCode.SUCCESS != status ) {
752 PrintUtil.fprintf_td(System.err, "initServerAdapter: initialization failed: %s: %s\n",
753 status.toString(), adapter.toString());
754 return false;
755 }
756 } else if( !adapter.setPowered( false ) ) {
757 PrintUtil.fprintf_td(System.err, "initServerAdapter: setPower.1 off failed: %s\n", adapter.toString());
758 return false;
759 }
760 // adapter is powered-off
761 PrintUtil.println(System.err, "initServerAdapter.1: "+adapter.toString());
762
763 {
764 HCIStatusCode status = adapter.setName(adapterName, adapterShortName);
765 if( HCIStatusCode.SUCCESS == status ) {
766 PrintUtil.fprintf_td(System.err, "initServerAdapter: setLocalName OK: %s\n", adapter.toString());
767 } else {
768 PrintUtil.fprintf_td(System.err, "initServerAdapter: setLocalName failed: %s\n", adapter.toString());
769 return false;
770 }
771
772 status = adapter.setSecureConnections( use_SC );
773 if( HCIStatusCode.SUCCESS == status ) {
774 PrintUtil.fprintf_td(System.err, "initServerAdapter: setSecureConnections OK: %s\n", adapter.toString());
775 } else {
776 PrintUtil.fprintf_td(System.err, "initServerAdapter: setSecureConnections failed: %s\n", adapter.toString());
777 return false;
778 }
779
780 final short conn_min_interval = 8; // 10ms
781 final short conn_max_interval = 40; // 50ms
782 final short conn_latency = 0;
783 final short supervision_timeout = 50; // 500ms
784 status = adapter.setDefaultConnParam(conn_min_interval, conn_max_interval, conn_latency, supervision_timeout);
785 if( HCIStatusCode.SUCCESS == status ) {
786 PrintUtil.fprintf_td(System.err, "initServerAdapter: setDefaultConnParam OK: %s\n", adapter.toString());
787 } else if( HCIStatusCode.UNKNOWN_COMMAND == status ) {
788 PrintUtil.fprintf_td(System.err, "initServerAdapter: setDefaultConnParam UNKNOWN_COMMAND (ignored): %s\n", adapter.toString());
789 } else {
790 PrintUtil.fprintf_td(System.err, "initServerAdapter: setDefaultConnParam failed: %s, %s\n", status.toString(), adapter.toString());
791 return false;
792 }
793
794 if( !adapter.setPowered( true ) ) {
795 PrintUtil.fprintf_td(System.err, "initServerAdapter: setPower.2 on failed: %s\n", adapter.toString());
796 return false;
797 }
798 }
799 // adapter is powered-on
800 PrintUtil.println(System.err, "initServerAdapter.2: "+adapter.toString());
801
802 {
803 final LE_Features le_feats = adapter.getLEFeatures();
804 PrintUtil.fprintf_td(System.err, "initServerAdapter: LE_Features %s\n", le_feats.toString());
805 }
806 if( adapter.getBTMajorVersion() > 4 ) {
807 // BT5 specific
808 final LE_PHYs Tx = new LE_PHYs( LE_PHYs.PHY.LE_2M );
809 final LE_PHYs Rx = new LE_PHYs( LE_PHYs.PHY.LE_2M );
810 final HCIStatusCode res = adapter.setDefaultLE_PHY(Tx, Rx);
811 PrintUtil.fprintf_td(System.err, "initServerAdapter: Set Default LE PHY: status %s: Tx %s, Rx %s\n",
812 res.toString(), Tx.toString(), Rx.toString());
813 }
815
816 Assert.assertTrue( adapter.addStatusListener( myAdapterStatusListener ) );
817
818 adapter.setServerConnSecurity(adapterSecurityLevel, SMPIOCapability.UNSET);
819
820 return true;
821 }
822}
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2021 Gothel Software e.K.
Selected standard GATT characteristic numbers in UUID16 format as defined.
Definition: DBGattChar.java:56
Representing a Gatt Characteristic object from the GATT server perspective.
Definition: DBGattChar.java:46
native boolean setValue(final byte[] source, final int source_pos, final int source_len, final int dest_pos)
Set this characteristic's native value.
synchronized boolean addListener(final Listener l)
Selected standard GATT service service numbers in UUID16 format as defined.
static String GENERIC_ACCESS
This service contains generic information about the device.
Representing a Gatt Service object from the ::GATTRole::Server perspective.
Bit mask of 'Extended Inquiry Response' (EIR) data fields, indicating a set of related data.
void set(final DataType bit)
Collection of 'Extended Advertising Data' (EAD), 'Advertising Data' (AD) or 'Extended Inquiry Respons...
native void setConnInterval(final short min, final short max)
Set slave connection interval range.
native String toString(final boolean includeServices)
final void addFlag(final GAPFlags.Bit f)
native void setName(final String name)
native void setServicesComplete(final boolean v)
native void addService(final String uuid)
Bit mask of 'Extended Inquiry Response' (EIR) data fields, indicating a set of related data.
Definition: GAPFlags.java:35
LE Link Layer Feature Set (bitmask)
LE Transport PHY bit values (bitmask)
Definition: LE_PHYs.java:36
static final String SERVER_KEY_PATH
static final String DataServiceUUID
This peripheral BTRole::Slave test participant works with DBTClient00.
DBTServer01(final String adapterName, final EUI48 useAdapter, final BTMode btMode, final boolean use_SC, final BTSecurityLevel adapterSecurityLevel, final boolean do_disconnect_randomly)
BTAdapter getAdapter()
Return the adapter for this endpoint.
HCIStatusCode startAdvertising(final String msg)
DBTServer01(final String adapterName, final EUI48 useAdapter, final BTMode btMode, final boolean use_SC, final BTSecurityLevel adapterSecurityLevel)
String getName()
Return name of this endpoint, which becomes the adapter's name.
boolean initAdapter(final BTAdapter adapter)
Initialize the given adapter for this endpoint.
void setProtocolSessionsLeft(final int v)
void close(final String msg)
void setAdapter(final BTAdapter serverAdapter)
Set the server adapter for this endpoint.
BTSecurityLevel getSecurityLevel()
Bluetooth adapter operating mode.
Definition: BTMode.java:34
DUAL
Dual Bluetooth mode, i.e.
Definition: BTMode.java:38
Bluetooth Security Level.
UNSET
Security Level not set, value 0.
Each enum represents a 'Extended Inquiry Response' (EIR) data field type bit.
Each enum represents a 'Extended Inquiry Response' (EIR) data field type bit.
Definition: GAPFlags.java:45
BT Core Spec v5.2: Vol 1, Part F Controller Error Codes: 1.3 List of Error Codes.
Each enum represents a 'LE Transport PHY' bit value.
Definition: LE_PHYs.java:43
SMP IO Capability value.
UNSET
Denoting unset value, i.e.
BTAdapter represents one local Bluetooth Controller.
Definition: BTAdapter.java:48
boolean isInitialized()
Returns true, if initialize(BTMode) has already been called for this adapter, otherwise false.
LE_Features getLEFeatures()
Return LE_Features for this controller.
boolean setPowered(final boolean power_on)
Sets the power state the adapter.
boolean equals(final Object obj)
If both types are of BTAdapter, it compares their BDAddressAndType, see getAddressAndType().
HCIStatusCode startAdvertising(final DBGattServer gattServerData, final EInfoReport eir, final EIRDataTypeSet adv_mask, final EIRDataTypeSet scanrsp_mask, final short adv_interval_min, final short adv_interval_max, final byte adv_type, final byte adv_chan_map, final byte filter_policy)
Starts advertising.
HCIStatusCode setSecureConnections(final boolean enable)
Enable or disable Secure Connections (SC) of the adapter.
String getName()
Returns the name.
void setSMPKeyPath(final String path)
Set the adapter's persistent storage directory for SMPKeyBin files.
void setServerConnSecurity(final BTSecurityLevel sec_level, final SMPIOCapability io_cap)
Sets the given ::BTSecurityLevel and ::SMPIOCapability for connecting device when in server (peripher...
boolean addStatusListener(final AdapterStatusListener listener)
Add the given AdapterStatusListener to the list if not already present.
HCIStatusCode setDefaultConnParam(final short conn_interval_min, final short conn_interval_max, final short conn_latency, final short supervision_timeout)
Set default connection parameter of incoming connections for this adapter when in server mode,...
HCIStatusCode setDefaultLE_PHY(final LE_PHYs Tx, final LE_PHYs Rx)
Sets default preference of LE_PHYs.
BDAddressAndType getAddressAndType()
Returns the adapter's public BDAddressAndType.
HCIStatusCode setName(String name, String short_name)
Sets the name and short-name.
boolean removeStatusListener(final AdapterStatusListener l)
Remove the given AdapterStatusListener from the list.
HCIStatusCode stopAdvertising()
Ends advertising.
int getBTMajorVersion()
Returns the Bluetooth major version of this adapter.
HCIStatusCode initialize(final BTMode btMode, boolean powerOn)
Initialize the adapter with default values, including power-on.
BTDevice represents one remote Bluetooth device.
Definition: BTDevice.java:47
boolean remove()
Remove this device from the system (like an unpair).
HCIStatusCode disconnect()
jau.direct_bt: Disconnect the LE or BREDR peer's GATT and HCI connection.