summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2022-02-07 02:09:07 +0100
committerSven Gothel <[email protected]>2022-02-07 02:09:07 +0100
commit42d1296cb7b269d164574ed1de35d04109c87e60 (patch)
tree4a31cf97b1f0542dd04b1cb239d474cc6ad26f09
parent24ef91d8c6290dfe0a5cc2caa1e70e7969412f60 (diff)
Added online unit testing using actual BT adapter, testing client with server functionality
Building with enabled *trial* and *testing* , i.e. live testing with 2 Bluetooth adapter is provided via the *cmake* build argument `-DBUILD_TRIAL=ON`, see above. The *trial* tests utilize one or more actual Bluetooth adapter, hence using the *capsh* launch for the required permissions as described above. Therefor, *sudo* will be called and a user interaction to enter the *sudo* password may occur. The *trial* tests cover *Direct-BT*'s Bluetooth functionality, having its *master/client* and *slave/server peripheral* facilities communicating via actual adapter, supporting regression testing of the API, its implementation and adapter.
-rw-r--r--.classpath1
-rw-r--r--CMakeLists.txt12
-rw-r--r--README.md40
-rw-r--r--scripts/build.sh5
l---------scripts/run-dbt_repeater00.sh1
-rw-r--r--trial/java/CMakeLists.txt39
-rw-r--r--trial/java/manifest.txt.in25
-rw-r--r--trial/java/trial/org/direct_bt/BaseDBTClientServer.java161
-rw-r--r--trial/java/trial/org/direct_bt/DBTClient00.java626
-rw-r--r--trial/java/trial/org/direct_bt/DBTConstants.java31
-rw-r--r--trial/java/trial/org/direct_bt/DBTServer00.java742
-rw-r--r--trial/java/trial/org/direct_bt/DBTUtils.java177
-rw-r--r--trial/java/trial/org/direct_bt/TestDBTClientServer00.java217
-rw-r--r--trial/java/trial/org/direct_bt/TestDBTClientServer10.java259
-rw-r--r--trial/java/trial/org/direct_bt/VersionInfo.java14
15 files changed, 2346 insertions, 4 deletions
diff --git a/.classpath b/.classpath
index b43b560d..3991550e 100644
--- a/.classpath
+++ b/.classpath
@@ -7,6 +7,7 @@
<classpathentry kind="src" path="jaulib/java"/>
<classpathentry kind="src" path="jaulib/java_base"/>
<classpathentry kind="src" path="jaulib/java_net"/>
+ <classpathentry kind="src" path="trial/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
<attributes>
<attribute name="module" value="true"/>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0535b6dd..61df04c9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,6 +12,10 @@ find_package (Threads REQUIRED)
# set(CMAKE_MESSAGE_LOG_LEVEL DEBUG)
+if (BUILD_TRIAL)
+ set (BUILD_TESTING ON)
+endif(BUILD_TRIAL)
+
include(jaulib/JaulibSetup.cmake)
JaulibSetup()
@@ -90,4 +94,12 @@ if (BUILD_TESTING)
endif(BUILDJAVA)
endif(BUILD_TESTING)
+if (BUILD_TRIAL)
+ enable_testing ()
+ if (BUILDJAVA)
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/trial/java/manifest.txt.in ${CMAKE_CURRENT_BINARY_DIR}/trial/java/manifest.txt)
+ add_subdirectory (trial/java)
+ endif(BUILDJAVA)
+endif(BUILD_TRIAL)
+
diff --git a/README.md b/README.md
index 5b17e48b..c18d00c6 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,11 @@ Some elaboration on the implementation details
> exposing BTSecurityLevel and SMPIOCapability setup per connection
> and providing *automatic security mode negotiation*.
>
+> Provoding *dbt_repeater00*, a *BT repeater* forwading between *GATT-Server* and *-Client*,
+> allowing protocol analysis between an external client and server.
+>
+> *Online* unit testing with two BT adapter is provided.
+>
> BREDR support is planned and prepared for.
>
@@ -210,7 +215,7 @@ A guide for getting started with *Direct-BT* on C++ and Java may follow up.
are available, demonstrating the event driven and multithreading workflow:
- [dbt_scanner10.cpp](https://jausoft.com/projects/direct_bt/build/documentation/cpp/html/dbt_scanner10_8cpp-example.html) *Master* with *Gatt-Client*
- [dbt_peripheral00.cpp](https://jausoft.com/projects/direct_bt/build/documentation/cpp/html/dbt_peripheral00_8cpp-example.html) *Peripheral* with *GATT-Server*
-- [dbt_repeater00.cpp](https://jausoft.com/projects/direct_bt/build/documentation/cpp/html/dbt_repeater00_8cpp-example.html) *Repeater* forwading between *GATT-Server* and *-Client*
+- [dbt_repeater00.cpp](https://jausoft.com/projects/direct_bt/build/documentation/cpp/html/dbt_repeater00_8cpp-example.html) *BT Repeater* forwading between *GATT-Server* and *-Client*, allowing protocol analysis between an external client and server.
*Direct-BT* [Java examples](https://jausoft.com/projects/direct_bt/build/documentation/java/html/examples.html)
@@ -289,6 +294,18 @@ Building debug build:
-DDEBUG=ON
~~~~~~~~~~~~~
+Building with enabled *testing*, i.e. offline testing without any potential interaction as user:
+
+~~~~~~~~~~~~~
+-DBUILD_TESTING=ON
+~~~~~~~~~~~~~
+
+Building with enabled *trial* and *testing* , i.e. live testing with 2 Bluetooth adapter and potential sudo interaction:
+
+~~~~~~~~~~~~~
+-DBUILD_TRIAL=ON
+~~~~~~~~~~~~~
+
Disable stripping native lib even in non debug build:
~~~~~~~~~~~~~
@@ -340,6 +357,23 @@ To build documentation run:
make doc
~~~~~~~~~~~~~
+### Unit Testing
+
+Building with enabled *testing*, i.e. offline testing without any potential interaction as user
+is provided via the *cmake* build argument `-DBUILD_TESTING=ON`, see above.
+
+Building with enabled *trial* and *testing* , i.e. live testing with 2 Bluetooth adapter
+is provided via the *cmake* build argument `-DBUILD_TRIAL=ON`, see above.
+
+The *trial* tests utilize one or more actual Bluetooth adapter,
+hence using the *capsh* launch for the required permissions as described above.
+Therefor, *sudo* will be called and a user interaction to enter the *sudo* password may occur.
+
+The *trial* tests cover *Direct-BT*'s Bluetooth functionality,
+having its *master/client* and *slave/server peripheral* facilities communicating via actual adapter,
+supporting regression testing of the API, its implementation and adapter.
+
+
### Cross Build
Also provided is a [cross-build script](https://jausoft.com/cgit/direct_bt.git/tree/scripts/build-cross.sh)
using chroot into a target system using [QEMU User space emulation](https://qemu-project.gitlab.io/qemu/user/main.html)
@@ -449,7 +483,9 @@ from the year 2016.
* TODO
**2.6.0**
-* TODO
+* Added *online* unit testing using actual BT adapter, testing *client* with *server* functionality.
+* BTAdapter/HCIHandler: Fix advertising state: Active until either disabled or connected.
+* DBTAdapter: Fix removeAllStatusListener(): Re-add internal listener to maintain functionality.
* GATT Server enhancements, incl new DBGattServer::Mode and `dbt_repeater00` implementation.
* BTDevice::getGattServices(): MTU and remote GATT Services shall be processed from here at request only, moved from BTDevice::connectGATT().
* jaulib v0.7.11 fixes
diff --git a/scripts/build.sh b/scripts/build.sh
index 57479f83..78c90df0 100644
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -36,8 +36,9 @@ buildit() {
cd build-$archabi
# CLANG_ARGS="-DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++"
- cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILDEXAMPLES=ON -DBUILD_TESTING=ON ..
- # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILDEXAMPLES=ON -DBUILD_TESTING=ON -DDEBUG=ON ..
+ cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILDEXAMPLES=ON -DBUILD_TRIAL=ON ..
+ # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILDEXAMPLES=ON -DBUILD_TESTING=ON ..
+ # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILDEXAMPLES=ON -DBUILD_TRIAL=ON -DDEBUG=ON ..
# cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILDEXAMPLES=ON -DBUILD_TESTING=ON -DUSE_STRIP=OFF ..
# cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILDEXAMPLES=ON -DBUILD_TESTING=ON -DUSE_STRIP=ON -DJAVAC_DEBUG_ARGS="none" ..
diff --git a/scripts/run-dbt_repeater00.sh b/scripts/run-dbt_repeater00.sh
new file mode 120000
index 00000000..68360a74
--- /dev/null
+++ b/scripts/run-dbt_repeater00.sh
@@ -0,0 +1 @@
+run-native-example.sh \ No newline at end of file
diff --git a/trial/java/CMakeLists.txt b/trial/java/CMakeLists.txt
new file mode 100644
index 00000000..85b1ab4f
--- /dev/null
+++ b/trial/java/CMakeLists.txt
@@ -0,0 +1,39 @@
+# java/CMakeLists.txt
+
+find_jar(JUNIT_JAR
+ NAMES junit4 junit
+ PATHS "/usr/share/java")
+
+set(direct_bt_trial_jar_file ${CMAKE_CURRENT_BINARY_DIR}/direct_bt-trial.jar CACHE FILEPATH "direct_bt trial jar file" FORCE)
+
+file(GLOB_RECURSE TEST_JAVA_SOURCES "*.java")
+
+file(GLOB_RECURSE TEST_JAVA_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "Test*.java")
+
+add_jar(direct_bt_trial
+ ${TEST_JAVA_SOURCES}
+ INCLUDE_JARS ${jaulib_fat_jar_file} ${direct_bt_fat_jar_file} ${jaulib_test_jar_file} ${JUNIT_JAR}
+ MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/manifest.txt
+ OUTPUT_NAME direct_bt-trial
+)
+add_dependencies(direct_bt_trial direct_bt_fat_jar jaulib_test)
+
+install (FILES ${direct_bt_trial_jar_file} DESTINATION ${CMAKE_INSTALL_LIBDIR}/../lib/java)
+
+string( REPLACE ".java" "" TEST_JAVA_FILES2 "${TEST_JAVA_FILES}" )
+string( REPLACE "/" "." BASENAMES_IDIOMATIC_EXAMPLES "${TEST_JAVA_FILES2}" )
+set( TARGETS_IDIOMATIC_EXAMPLES ${BASENAMES_IDIOMATIC_EXAMPLES} )
+
+#foreach(name ${TARGETS_IDIOMATIC_EXAMPLES})
+# add_test (NAME ${name} COMMAND ${JAVA_RUNTIME}
+# -cp ${JUNIT_JAR}:${direct_bt_fat_jar_file}:${jaulib_trial_jar_file}:${direct_bt_trial_jar_file}
+# org.junit.runner.JUnitCore ${name})
+#endforeach()
+
+foreach(name ${TARGETS_IDIOMATIC_EXAMPLES})
+ add_test (NAME ${name} COMMAND sudo /sbin/capsh --caps=cap_net_raw,cap_net_admin+eip\ cap_setpcap,cap_setuid,cap_setgid+ep
+ --keep=1 --user=sven --addamb=cap_net_raw,cap_net_admin+eip
+ -- -c "ulimit -c unlimited; $EXE_WRAPPER ${JAVA_RUNTIME} -cp ${JUNIT_JAR}:${direct_bt_fat_jar_file}:${jaulib_test_jar_file}:${direct_bt_trial_jar_file} org.junit.runner.JUnitCore ${name}")
+endforeach()
+
+
diff --git a/trial/java/manifest.txt.in b/trial/java/manifest.txt.in
new file mode 100644
index 00000000..1810a199
--- /dev/null
+++ b/trial/java/manifest.txt.in
@@ -0,0 +1,25 @@
+Manifest-Version: 1.0
+Bundle-Date: @BUILD_TSTAMP@
+Bundle-ManifestVersion: 2
+Bundle-Name: org.direct_bt.trial
+Bundle-SymbolicName: org.direct_bt.trial
+Bundle-Version: @VERSION_SHORT@
+Export-Package: org.direct_bt.trial
+Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.9))"
+Package-Title: org.direct_bt.trial
+Package-Vendor: Gothel Software
+Package-Version: @VERSION_SHORT@
+Specification-Title: Direct-BT Unit Tests
+Specification-Vendor: Gothel Software
+Specification-Version: @VERSION_API@
+Implementation-Title: Direct-BT Unit Tests
+Implementation-Vendor: Gothel Software
+Implementation-Version: @VERSION@
+Implementation-Commit: @VERSION_SHA1@
+Implementation-URL: http://www.jausoft.com/
+Extension-Name: org.direct_bt.trial
+Trusted-Library: true
+Permissions: all-permissions
+Application-Library-Allowable-Codebase: *
+Class-Path: direct_bt-fat.jar jaulib-test.jar
+Main-Class: trial.org.direct_bt.VersionInfo
diff --git a/trial/java/trial/org/direct_bt/BaseDBTClientServer.java b/trial/java/trial/org/direct_bt/BaseDBTClientServer.java
new file mode 100644
index 00000000..8739c038
--- /dev/null
+++ b/trial/java/trial/org/direct_bt/BaseDBTClientServer.java
@@ -0,0 +1,161 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package trial.org.direct_bt;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+import org.direct_bt.BTAdapter;
+import org.direct_bt.BTDeviceRegistry;
+import org.direct_bt.BTException;
+import org.direct_bt.BTFactory;
+import org.direct_bt.BTManager;
+import org.direct_bt.BTSecurityRegistry;
+import org.direct_bt.BTUtils;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+import jau.test.junit.util.SingletonJunitCase;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public abstract class BaseDBTClientServer extends SingletonJunitCase {
+ static boolean DEBUG = false;
+
+ @BeforeClass
+ public static final void setupAll() {
+ BTFactory.initDirectBTLibrary();
+
+ final Class<?> ThisClazz = MethodHandles.lookup().lookupClass();
+ BTUtils.println(System.err, "++++ Test "+ThisClazz.getSimpleName()+".setupAll()");
+
+ if( DEBUG ) {
+ System.setProperty("direct_bt.debug", "true");
+ System.setProperty("org.direct_bt.debug", "true");
+ }
+ Assert.assertTrue( DBTUtils.rmKeyFolder() );
+ Assert.assertTrue( DBTUtils.mkdirKeyFolder() );
+ }
+
+ /**
+ * Ensure
+ * - all adapter are powered off
+ * - manager being shutdown
+ */
+ @AfterClass
+ public static final void cleanupAll() {
+ final Class<?> ThisClazz = MethodHandles.lookup().lookupClass();
+ BTUtils.println(System.err, "++++ Test "+ThisClazz.getSimpleName()+".cleanupAll()");
+
+ BTManager manager = null;
+ try {
+ manager = BTFactory.getDirectBTManager();
+ } catch (BTException | NoSuchMethodException | SecurityException
+ | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | ClassNotFoundException e) {
+ e.printStackTrace();
+ BTUtils.println(System.err, "Unable to instantiate DirectBT BluetoothManager: "+e.getMessage());
+ e.printStackTrace();
+ }
+ if( null != manager ) {
+ final List<BTAdapter> adapters = manager.getAdapters();
+ for(final BTAdapter a : adapters) {
+ a.setPowered(false);
+ }
+ // All implicit via destructor or shutdown hook!
+ manager.shutdown(); /* implies: adapter.close(); */
+ }
+ }
+
+ /**
+ * Ensure
+ * - all adapter are powered off
+ */
+ @Before
+ public final void setupTest() {
+ final Class<?> ThisClazz = MethodHandles.lookup().lookupClass();
+ BTUtils.println(System.err, "++++ Test "+ThisClazz.getSimpleName()+".setupTest()");
+
+ BTManager manager = null;
+ try {
+ manager = BTFactory.getDirectBTManager();
+ } catch (BTException | NoSuchMethodException | SecurityException
+ | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | ClassNotFoundException e) {
+ e.printStackTrace();
+ BTUtils.println(System.err, "Unable to instantiate DirectBT BluetoothManager: "+e.getMessage());
+ e.printStackTrace();
+ }
+ if( null != manager ) {
+ final List<BTAdapter> adapters = manager.getAdapters();
+ for(final BTAdapter a : adapters) {
+ Assert.assertTrue( a.setPowered(false) );
+ }
+ }
+ }
+
+ /**
+ * Ensure
+ * - remove all status listener from all adapter
+ * - all adapter are powered off
+ * - clear BTDeviceRegistry
+ * - clear BTSecurityRegistry
+ */
+ @After
+ public final void cleanupTest() {
+ final Class<?> ThisClazz = MethodHandles.lookup().lookupClass();
+ BTUtils.println(System.err, "++++ Test "+ThisClazz.getSimpleName()+".cleanupTest()");
+
+ BTManager manager = null;
+ try {
+ manager = BTFactory.getDirectBTManager();
+ } catch (BTException | NoSuchMethodException | SecurityException
+ | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | ClassNotFoundException e) {
+ e.printStackTrace();
+ BTUtils.println(System.err, "Unable to instantiate DirectBT BluetoothManager: "+e.getMessage());
+ e.printStackTrace();
+ }
+ if( null != manager ) {
+ final List<BTAdapter> adapters = manager.getAdapters();
+ for(final BTAdapter a : adapters) {
+ {
+ final int r = a.removeAllStatusListener();
+ Assert.assertTrue("Not >= 0 removed listener, but "+r, 0 <= r );
+ }
+ Assert.assertTrue( a.setPowered(false) );
+ }
+ }
+ BTDeviceRegistry.clearWaitForDevices();
+ BTDeviceRegistry.clearProcessedDevices();
+ BTDeviceRegistry.clearProcessingDevices();
+ BTSecurityRegistry.clear();
+ }
+}
diff --git a/trial/java/trial/org/direct_bt/DBTClient00.java b/trial/java/trial/org/direct_bt/DBTClient00.java
new file mode 100644
index 00000000..19721e8b
--- /dev/null
+++ b/trial/java/trial/org/direct_bt/DBTClient00.java
@@ -0,0 +1,626 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package trial.org.direct_bt;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.direct_bt.AdapterSettings;
+import org.direct_bt.AdapterStatusListener;
+import org.direct_bt.BTMode;
+import org.direct_bt.BTSecurityLevel;
+import org.direct_bt.BTAdapter;
+import org.direct_bt.BTDevice;
+import org.direct_bt.BTDeviceRegistry;
+import org.direct_bt.BTGattChar;
+import org.direct_bt.BTGattCmd;
+import org.direct_bt.BTGattDesc;
+import org.direct_bt.BTGattService;
+import org.direct_bt.BTSecurityRegistry;
+import org.direct_bt.BTUtils;
+import org.direct_bt.DiscoveryPolicy;
+import org.direct_bt.EIRDataTypeSet;
+import org.direct_bt.EInfoReport;
+import org.direct_bt.GattCharPropertySet;
+import org.direct_bt.HCIStatusCode;
+import org.direct_bt.LE_Features;
+import org.direct_bt.LE_PHYs;
+import org.direct_bt.PairingMode;
+import org.direct_bt.SMPIOCapability;
+import org.direct_bt.SMPKeyBin;
+import org.direct_bt.SMPPairingState;
+import org.direct_bt.ScanType;
+import org.jau.net.EUI48;
+
+/**
+ * This Java scanner {@link BTRole::Master} test case, working with {@link DBTServer00}.
+ */
+public class DBTClient00 {
+ /**
+ * Disconnect after processing.
+ *
+ * Default is `false`.
+ */
+ boolean KEEP_CONNECTED = false;
+
+ /**
+ * Remove device when disconnecting.
+ *
+ * This removes the device from all instances within adapter
+ * and hence all potential side-effects of the current instance.
+ *
+ * Default is `false`, since it is good to test whether such side-effects exists.
+ */
+ boolean REMOVE_DEVICE = false;
+
+ final boolean GATT_VERBOSE = false;
+ final boolean SHOW_UPDATE_EVENTS = false;
+
+ final long timestamp_t0 = BTUtils.startupTimeMillis();
+
+ EUI48 useAdapter = EUI48.ALL_DEVICE;
+ BTMode btMode = BTMode.DUAL;
+ BTAdapter clientAdapter = null;
+
+ AtomicInteger deviceReadyCount = new AtomicInteger(0);
+
+ AtomicInteger notificationsReceived = new AtomicInteger(0);
+ AtomicInteger indicationsReceived = new AtomicInteger(0);
+ AtomicInteger completedGATTCommands = new AtomicInteger(0);
+ AtomicInteger completedMeasurements = new AtomicInteger(0);
+ AtomicInteger measurementsLeft = new AtomicInteger(1);
+
+ // Default from DBTServer00.java (or dbt_peripheral00.cpp or DBTPeripheral00.java)
+ final String cmd_uuid = "d0ca6bf3-3d52-4760-98e5-fc5883e93712";
+ final String cmd_rsp_uuid = "d0ca6bf3-3d53-4760-98e5-fc5883e93712";
+ final byte cmd_arg = (byte)0x44;
+
+ public DBTClient00(final EUI48 useAdapter, final BTMode btMode) {
+ this.useAdapter = useAdapter;
+ this.btMode = btMode;
+ }
+ public void setAdapter(final BTAdapter clientAdapter) {
+ this.clientAdapter = clientAdapter;
+ }
+ public BTAdapter getAdapter() { return clientAdapter; }
+
+ static void printf(final String format, final Object... args) {
+ final Object[] args2 = new Object[args.length+1];
+ args2[0] = BTUtils.elapsedTimeMillis();
+ System.arraycopy(args, 0, args2, 1, args.length);
+ System.err.printf("[%,9d] "+format, args2);
+ // System.err.printf("[%,9d] ", BluetoothUtils.getElapsedMillisecond());
+ // System.err.printf(format, args);
+ }
+
+ static void executeOffThread(final Runnable runobj, final String threadName, final boolean detach) {
+ final Thread t = new Thread( runobj, threadName );
+ if( detach ) {
+ t.setDaemon(true); // detach thread
+ }
+ t.start();
+ }
+ static void execute(final Runnable task, final boolean offThread) {
+ if( offThread ) {
+ final Thread t = new Thread(task);
+ t.setDaemon(true);
+ t.start();
+ } else {
+ task.run();
+ }
+ }
+
+ class MyAdapterStatusListener extends AdapterStatusListener {
+ @Override
+ public void adapterSettingsChanged(final BTAdapter adapter, final AdapterSettings oldmask,
+ final AdapterSettings newmask, final AdapterSettings changedmask, final long timestamp) {
+ final boolean initialSetting = oldmask.isEmpty();
+ if( initialSetting ) {
+ BTUtils.println(System.err, "****** Client SETTINGS: "+oldmask+" -> "+newmask+", initial "+changedmask);
+ } else {
+ BTUtils.println(System.err, "****** Client SETTINGS: "+oldmask+" -> "+newmask+", changed "+changedmask);
+ }
+ BTUtils.println(System.err, "Status Adapter:");
+ BTUtils.println(System.err, adapter.toString());
+ }
+
+ @Override
+ public void discoveringChanged(final BTAdapter adapter, final ScanType currentMeta, final ScanType changedType, final boolean changedEnabled, final DiscoveryPolicy policy, final long timestamp) {
+ BTUtils.println(System.err, "****** Client DISCOVERING: meta "+currentMeta+", changed["+changedType+", enabled "+changedEnabled+", policy "+policy+"] on "+adapter);
+ }
+
+ @Override
+ public boolean deviceFound(final BTDevice device, final long timestamp) {
+ if( !BTDeviceRegistry.isDeviceProcessing( device.getAddressAndType() ) &&
+ ( !BTDeviceRegistry.isWaitingForAnyDevice() ||
+ ( BTDeviceRegistry.isWaitingForDevice(device.getAddressAndType().address, device.getName()) &&
+ ( 0 < measurementsLeft.get() || !BTDeviceRegistry.isDeviceProcessed(device.getAddressAndType()) )
+ )
+ )
+ )
+ {
+ BTUtils.println(System.err, "****** Client FOUND__-0: Connecting "+device.toString());
+ {
+ final long td = BTUtils.currentTimeMillis() - timestamp_t0; // adapter-init -> now
+ BTUtils.println(System.err, "PERF: adapter-init -> FOUND__-0 " + td + " ms");
+ }
+ executeOffThread( () -> { connectDiscoveredDevice(device); },
+ "DBT-Connect-"+device.getAddressAndType(), true /* detach */);
+ return true;
+ } else {
+ BTUtils.println(System.err, "****** Client FOUND__-1: NOP "+device.toString());
+ return false;
+ }
+ }
+
+ @Override
+ public void deviceUpdated(final BTDevice device, final EIRDataTypeSet updateMask, final long timestamp) {
+ }
+
+ @Override
+ public void deviceConnected(final BTDevice device, final short handle, final long timestamp) {
+ BTUtils.println(System.err, "****** Client CONNECTED: "+device.toString());
+ }
+
+ @Override
+ public void devicePairingState(final BTDevice device, final SMPPairingState state, final PairingMode mode, final long timestamp) {
+ BTUtils.println(System.err, "****** Client PAIRING_STATE: state "+state+", mode "+mode+": "+device);
+ switch( state ) {
+ case NONE:
+ // next: deviceReady(..)
+ break;
+ case FAILED: {
+ final boolean res = SMPKeyBin.remove(DBTConstants.CLIENT_KEY_PATH, device);
+ BTUtils.println(System.err, "****** Client PAIRING_STATE: state "+state+"; Remove key file "+SMPKeyBin.getFilename(DBTConstants.CLIENT_KEY_PATH, device)+", res "+res);
+ // next: deviceReady() or deviceDisconnected(..)
+ } break;
+ case REQUESTED_BY_RESPONDER:
+ // next: FEATURE_EXCHANGE_STARTED
+ break;
+ case FEATURE_EXCHANGE_STARTED:
+ // next: FEATURE_EXCHANGE_COMPLETED
+ break;
+ case FEATURE_EXCHANGE_COMPLETED:
+ // next: PASSKEY_EXPECTED... or KEY_DISTRIBUTION
+ break;
+ case PASSKEY_EXPECTED: {
+ final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, "");
+ if( null != sec && sec.getPairingPasskey() != BTSecurityRegistry.NO_PASSKEY ) {
+ executeOffThread( () -> { device.setPairingPasskey( sec.getPairingPasskey() ); }, "DBT-SetPasskey-"+device.getAddressAndType(), true /* detach */);
+ } else {
+ executeOffThread( () -> { device.setPairingPasskey( 0 ); }, "DBT-SetPasskey-"+device.getAddressAndType(), true /* detach */);
+ // 3s disconnect: executeOffThread( () -> { device.setPairingPasskeyNegative(); }, "DBT-SetPasskeyNegative-"+device.getAddressAndType(), true /* detach */);
+ }
+ // next: KEY_DISTRIBUTION or FAILED
+ } break;
+ case NUMERIC_COMPARE_EXPECTED: {
+ final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, "");
+ if( null != sec ) {
+ executeOffThread( () -> { device.setPairingNumericComparison( sec.getPairingNumericComparison() ); }, "DBT-SetNumericComp-"+device.getAddressAndType(), true /* detach */);
+ } else {
+ executeOffThread( () -> { device.setPairingNumericComparison( false ); }, "DBT-SetNumericCompFalse-"+device.getAddressAndType(), true /* detach */);
+ }
+ // next: KEY_DISTRIBUTION or FAILED
+ } break;
+ case OOB_EXPECTED:
+ // FIXME: ABORT
+ break;
+ case KEY_DISTRIBUTION:
+ // next: COMPLETED or FAILED
+ break;
+ case COMPLETED:
+ // next: deviceReady(..)
+ break;
+ default: // nop
+ break;
+ }
+ }
+
+ @Override
+ public void deviceReady(final BTDevice device, final long timestamp) {
+ if( !BTDeviceRegistry.isDeviceProcessing( device.getAddressAndType() ) &&
+ ( !BTDeviceRegistry.isWaitingForAnyDevice() ||
+ ( BTDeviceRegistry.isWaitingForDevice(device.getAddressAndType().address, device.getName()) &&
+ ( 0 < measurementsLeft.get() || !BTDeviceRegistry.isDeviceProcessed(device.getAddressAndType()) )
+ )
+ )
+ )
+ {
+ deviceReadyCount.incrementAndGet();
+ BTUtils.println(System.err, "****** Client READY-0: Processing["+deviceReadyCount.get()+"] "+device.toString());
+ {
+ final long td = BTUtils.currentTimeMillis() - timestamp_t0; // adapter-init -> now
+ BTUtils.println(System.err, "PERF: adapter-init -> READY-0 " + td + " ms");
+ }
+ BTDeviceRegistry.addToProcessingDevices(device.getAddressAndType(), device.getName());
+
+ // Be nice to Test* case, allowing to reach its own listener.deviceReady() added later
+ executeOffThread( () -> { processReadyDevice(device); },
+ "DBT-Process-"+device.getAddressAndType(), true /* detach */);
+ // processReadyDevice(device); // AdapterStatusListener::deviceReady() explicitly allows prolonged and complex code execution!
+ } else {
+ BTUtils.println(System.err, "****** Client READY-1: NOP " + device.toString());
+ }
+ }
+
+ @Override
+ public void deviceDisconnected(final BTDevice device, final HCIStatusCode reason, final short handle, final long timestamp) {
+ BTUtils.println(System.err, "****** Client DISCONNECTED: Reason "+reason+", old handle 0x"+Integer.toHexString(handle)+": "+device+" on "+device.getAdapter());
+
+ executeOffThread( () -> { removeDevice(device); }, "DBT-Remove-"+device.getAddressAndType(), true /* detach */);
+ }
+
+ @Override
+ public String toString() {
+ return "AdapterStatusListener[user, per-adapter]";
+ }
+ };
+
+ class MyGATTEventListener implements BTGattChar.Listener {
+ private final int i, j;
+
+ public MyGATTEventListener(final int i_, final int j_) { i=i_; j=j_; }
+
+ @Override
+ public void notificationReceived(final BTGattChar charDecl,
+ final byte[] value, final long timestamp) {
+ if( GATT_VERBOSE ) {
+ final long tR = BTUtils.currentTimeMillis();
+ printf("**[%02d.%02d] Characteristic-Notify: UUID %s, td %d ******\n",
+ i, j, charDecl.getUUID(), (tR-timestamp));
+ printf("**[%02d.%02d] Characteristic: %s ******\n", i, j, charDecl.toString());
+ printf("**[%02d.%02d] Value R: size %d, ro: %s ******\n", i, j, value.length, BTUtils.bytesHexString(value, 0, -1, true));
+ printf("**[%02d.%02d] Value S: %s ******\n", i, j, BTUtils.decodeUTF8String(value, 0, value.length));
+ }
+ notificationsReceived.incrementAndGet();
+ }
+
+ @Override
+ public void indicationReceived(final BTGattChar charDecl,
+ final byte[] value, final long timestamp, final boolean confirmationSent) {
+ if( GATT_VERBOSE ) {
+ final long tR = BTUtils.currentTimeMillis();
+ printf("**[%02d.%02d] Characteristic-Indication: UUID %s, td %d, confirmed %b ******\n",
+ i, j, charDecl.getUUID(), (tR-timestamp), confirmationSent);
+ printf("**[%02d.%02d] Characteristic: %s ******\n", i, j, charDecl.toString());
+ printf("**[%02d.%02d] Value R: size %d, ro: %s ******\n", i, j, value.length, BTUtils.bytesHexString(value, 0, -1, true));
+ printf("**[%02d.%02d] Value S: %s ******\n", i, j, BTUtils.decodeUTF8String(value, 0, value.length));
+ }
+ indicationsReceived.incrementAndGet();
+ }
+ }
+
+ private void resetLastProcessingStats() {
+ completedGATTCommands.set(0);
+ notificationsReceived.set(0);
+ indicationsReceived.set(0);
+ }
+
+ private void connectDiscoveredDevice(final BTDevice device) {
+ BTUtils.println(System.err, "****** Connecting Device: Start " + device.toString());
+
+ resetLastProcessingStats();
+
+ final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, device.getName());
+ if( null != sec ) {
+ BTUtils.println(System.err, "****** Connecting Device: Found SecurityDetail "+sec.toString()+" for "+device.toString());
+ } else {
+ BTUtils.println(System.err, "****** Connecting Device: No SecurityDetail for "+device.toString());
+ }
+ final BTSecurityLevel req_sec_level = null != sec ? sec.getSecLevel() : BTSecurityLevel.UNSET;
+ HCIStatusCode res = device.uploadKeys(DBTConstants.CLIENT_KEY_PATH, req_sec_level, true /* verbose_ */);
+ BTUtils.fprintf_td(System.err, "****** Connecting Device: BTDevice::uploadKeys(...) result %s\n", res.toString());
+ if( HCIStatusCode.SUCCESS != res ) {
+ if( null != sec ) {
+ if( sec.isSecurityAutoEnabled() ) {
+ final boolean r = device.setConnSecurityAuto( sec.getSecurityAutoIOCap() );
+ BTUtils.println(System.err, "****** Connecting Device: Using SecurityDetail.SEC AUTO "+sec+" -> set OK "+r);
+ } else if( sec.isSecLevelOrIOCapSet() ) {
+ final boolean r = device.setConnSecurity(sec.getSecLevel(), sec.getIOCap());
+ BTUtils.println(System.err, "****** Connecting Device: Using SecurityDetail.Level+IOCap "+sec+" -> set OK "+r);
+ } else {
+ final boolean r = device.setConnSecurityAuto( SMPIOCapability.KEYBOARD_ONLY );
+ BTUtils.println(System.err, "****** Connecting Device: Setting SEC AUTO security detail w/ KEYBOARD_ONLY ("+sec+") -> set OK "+r);
+ }
+ } else {
+ final boolean r = device.setConnSecurityAuto( SMPIOCapability.KEYBOARD_ONLY );
+ BTUtils.println(System.err, "****** Connecting Device: Setting SEC AUTO security detail w/ KEYBOARD_ONLY -> set OK "+r);
+ }
+ }
+ final EInfoReport eir = device.getEIR();
+ BTUtils.println(System.err, "Using EIR "+eir.toString());
+
+ short conn_interval_min = (short)12;
+ short conn_interval_max = (short)12;
+ final short conn_latency = (short)0;
+ if( eir.isSet(EIRDataTypeSet.DataType.CONN_IVAL) ) {
+ final short[] minmax = new short[2];
+ eir.getConnInterval(minmax);
+ conn_interval_min = minmax[0];
+ conn_interval_max = minmax[1];
+ }
+ final short supervision_timeout = BTUtils.getHCIConnSupervisorTimeout(conn_latency, (int) ( conn_interval_max * 1.25 ) /* ms */);
+ res = device.connectLE(le_scan_interval, le_scan_window, conn_interval_min, conn_interval_max, conn_latency, supervision_timeout);
+ // res = device.connectDefault();
+ BTUtils.println(System.err, "****** Connecting Device Command, res "+res+": End result "+res+" of " + device.toString());
+ }
+
+ private void processReadyDevice(final BTDevice device) {
+ BTUtils.println(System.err, "****** Processing Ready Device: Start " + device.toString());
+ final long t1 = BTUtils.currentTimeMillis();
+
+ SMPKeyBin.createAndWrite(device, DBTConstants.CLIENT_KEY_PATH, true /* verbose */);
+ final long t2 = BTUtils.currentTimeMillis();
+
+ boolean success = false;
+
+ {
+ final LE_PHYs resTx[] = { new LE_PHYs() };
+ final LE_PHYs resRx[] = { new LE_PHYs() };
+ final HCIStatusCode res = device.getConnectedLE_PHY(resTx, resRx);
+ BTUtils.fprintf_td(System.err, "****** Got Connected LE PHY: status %s: Tx %s, Rx %s\n",
+ res.toString(), resTx[0].toString(), resRx[0].toString());
+ }
+ final long t3 = BTUtils.currentTimeMillis();
+
+ //
+ // GATT Service Processing
+ //
+ try {
+ final List<BTGattService> primServices = device.getGattServices();
+ if( null == primServices || 0 == primServices.size() ) {
+ // Cheating the flow, but avoiding: goto, do-while-false and lastly unreadable intendations
+ // And it is an error case nonetheless ;-)
+ throw new RuntimeException("Processing Ready Device: getServices() failed " + device.toString());
+ }
+ final long t5 = BTUtils.currentTimeMillis();
+ {
+ final long td00 = device.getLastDiscoveryTimestamp() - timestamp_t0; // adapter-init to discovered
+ final long td01 = t1 - timestamp_t0; // adapter-init to processing-start
+ final long td05 = t5 - timestamp_t0; // adapter-init -> gatt-complete
+ final long tdc1 = t1 - device.getLastDiscoveryTimestamp(); // discovered to processing-start
+ final long tdc5 = t5 - device.getLastDiscoveryTimestamp(); // discovered to gatt-complete
+ final long td12 = t2 - t1; // SMPKeyBin
+ final long td23 = t3 - t2; // LE_PHY
+ final long td13 = t3 - t1; // SMPKeyBin + LE_PHY
+ final long td35 = t5 - t3; // get-gatt-services
+ BTUtils.println(System.err, System.lineSeparator()+System.lineSeparator());
+ BTUtils.println(System.err, "PERF: GATT primary-services completed"+System.lineSeparator()+
+ "PERF: adapter-init to discovered " + td00 + " ms,"+System.lineSeparator()+
+ "PERF: adapter-init to processing-start " + td01 + " ms,"+System.lineSeparator()+
+ "PERF: adapter-init to gatt-complete " + td05 + " ms,"+System.lineSeparator()+
+ "PERF: discovered to processing-start " + tdc1 + " ms,"+System.lineSeparator()+
+ "PERF: discovered to gatt-complete " + tdc5 + " ms,"+System.lineSeparator()+
+ "PERF: SMPKeyBin + LE_PHY " + td13 + " ms (SMPKeyBin "+td12+"ms, LE_PHY "+td23+"ms), "+System.lineSeparator()+
+ "PERF: get-gatt-services " + td35 + " ms,"+System.lineSeparator());
+ }
+
+ {
+ final BTGattCmd cmd = new BTGattCmd(device, "TestCmd", null /* service_uuid */, cmd_uuid, cmd_rsp_uuid);
+ cmd.setVerbose(true);
+ final boolean cmd_resolved = cmd.isResolved();
+ BTUtils.println(System.err, "Command test: "+cmd.toString()+", resolved "+cmd_resolved);
+ final byte[] cmd_data = { cmd_arg };
+ final HCIStatusCode cmd_res = cmd.send(true /* prefNoAck */, cmd_data, 3000 /* timeoutMS */);
+ if( HCIStatusCode.SUCCESS == cmd_res ) {
+ if( cmd.hasResponseSet() ) {
+ final byte[] resp = cmd.getResponse();
+ if( 1 == resp.length && resp[0] == cmd_arg ) {
+ BTUtils.fprintf_td(System.err, "Success: %s -> %s (echo response)\n", cmd.toString(), BTUtils.bytesHexString(resp, 0, resp.length, true /* lsb */));
+ completedGATTCommands.incrementAndGet();
+ } else {
+ BTUtils.fprintf_td(System.err, "Failure: %s -> %s (different response)\n", cmd.toString(), BTUtils.bytesHexString(resp, 0, resp.length, true /* lsb */));
+ }
+ } else {
+ BTUtils.fprintf_td(System.err, "Failure: %s -> no response\n", cmd.toString());
+ }
+ } else {
+ BTUtils.fprintf_td(System.err, "Failure: %s -> %s\n", cmd.toString(), cmd_res.toString());
+ }
+ cmd.close();
+ }
+
+ try {
+ int i=0;
+ for(final Iterator<BTGattService> srvIter = primServices.iterator(); srvIter.hasNext(); i++) {
+ final BTGattService primService = srvIter.next();
+ if( GATT_VERBOSE ) {
+ printf(" [%02d] Service UUID %s\n", i, primService.getUUID());
+ printf(" [%02d] %s\n", i, primService.toString());
+ }
+ int j=0;
+ final List<BTGattChar> serviceCharacteristics = primService.getChars();
+ for(final Iterator<BTGattChar> charIter = serviceCharacteristics.iterator(); charIter.hasNext(); j++) {
+ final BTGattChar serviceChar = charIter.next();
+ if( GATT_VERBOSE ) {
+ printf(" [%02d.%02d] Characteristic: UUID %s\n", i, j, serviceChar.getUUID());
+ printf(" [%02d.%02d] %s\n", i, j, serviceChar.toString());
+ }
+ final GattCharPropertySet properties = serviceChar.getProperties();
+ if( properties.isSet(GattCharPropertySet.Type.Read) ) {
+ final byte[] value = serviceChar.readValue();
+ final String svalue = BTUtils.decodeUTF8String(value, 0, value.length);
+ if( GATT_VERBOSE ) {
+ printf(" [%02d.%02d] value: %s ('%s')\n", i, j, BTUtils.bytesHexString(value, 0, -1, true), svalue);
+ }
+ }
+ int k=0;
+ final List<BTGattDesc> charDescList = serviceChar.getDescriptors();
+ for(final Iterator<BTGattDesc> descIter = charDescList.iterator(); descIter.hasNext(); k++) {
+ final BTGattDesc charDesc = descIter.next();
+ if( GATT_VERBOSE ) {
+ printf(" [%02d.%02d.%02d] Descriptor: UUID %s\n", i, j, k, charDesc.getUUID());
+ printf(" [%02d.%02d.%02d] %s\n", i, j, k, charDesc.toString());
+ }
+ }
+ final boolean cccdEnableResult[] = { false, false };
+ if( serviceChar.enableNotificationOrIndication( cccdEnableResult ) ) {
+ // ClientCharConfigDescriptor (CCD) is available
+ final boolean clAdded = null != serviceChar.addCharListener( new MyGATTEventListener(i, j) );
+ if( GATT_VERBOSE ) {
+ printf(" [%02d.%02d] Characteristic-Listener: Notification(%b), Indication(%b): Added %b\n",
+ i, j, cccdEnableResult[0], cccdEnableResult[1], clAdded);
+ printf("\n");
+ }
+ }
+ }
+ if( GATT_VERBOSE ) {
+ printf("\n");
+ }
+ }
+ } catch( final Exception ex) {
+ BTUtils.println(System.err, "Caught "+ex.getMessage());
+ ex.printStackTrace();
+ }
+ // FIXME sleep 1s for notifications and/or indications ...
+ try {
+ Thread.sleep(1000);
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ success = completedGATTCommands.get() > 0 && ( notificationsReceived.get() > 0 || indicationsReceived.get() > 0 );
+ } catch (final Throwable t ) {
+ BTUtils.println(System.err, "****** Processing Ready Device: Exception caught for " + device.toString() + ": "+t.getMessage());
+ t.printStackTrace();
+ }
+
+ BTUtils.println(System.err, "****** Processing Ready Device: End-1: Success " + success +
+ " on " + device.toString() + "; devInProc "+BTDeviceRegistry.getProcessingDeviceCount());
+
+ BTDeviceRegistry.removeFromProcessingDevices( device.getAddressAndType() );
+
+ if( DiscoveryPolicy.PAUSE_CONNECTED_UNTIL_DISCONNECTED == discoveryPolicy ) {
+ device.getAdapter().removeDevicePausingDiscovery(device);
+ }
+
+ BTUtils.println(System.err, "****** Processing Ready Device: End-2: Success " + success +
+ " on " + device.toString() + "; devInProc "+BTDeviceRegistry.getProcessingDeviceCount());
+ if( success ) {
+ BTDeviceRegistry.addToProcessedDevices(device.getAddressAndType(), device.getName());
+ }
+ device.removeAllCharListener();
+
+ if( !KEEP_CONNECTED ) {
+ if( REMOVE_DEVICE ) {
+ device.remove();
+ } else {
+ device.disconnect();
+ }
+ }
+
+ if( success ) {
+ completedMeasurements.addAndGet(1);
+ }
+ if( 0 < measurementsLeft.get() ) {
+ measurementsLeft.decrementAndGet();
+ }
+ BTUtils.println(System.err, "****** Processing Ready Device: Success "+success+
+ "; Measurements completed "+completedMeasurements.get()+
+ ", done "+completedMeasurements.get()+
+ ", left "+measurementsLeft.get()+
+ "; Received notitifications "+notificationsReceived.get()+", indications "+indicationsReceived.get()+
+ "; Completed GATT commands "+completedGATTCommands.get()+
+ ": "+device.getAddressAndType());
+ }
+
+ private void removeDevice(final BTDevice device) {
+ BTUtils.println(System.err, "****** Remove Device: removing: "+device.getAddressAndType());
+
+ BTDeviceRegistry.removeFromProcessingDevices(device.getAddressAndType());
+
+ if( REMOVE_DEVICE ) {
+ device.remove();
+ }
+ }
+
+ DiscoveryPolicy discoveryPolicy = DiscoveryPolicy.PAUSE_CONNECTED_UNTIL_READY; // default value
+ boolean le_scan_active = true; // default value
+ static final short le_scan_interval = (short)24; // default value
+ static final short le_scan_window = (short)24; // default value
+ static final byte filter_policy = (byte)0; // default value
+ static final boolean filter_dup = true; // default value
+
+ public HCIStatusCode startDiscovery(final BTAdapter adapter, final String msg) {
+ if( !useAdapter.equals(EUI48.ALL_DEVICE) && !useAdapter.equals(adapter.getAddressAndType().address) ) {
+ BTUtils.fprintf_td(System.err, "****** Start discovery (%s): Adapter not selected: %s\n", msg, adapter.toString());
+ return HCIStatusCode.FAILED;
+ }
+
+ resetLastProcessingStats();
+
+ final HCIStatusCode status = adapter.startDiscovery( discoveryPolicy, le_scan_active, le_scan_interval, le_scan_window, filter_policy, filter_dup );
+ BTUtils.println(System.err, "****** Start discovery ("+msg+") result: "+status);
+ return status;
+ }
+
+ public HCIStatusCode stopDiscovery(final BTAdapter adapter, final String msg) {
+ if( !useAdapter.equals(EUI48.ALL_DEVICE) && !useAdapter.equals(adapter.getAddressAndType().address) ) {
+ BTUtils.fprintf_td(System.err, "****** Stop discovery (%s): Adapter not selected: %s\n", msg, adapter.toString());
+ return HCIStatusCode.FAILED;
+ }
+
+ final HCIStatusCode status = adapter.stopDiscovery();
+ BTUtils.println(System.err, "****** Stop discovery ("+msg+") result: "+status);
+ return status;
+ }
+
+ public boolean initAdapter(final BTAdapter adapter) {
+ if( !useAdapter.equals(EUI48.ALL_DEVICE) && !useAdapter.equals(adapter.getAddressAndType().address) ) {
+ BTUtils.fprintf_td(System.err, "initClientAdapter: Adapter not selected: %s\n", adapter.toString());
+ return false;
+ }
+ // Initialize with defaults and power-on
+ if( !adapter.isInitialized() ) {
+ final HCIStatusCode status = adapter.initialize( btMode );
+ if( HCIStatusCode.SUCCESS != status ) {
+ BTUtils.fprintf_td(System.err, "initClientAdapter: Adapter initialization failed: %s: %s\n",
+ status.toString(), adapter.toString());
+ return false;
+ }
+ } else if( !adapter.setPowered( true ) ) {
+ BTUtils.fprintf_td(System.err, "initClientAdapter: Already initialized adapter power-on failed:: %s\n", adapter.toString());
+ return false;
+ }
+ // adapter is powered-on
+ BTUtils.fprintf_td(System.err, "initClientAdapter: %s\n", adapter.toString());
+ {
+ final LE_Features le_feats = adapter.getLEFeatures();
+ BTUtils.fprintf_td(System.err, "initClientAdapter: LE_Features %s\n", le_feats.toString());
+ }
+ {
+ final LE_PHYs Tx = new LE_PHYs(LE_PHYs.PHY.LE_2M);
+ final LE_PHYs Rx = new LE_PHYs(LE_PHYs.PHY.LE_2M);
+
+ final HCIStatusCode res = adapter.setDefaultLE_PHY(Tx, Rx);
+ BTUtils.fprintf_td(System.err, "initClientAdapter: Set Default LE PHY: status %s: Tx %s, Rx %s\n",
+ res.toString(), Tx.toString(), Rx.toString());
+ }
+ final AdapterStatusListener asl = new MyAdapterStatusListener();
+ adapter.addStatusListener( asl );
+
+ return true;
+ }
+}
diff --git a/trial/java/trial/org/direct_bt/DBTConstants.java b/trial/java/trial/org/direct_bt/DBTConstants.java
new file mode 100644
index 00000000..5cf890d5
--- /dev/null
+++ b/trial/java/trial/org/direct_bt/DBTConstants.java
@@ -0,0 +1,31 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package trial.org.direct_bt;
+
+public class DBTConstants {
+ public static final String CLIENT_KEY_PATH = "client_keys";
+
+ public static final String SERVER_KEY_PATH = "server_keys";
+}
diff --git a/trial/java/trial/org/direct_bt/DBTServer00.java b/trial/java/trial/org/direct_bt/DBTServer00.java
new file mode 100644
index 00000000..571387a6
--- /dev/null
+++ b/trial/java/trial/org/direct_bt/DBTServer00.java
@@ -0,0 +1,742 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package trial.org.direct_bt;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.direct_bt.AdapterSettings;
+import org.direct_bt.AdapterStatusListener;
+import org.direct_bt.BDAddressAndType;
+import org.direct_bt.BTMode;
+import org.direct_bt.BTSecurityLevel;
+import org.direct_bt.BTAdapter;
+import org.direct_bt.BTDevice;
+import org.direct_bt.BTDeviceRegistry;
+import org.direct_bt.BTSecurityRegistry;
+import org.direct_bt.BTUtils;
+import org.direct_bt.DBGattChar;
+import org.direct_bt.DBGattDesc;
+import org.direct_bt.DBGattServer;
+import org.direct_bt.DBGattService;
+import org.direct_bt.DBGattValue;
+import org.direct_bt.DiscoveryPolicy;
+import org.direct_bt.EIRDataTypeSet;
+import org.direct_bt.EInfoReport;
+import org.direct_bt.GAPFlags;
+import org.direct_bt.GattCharPropertySet;
+import org.direct_bt.HCIStatusCode;
+import org.direct_bt.LE_Features;
+import org.direct_bt.LE_PHYs;
+import org.direct_bt.PairingMode;
+import org.direct_bt.SMPIOCapability;
+import org.direct_bt.SMPPairingState;
+import org.direct_bt.ScanType;
+import org.jau.net.EUI48;
+
+/**
+ * This Java peripheral {@link BTRole::Slave} test case working with {@link DBTClient00}.
+ */
+public class DBTServer00 {
+ final boolean GATT_VERBOSE = false;
+ boolean SHOW_UPDATE_EVENTS = false;
+
+ EUI48 useAdapter = EUI48.ALL_DEVICE;
+ BTMode btMode = BTMode.DUAL;
+ boolean use_SC = true;
+ String adapterName = "TestDev001_J";
+ final String adapterShortName = "TDev001J";
+ BTSecurityLevel adapterSecurityLevel = BTSecurityLevel.UNSET;
+ BTAdapter serverAdapter = null;
+
+ public AtomicInteger servedConnections = new AtomicInteger(0);
+
+ public DBTServer00(final EUI48 useAdapter, final BTMode btMode, final boolean use_SC, final String adapterName, final BTSecurityLevel adapterSecurityLevel) {
+ this.useAdapter = useAdapter;
+ this.btMode = btMode;
+ this.use_SC = use_SC;
+ this.adapterName = adapterName;
+ this.adapterSecurityLevel = adapterSecurityLevel;
+
+ final MyGATTServerListener listener = new MyGATTServerListener();
+ dbGattServer.addListener( listener );
+ }
+ public DBTServer00(final EUI48 useAdapter, final String adapterName, final BTSecurityLevel adapterSecurityLevel) {
+ this(useAdapter, BTMode.DUAL, true /* SC */, adapterName, adapterSecurityLevel);
+ }
+ public DBTServer00(final String adapterName, final BTSecurityLevel adapterSecurityLevel) {
+ this(EUI48.ALL_DEVICE, BTMode.DUAL, true /* SC */, adapterName, adapterSecurityLevel);
+ }
+ public void setAdapter(final BTAdapter serverAdapter) {
+ this.serverAdapter = serverAdapter;
+ }
+ public BTAdapter getAdapter() { return serverAdapter; }
+
+ boolean matches(final List<BDAddressAndType> cont, final BDAddressAndType mac) {
+ for(final Iterator<BDAddressAndType> it = cont.iterator(); it.hasNext(); ) {
+ if( it.next().matches(mac) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static void printf(final String format, final Object... args) {
+ final Object[] args2 = new Object[args.length+1];
+ args2[0] = BTUtils.elapsedTimeMillis();
+ System.arraycopy(args, 0, args2, 1, args.length);
+ System.err.printf("[%,9d] "+format, args2);
+ // System.err.printf("[%,9d] ", BluetoothUtils.getElapsedMillisecond());
+ // System.err.printf(format, args);
+ }
+
+ static Thread executeOffThread(final Runnable runobj, final String threadName, final boolean detach) {
+ final Thread t = new Thread( runobj, threadName );
+ if( detach ) {
+ t.setDaemon(true); // detach thread
+ }
+ t.start();
+ return t;
+ }
+ static Thread executeOffThread(final Runnable runobj, final boolean detach) {
+ final Thread t = new Thread( runobj );
+ if( detach ) {
+ t.setDaemon(true); // detach thread
+ }
+ t.start();
+ return t;
+ }
+
+ static DBGattValue make_gvalue(final String name) {
+ final byte[] p = name.getBytes(StandardCharsets.UTF_8);
+ return new DBGattValue(p, p.length);
+ }
+ static DBGattValue make_gvalue(final String name, final int capacity) {
+ final byte[] p = name.getBytes(StandardCharsets.UTF_8);
+ return new DBGattValue(p, Math.max(capacity, p.length), capacity > p.length/* variable_length */);
+ }
+ static DBGattValue make_gvalue(final short v) {
+ final byte[] p = { (byte)0, (byte)0 };
+ p[0] = (byte)(v);
+ p[1] = (byte)(v >> 8);
+ return new DBGattValue(p, p.length);
+ }
+ static DBGattValue make_gvalue(final int capacity, final int size) {
+ final byte[] p = new byte[size];
+ return new DBGattValue(p, capacity, true /* variable_length */);
+ }
+
+ static String DataServiceUUID = "d0ca6bf3-3d50-4760-98e5-fc5883e93712";
+ static String StaticDataUUID = "d0ca6bf3-3d51-4760-98e5-fc5883e93712";
+ static String CommandUUID = "d0ca6bf3-3d52-4760-98e5-fc5883e93712";
+ static String ResponseUUID = "d0ca6bf3-3d53-4760-98e5-fc5883e93712";
+ static String PulseDataUUID = "d0ca6bf3-3d54-4760-98e5-fc5883e93712";
+
+ // DBGattServerRef dbGattServer = std::make_shared<DBGattServer>(
+ private final DBGattServer dbGattServer = new DBGattServer(
+ /* services: */
+ Arrays.asList( // DBGattService
+ new DBGattService ( true /* primary */,
+ DBGattService.UUID16.GENERIC_ACCESS /* type_ */,
+ Arrays.asList( // DBGattChar
+ new DBGattChar( DBGattChar.UUID16.DEVICE_NAME /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Read),
+ new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
+ make_gvalue(adapterName, 128) /* value */ ),
+ new DBGattChar( DBGattChar.UUID16.APPEARANCE /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Read),
+ new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
+ make_gvalue((short)0) /* value */ )
+ ) ),
+ new DBGattService ( true /* primary */,
+ DBGattService.UUID16.DEVICE_INFORMATION /* type_ */,
+ Arrays.asList( // DBGattChar
+ new DBGattChar( DBGattChar.UUID16.MANUFACTURER_NAME_STRING /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Read),
+ new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
+ make_gvalue("Gothel Software") /* value */ ),
+ new DBGattChar( DBGattChar.UUID16.MODEL_NUMBER_STRING /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Read),
+ new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
+ make_gvalue("2.4.0-pre") /* value */ ),
+ new DBGattChar( DBGattChar.UUID16.SERIAL_NUMBER_STRING /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Read),
+ new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
+ make_gvalue("sn:0123456789") /* value */ ),
+ new DBGattChar( DBGattChar.UUID16.HARDWARE_REVISION_STRING /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Read),
+ new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
+ make_gvalue("hw:0123456789") /* value */ ),
+ new DBGattChar( DBGattChar.UUID16.FIRMWARE_REVISION_STRING /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Read),
+ new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
+ make_gvalue("fw:0123456789") /* value */ ),
+ new DBGattChar( DBGattChar.UUID16.SOFTWARE_REVISION_STRING /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Read),
+ new ArrayList<DBGattDesc>(/* intentionally w/o Desc */ ),
+ make_gvalue("sw:0123456789") /* value */ )
+ ) ),
+ new DBGattService ( true /* primary */,
+ DataServiceUUID /* type_ */,
+ Arrays.asList( // DBGattChar
+ new DBGattChar( StaticDataUUID /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Read),
+ Arrays.asList( // DBGattDesc
+ new DBGattDesc( DBGattDesc.UUID16.USER_DESC, make_gvalue("DATA_STATIC") )
+ ),
+ make_gvalue("Proprietary Static Data 0x00010203") /* value */ ),
+ new DBGattChar( CommandUUID /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.WriteNoAck).set(GattCharPropertySet.Type.WriteWithAck),
+ Arrays.asList( // DBGattDesc
+ new DBGattDesc( DBGattDesc.UUID16.USER_DESC, make_gvalue("COMMAND") )
+ ),
+ make_gvalue(128, 64) /* value */ ),
+ new DBGattChar( ResponseUUID /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Notify).set(GattCharPropertySet.Type.Indicate),
+ Arrays.asList( // DBGattDesc
+ new DBGattDesc( DBGattDesc.UUID16.USER_DESC, make_gvalue("RESPONSE") ),
+ DBGattDesc.createClientCharConfig()
+ ),
+ make_gvalue((short)0) /* value */ ),
+ new DBGattChar( PulseDataUUID /* value_type_ */,
+ new GattCharPropertySet(GattCharPropertySet.Type.Notify).set(GattCharPropertySet.Type.Indicate),
+ Arrays.asList( // DBGattDesc
+ new DBGattDesc( DBGattDesc.UUID16.USER_DESC, make_gvalue("DATA_PULSE") ),
+ DBGattDesc.createClientCharConfig()
+ ),
+ make_gvalue("Synthethic Sensor 01") /* value */ )
+ ) )
+ ) );
+
+
+ class MyAdapterStatusListener extends AdapterStatusListener {
+ @Override
+ public void adapterSettingsChanged(final BTAdapter adapter, final AdapterSettings oldmask,
+ final AdapterSettings newmask, final AdapterSettings changedmask, final long timestamp) {
+ final boolean initialSetting = oldmask.isEmpty();
+ if( initialSetting ) {
+ BTUtils.println(System.err, "****** Server SETTINGS: "+oldmask+" -> "+newmask+", initial "+changedmask);
+ } else {
+ BTUtils.println(System.err, "****** Server SETTINGS: "+oldmask+" -> "+newmask+", changed "+changedmask);
+ }
+ BTUtils.println(System.err, "Status Adapter:");
+ BTUtils.println(System.err, adapter.toString());
+ }
+
+ @Override
+ public void discoveringChanged(final BTAdapter adapter, final ScanType currentMeta, final ScanType changedType, final boolean changedEnabled, final DiscoveryPolicy policy, final long timestamp) {
+ BTUtils.println(System.err, "****** Server DISCOVERING: meta "+currentMeta+", changed["+changedType+", enabled "+changedEnabled+", policy "+policy+"] on "+adapter);
+ }
+
+ @Override
+ public boolean deviceFound(final BTDevice device, final long timestamp) {
+ BTUtils.println(System.err, "****** Server FOUND__-1: NOP "+device.toString());
+ return false;
+ }
+
+ @Override
+ public void deviceUpdated(final BTDevice device, final EIRDataTypeSet updateMask, final long timestamp) {
+ if( SHOW_UPDATE_EVENTS ) {
+ BTUtils.println(System.err, "****** Server UPDATED: "+updateMask+" of "+device);
+ }
+ }
+
+ @Override
+ public void deviceConnected(final BTDevice device, final short handle, final long timestamp) {
+ BTUtils.println(System.err, "****** Server CONNECTED: "+device.toString());
+ }
+
+ @Override
+ public void devicePairingState(final BTDevice device, final SMPPairingState state, final PairingMode mode, final long timestamp) {
+ BTUtils.println(System.err, "****** Server PAIRING_STATE: state "+state+", mode "+mode+": "+device);
+ switch( state ) {
+ case NONE:
+ // next: deviceReady(..)
+ break;
+ case FAILED: {
+ // next: deviceReady() or deviceDisconnected(..)
+ } break;
+ case REQUESTED_BY_RESPONDER:
+ // next: FEATURE_EXCHANGE_STARTED
+ break;
+ case FEATURE_EXCHANGE_STARTED:
+ // next: FEATURE_EXCHANGE_COMPLETED
+ break;
+ case FEATURE_EXCHANGE_COMPLETED:
+ // next: PASSKEY_EXPECTED... or KEY_DISTRIBUTION
+ break;
+ case PASSKEY_EXPECTED: {
+ final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, "");
+ if( null != sec && sec.getPairingPasskey() != BTSecurityRegistry.NO_PASSKEY ) {
+ executeOffThread( () -> { device.setPairingPasskey( sec.getPairingPasskey() ); }, "DBT-SetPasskey-"+device.getAddressAndType(), true /* detach */);
+ } else {
+ executeOffThread( () -> { device.setPairingPasskey( 0 ); }, "DBT-SetPasskey-"+device.getAddressAndType(), true /* detach */);
+ // 3s disconnect: executeOffThread( () -> { device.setPairingPasskeyNegative(); }, "DBT-SetPasskeyNegative-"+device.getAddressAndType(), true /* detach */);
+ }
+ // next: KEY_DISTRIBUTION or FAILED
+ } break;
+ case NUMERIC_COMPARE_EXPECTED: {
+ final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getStartOf(device.getAddressAndType().address, "");
+ if( null != sec ) {
+ executeOffThread( () -> { device.setPairingNumericComparison( sec.getPairingNumericComparison() ); }, "DBT-SetNumericComp-"+device.getAddressAndType(), true /* detach */);
+ } else {
+ executeOffThread( () -> { device.setPairingNumericComparison( false ); }, "DBT-SetNumericCompFalse-"+device.getAddressAndType(), true /* detach */);
+ }
+ // next: KEY_DISTRIBUTION or FAILED
+ } break;
+ case OOB_EXPECTED:
+ // FIXME: ABORT
+ break;
+ case KEY_DISTRIBUTION:
+ // next: COMPLETED or FAILED
+ break;
+ case COMPLETED:
+ // next: deviceReady(..)
+ break;
+ default: // nop
+ break;
+ }
+ }
+
+ @Override
+ public void deviceReady(final BTDevice device, final long timestamp) {
+ BTUtils.println(System.err, "****** Server READY-1: NOP " + device.toString());
+ }
+
+ @Override
+ public void deviceDisconnected(final BTDevice device, final HCIStatusCode reason, final short handle, final long timestamp) {
+ BTUtils.println(System.err, "****** Server DISCONNECTED (count "+(servedConnections.get()+1)+"): Reason "+reason+", old handle 0x"+Integer.toHexString(handle)+": "+device+" on "+device.getAdapter());
+
+ executeOffThread( () -> { processDisconnectedDevice(device); },
+ "DBT-Disconnected-"+device.getAdapter().getAddressAndType(), true /* detach */);
+ }
+
+ @Override
+ public String toString() {
+ return "AdapterStatusListener[user, per-adapter]";
+ }
+ };
+
+ class MyGATTServerListener extends DBGattServer.Listener implements AutoCloseable {
+ private volatile boolean sync_data;
+ private final Thread pulseSenderThread;
+ private volatile boolean stopPulseSender = false;
+
+ private volatile short handlePulseDataNotify = 0;
+ private volatile short handlePulseDataIndicate = 0;
+ private volatile short handleResponseDataNotify = 0;
+ private volatile short handleResponseDataIndicate = 0;
+
+ private BTDevice connectedDevice;
+ private int usedMTU = 23; // BTGattHandler::number(BTGattHandler::Defaults::MIN_ATT_MTU);
+
+ private boolean matches(final BTDevice device) {
+ final boolean local = sync_data; // SC-DRF acquire via sc_atomic_bool::load()
+ final boolean res = null != connectedDevice ? connectedDevice.equals(device) : false;
+ sync_data = local; // SC-DRF release via sc_atomic_bool::store()
+ return res;
+ }
+
+ private void clear() {
+ final boolean local = sync_data; // SC-DRF acquire via sc_atomic_bool::load()
+
+ handlePulseDataNotify = 0;
+ handlePulseDataIndicate = 0;
+ handleResponseDataNotify = 0;
+ handleResponseDataIndicate = 0;
+ connectedDevice = null;
+
+ dbGattServer.resetGattClientCharConfig(DataServiceUUID, PulseDataUUID);
+ dbGattServer.resetGattClientCharConfig(DataServiceUUID, ResponseUUID);
+
+ sync_data = local; // SC-DRF release via sc_atomic_bool::store()
+ }
+
+ private void pulseSender() {
+ while( !stopPulseSender ) {
+ {
+ final boolean local = sync_data; // SC-DRF acquire via sc_atomic_bool::load()
+ if( null != connectedDevice && connectedDevice.getConnected() ) {
+ if( 0 != handlePulseDataNotify || 0 != handlePulseDataIndicate ) {
+ final String data = String.format("Dynamic Data Example. Elapsed Milliseconds: %,9d", BTUtils.elapsedTimeMillis());
+ final byte[] v = data.getBytes(StandardCharsets.UTF_8);
+ if( 0 != handlePulseDataNotify ) {
+ if( GATT_VERBOSE ) {
+ BTUtils.fprintf_td(System.err, "****** GATT::sendNotification: PULSE to %s\n", connectedDevice.toString());
+ }
+ connectedDevice.sendNotification(handlePulseDataNotify, v);
+ }
+ if( 0 != handlePulseDataIndicate ) {
+ if( GATT_VERBOSE ) {
+ BTUtils.fprintf_td(System.err, "****** GATT::sendIndication: PULSE to %s\n", connectedDevice.toString());
+ }
+ connectedDevice.sendIndication(handlePulseDataIndicate, v);
+ }
+ }
+ }
+ sync_data = local; // SC-DRF release via sc_atomic_bool::store()
+ }
+ try {
+ Thread.sleep(500); // 500ms
+ } catch (final InterruptedException e) { }
+ }
+ }
+
+ void sendResponse(final byte[] data) {
+ if( null != connectedDevice && connectedDevice.getConnected() ) {
+ if( 0 != handleResponseDataNotify || 0 != handleResponseDataIndicate ) {
+ if( 0 != handleResponseDataNotify ) {
+ if( GATT_VERBOSE ) {
+ BTUtils.fprintf_td(System.err, "****** GATT::sendNotification: %s to %s\n",
+ BTUtils.bytesHexString(data, 0, data.length, true /* lsb */), connectedDevice.toString());
+ }
+ connectedDevice.sendNotification(handleResponseDataNotify, data);
+ }
+ if( 0 != handleResponseDataIndicate ) {
+ if( GATT_VERBOSE ) {
+ BTUtils.fprintf_td(System.err, "****** GATT::sendIndication: %s to %s\n",
+ BTUtils.bytesHexString(data, 0, data.length, true /* lsb */), connectedDevice.toString());
+ }
+ connectedDevice.sendIndication(handleResponseDataIndicate, data);
+ }
+ }
+ }
+ }
+
+ public MyGATTServerListener() {
+ pulseSenderThread = executeOffThread( () -> { pulseSender(); }, "GattServer-PulseSender", false /* detach */);
+ }
+
+ @Override
+ public void close() {
+ {
+ final boolean local = sync_data; // SC-DRF acquire via sc_atomic_bool::load()
+ stopPulseSender = true;
+ connectedDevice = null;
+ sync_data = local; // SC-DRF release via sc_atomic_bool::store()
+ }
+ if( !pulseSenderThread.isDaemon() ) {
+ try {
+ pulseSenderThread.join(1000);
+ } catch (final InterruptedException e) { }
+ }
+ super.close();
+ }
+
+ @Override
+ public void connected(final BTDevice device, final int initialMTU) {
+ final boolean local = sync_data; // SC-DRF acquire via sc_atomic_bool::load()
+ final boolean available = null == connectedDevice;
+ BTUtils.fprintf_td(System.err, "****** GATT::connected(available %b): initMTU %d, %s\n",
+ available, initialMTU, device.toString());
+ if( available ) {
+ connectedDevice = device;
+ usedMTU = initialMTU;
+ }
+ sync_data = local; // SC-DRF release via sc_atomic_bool::store()
+ }
+
+ @Override
+ public void disconnected(final BTDevice device) {
+ final boolean local = sync_data; // SC-DRF acquire via sc_atomic_bool::load()
+ final boolean match = null != connectedDevice ? connectedDevice.equals(device) : false;
+ BTUtils.fprintf_td(System.err, "****** GATT::disconnected(match %b): %s\n", match, device.toString());
+ if( match ) {
+ clear();
+ }
+ sync_data = local; // SC-DRF release via sc_atomic_bool::store()
+ }
+
+ @Override
+ public void mtuChanged(final BTDevice device, final int mtu) {
+ final boolean match = matches(device);
+ if( GATT_VERBOSE ) {
+ BTUtils.fprintf_td(System.err, "****** GATT::mtuChanged(match %b): %d -> %d, %s\n",
+ match, match ? (int)usedMTU : 0, mtu, device.toString());
+ }
+ if( match ) {
+ usedMTU = mtu;
+ }
+ }
+
+ @Override
+ public boolean readCharValue(final BTDevice device, final DBGattService s, final DBGattChar c) {
+ final boolean match = matches(device);
+ if( GATT_VERBOSE ) {
+ BTUtils.fprintf_td(System.err, "****** GATT::readCharValue(match %b): to %s, from\n %s\n %s\n",
+ match, device.toString(), s.toString(), c.toString());
+ }
+ return match;
+ }
+
+ @Override
+ public boolean readDescValue(final BTDevice device, final DBGattService s, final DBGattChar c, final DBGattDesc d) {
+ final boolean match = matches(device);
+ if( GATT_VERBOSE ) {
+ BTUtils.fprintf_td(System.err, "****** GATT::readDescValue(match %b): to %s, from\n %s\n %s\n %s\n",
+ match, device.toString(), s.toString(), c.toString(), d.toString());
+ }
+ return match;
+ }
+
+ @Override
+ public boolean writeCharValue(final BTDevice device, final DBGattService s, final DBGattChar c, final byte[] value, final int value_offset) {
+ final boolean match = matches(device);
+ if( GATT_VERBOSE ) {
+ final String value_s = BTUtils.bytesHexString(value, 0, value.length, true /* lsbFirst */)+
+ " '"+BTUtils.decodeUTF8String(value, 0, value.length)+"'";
+ BTUtils.fprintf_td(System.err, "****** GATT::writeCharValue(match %b): %s @ %d from %s, to\n %s\n %s\n",
+ match, value_s, value_offset,
+ device.toString(), s.toString(), c.toString());
+ }
+ return match;
+ }
+
+ @Override
+ public void writeCharValueDone(final BTDevice device, final DBGattService s, final DBGattChar c) {
+ final boolean match = matches(device);
+ final DBGattValue value = c.getValue();
+ if( GATT_VERBOSE ) {
+ BTUtils.fprintf_td(System.err, "****** GATT::writeCharValueDone(match %b): From %s, to\n %s\n %s\n Char-Value: %s\n",
+ match, device.toString(), s.toString(), c.toString(), value.toString());
+ }
+ if( match &&
+ c.getValueType().equals( CommandUUID ) &&
+ ( 0 != handleResponseDataNotify || 0 != handleResponseDataIndicate ) )
+ {
+ executeOffThread( () -> { sendResponse( value.data() ); }, true /* detach */);
+ }
+ }
+
+ @Override
+ public boolean writeDescValue(final BTDevice device, final DBGattService s, final DBGattChar c, final DBGattDesc d,
+ final byte[] value, final int value_offset)
+ {
+ final boolean match = matches(device);
+ if( GATT_VERBOSE ) {
+ final String value_s = BTUtils.bytesHexString(value, 0, value.length, true /* lsbFirst */)+
+ " '"+BTUtils.decodeUTF8String(value, 0, value.length)+"'";
+ BTUtils.fprintf_td(System.err, "****** GATT::writeDescValue(match %b): %s @ %d from %s\n %s\n %s\n %s\n",
+ match, value_s, value_offset,
+ device.toString(), s.toString(), c.toString(), d.toString());
+ }
+ return match;
+ }
+
+ @Override
+ public void writeDescValueDone(final BTDevice device, final DBGattService s, final DBGattChar c, final DBGattDesc d) {
+ if( GATT_VERBOSE ) {
+ final boolean match = matches(device);
+ final DBGattValue value = d.getValue();
+ BTUtils.fprintf_td(System.err, "****** GATT::writeDescValueDone(match %b): From %s\n %s\n %s\n %s\n Desc-Value: %s\n",
+ match, device.toString(), s.toString(), c.toString(), d.toString(), value.toString());
+ }
+ }
+
+ @Override
+ public void clientCharConfigChanged(final BTDevice device, final DBGattService s, final DBGattChar c, final DBGattDesc d,
+ final boolean notificationEnabled, final boolean indicationEnabled)
+ {
+ final boolean match = matches(device);
+ if( GATT_VERBOSE ) {
+ final DBGattValue value = d.getValue();
+ BTUtils.fprintf_td(System.err, "****** GATT::clientCharConfigChanged(match %b): notify %b, indicate %b from %s\n %s\n %s\n %s\n Desc-Value: %s\n",
+ match, notificationEnabled, indicationEnabled,
+ device.toString(), s.toString(), c.toString(), d.toString(), value.toString());
+ }
+ if( match ) {
+ final boolean local = sync_data; // SC-DRF acquire via sc_atomic_bool::load()
+ final String value_type = c.getValueType();
+ final short value_handle = c.getValueHandle();
+ if( value_type.equals( PulseDataUUID ) ) {
+ handlePulseDataNotify = notificationEnabled ? value_handle : 0;
+ handlePulseDataIndicate = indicationEnabled ? value_handle : 0;
+ } else if( value_type.equals( ResponseUUID ) ) {
+ handleResponseDataNotify = notificationEnabled ? value_handle : 0;
+ handleResponseDataIndicate = indicationEnabled ? value_handle : 0;
+ }
+ sync_data = local; // SC-DRF release via sc_atomic_bool::store()
+ }
+ }
+ }
+ static final short adv_interval_min=(short)640;
+ static final short adv_interval_max=(short)640;
+ static final byte adv_type=(byte)0; // AD_PDU_Type::ADV_IND;
+ static final byte adv_chan_map=(byte)0x07;
+ static final byte filter_policy=(byte)0x00;
+
+ public HCIStatusCode stopAdvertising(final BTAdapter adapter, final String msg) {
+ if( !useAdapter.equals(EUI48.ALL_DEVICE) && !useAdapter.equals(adapter.getAddressAndType().address) ) {
+ BTUtils.fprintf_td(System.err, "****** Stop advertising (%s): Adapter not selected: %s\n", msg, adapter.toString());
+ return HCIStatusCode.FAILED;
+ }
+ final HCIStatusCode status = adapter.stopAdvertising();
+ BTUtils.println(System.err, "****** Stop advertising ("+msg+") result: "+status+": "+adapter.toString());
+ return status;
+ }
+
+ public HCIStatusCode startAdvertising(final BTAdapter adapter, final String msg) {
+ if( !useAdapter.equals(EUI48.ALL_DEVICE) && !useAdapter.equals(adapter.getAddressAndType().address) ) {
+ BTUtils.fprintf_td(System.err, "****** Start advertising (%s): Adapter not selected: %s\n", msg, adapter.toString());
+ return HCIStatusCode.FAILED;
+ }
+ final EInfoReport eir = new EInfoReport();
+ final EIRDataTypeSet adv_mask = new EIRDataTypeSet();
+ final EIRDataTypeSet scanrsp_mask = new EIRDataTypeSet();
+
+ adv_mask.set(EIRDataTypeSet.DataType.FLAGS);
+ adv_mask.set(EIRDataTypeSet.DataType.SERVICE_UUID);
+
+ scanrsp_mask.set(EIRDataTypeSet.DataType.NAME);
+ scanrsp_mask.set(EIRDataTypeSet.DataType.CONN_IVAL);
+
+ eir.addFlag(GAPFlags.Bit.LE_Gen_Disc);
+ eir.addFlag(GAPFlags.Bit.BREDR_UNSUP);
+
+ eir.addService(DataServiceUUID);
+ eir.setServicesComplete(false);
+
+ eir.setName(adapter.getName());
+ eir.setConnInterval((short)10, (short)24);
+
+ final DBGattChar gattDevNameChar = dbGattServer.findGattChar(DBGattService.UUID16.GENERIC_ACCESS, DBGattChar.UUID16.DEVICE_NAME);
+ if( null != gattDevNameChar ) {
+ final byte[] aname_bytes = adapter.getName().getBytes(StandardCharsets.UTF_8);
+ gattDevNameChar.setValue(aname_bytes, 0, aname_bytes.length, 0);
+ }
+
+ BTUtils.println(System.err, "****** Start advertising ("+msg+"): EIR "+eir.toString());
+ BTUtils.println(System.err, "****** Start advertising ("+msg+"): adv "+adv_mask.toString()+", scanrsp "+scanrsp_mask.toString());
+
+ final HCIStatusCode status = adapter.startAdvertising(dbGattServer, eir, adv_mask, scanrsp_mask,
+ adv_interval_min, adv_interval_max,
+ adv_type, adv_chan_map, filter_policy);
+ BTUtils.println(System.err, "****** Start advertising ("+msg+") result: "+status+": "+adapter.toString());
+ if( GATT_VERBOSE ) {
+ BTUtils.println(System.err, dbGattServer.toFullString());
+ }
+ return status;
+ }
+
+ private void processDisconnectedDevice(final BTDevice device) {
+ BTUtils.println(System.err, "****** Disconnected Device (count "+(servedConnections.get()+1)+"): Start "+device.toString());
+
+ // already unpaired
+ BTDeviceRegistry.removeFromProcessingDevices(device.getAddressAndType());
+ try {
+ Thread.sleep(100); // wait a little (FIXME: Fast restart of advertising error)
+ } catch (final InterruptedException e) { }
+
+ BTUtils.println(System.err, "****** Disonnected Device: End "+device.toString());
+ servedConnections.addAndGet(1);
+ }
+
+ public boolean initAdapter(final BTAdapter adapter) {
+ if( !useAdapter.equals(EUI48.ALL_DEVICE) && !useAdapter.equals(adapter.getAddressAndType().address) ) {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: Adapter not selected: %s\n", adapter.toString());
+ return false;
+ }
+ if( !adapter.isInitialized() ) {
+ // Initialize with defaults and power-on
+ final HCIStatusCode status = adapter.initialize( btMode );
+ if( HCIStatusCode.SUCCESS != status ) {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: initialization failed: %s: %s\n",
+ status.toString(), adapter.toString());
+ return false;
+ }
+ } else if( !adapter.setPowered( true ) ) {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: setPower.1 on failed: %s\n", adapter.toString());
+ return false;
+ }
+ // adapter is powered-on
+ BTUtils.println(System.err, "initServerAdapter.1: "+adapter.toString());
+
+ if( adapter.setPowered(false) ) {
+ HCIStatusCode status = adapter.setName(adapterName, adapterShortName);
+ if( HCIStatusCode.SUCCESS == status ) {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: setLocalName OK: %s\n", adapter.toString());
+ } else {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: setLocalName failed: %s\n", adapter.toString());
+ return false;
+ }
+
+ status = adapter.setSecureConnections( use_SC );
+ if( HCIStatusCode.SUCCESS == status ) {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: setSecureConnections OK: %s\n", adapter.toString());
+ } else {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: setSecureConnections failed: %s\n", adapter.toString());
+ return false;
+ }
+
+ final short conn_min_interval = 8; // 10ms
+ final short conn_max_interval = 40; // 50ms
+ final short conn_latency = 0;
+ final short supervision_timeout = 50; // 500ms
+ status = adapter.setDefaultConnParam(conn_min_interval, conn_max_interval, conn_latency, supervision_timeout);
+ if( HCIStatusCode.SUCCESS == status ) {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: setDefaultConnParam OK: %s\n", adapter.toString());
+ } else {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: setDefaultConnParam failed: %s\n", adapter.toString());
+ return false;
+ }
+
+ if( !adapter.setPowered( true ) ) {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: setPower.2 on failed: %s\n", adapter.toString());
+ return false;
+ }
+ } else {
+ BTUtils.fprintf_td(System.err, "initServerAdapter: setPowered.2 off failed: %s\n", adapter.toString());
+ }
+ BTUtils.println(System.err, "initServerAdapter.2: "+adapter.toString());
+
+ {
+ final LE_Features le_feats = adapter.getLEFeatures();
+ BTUtils.fprintf_td(System.err, "initServerAdapter: LE_Features %s\n", le_feats.toString());
+ }
+ {
+ final LE_PHYs Tx = new LE_PHYs( LE_PHYs.PHY.LE_2M );
+ final LE_PHYs Rx = new LE_PHYs( LE_PHYs.PHY.LE_2M );
+ final HCIStatusCode res = adapter.setDefaultLE_PHY(Tx, Rx);
+ BTUtils.fprintf_td(System.err, "initServerAdapter: Set Default LE PHY: status %s: Tx %s, Rx %s\n",
+ res.toString(), Tx.toString(), Rx.toString());
+ }
+ adapter.setSMPKeyPath(DBTConstants.SERVER_KEY_PATH);
+
+ // adapter is powered-on
+ final AdapterStatusListener asl = new MyAdapterStatusListener();
+ adapter.addStatusListener( asl );
+ // Flush discovered devices after registering our status listener.
+ // This avoids discovered devices before we have registered!
+ adapter.removeDiscoveredDevices();
+
+ adapter.setServerConnSecurity(adapterSecurityLevel, SMPIOCapability.UNSET);
+
+ return true;
+ }
+}
diff --git a/trial/java/trial/org/direct_bt/DBTUtils.java b/trial/java/trial/org/direct_bt/DBTUtils.java
new file mode 100644
index 00000000..586bc2ea
--- /dev/null
+++ b/trial/java/trial/org/direct_bt/DBTUtils.java
@@ -0,0 +1,177 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package trial.org.direct_bt;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.direct_bt.BTFactory;
+import org.direct_bt.BTUtils;
+import org.direct_bt.DirectBTVersion;
+import org.jau.util.VersionUtil;
+
+public class DBTUtils {
+ public static final void printVersionInfo() {
+ BTFactory.initDirectBTLibrary();
+
+ BTUtils.println(System.err, "BTFactory: Jaulib: Available "+BTFactory.JAULIB_AVAILABLE+", JarCache in use "+BTFactory.JAULIB_JARCACHE_USED);
+ if( BTFactory.JAULIB_AVAILABLE ) {
+ System.err.println(VersionUtil.getPlatformInfo());
+ BTUtils.println(System.err, "Version Info:");
+ final DirectBTVersion v = DirectBTVersion.getInstance();
+ System.err.println(v.toString());
+ BTUtils.println(System.err, "");
+ BTUtils.println(System.err, "Full Manifest:");
+ System.err.println(v.getFullManifestInfo(null).toString());
+ } else {
+ BTUtils.println(System.err, "Full Manifest:");
+ final Manifest manifest = BTFactory.getManifest(BTFactory.class.getClassLoader(), new String[] { "org.direct_bt" } );
+ final Attributes attr = manifest.getMainAttributes();
+ final Set<Object> keys = attr.keySet();
+ final StringBuilder sb = new StringBuilder();
+ for(final Iterator<Object> iter=keys.iterator(); iter.hasNext(); ) {
+ final Attributes.Name key = (Attributes.Name) iter.next();
+ final String val = attr.getValue(key);
+ sb.append(" ");
+ sb.append(key);
+ sb.append(" = ");
+ sb.append(val);
+ sb.append(System.lineSeparator());
+ }
+ System.err.println(sb.toString());
+ }
+
+ BTUtils.println(System.err, "DirectBT Native Version "+BTFactory.getNativeVersion()+" (API "+BTFactory.getNativeAPIVersion()+")");
+ BTUtils.println(System.err, "DirectBT Java Version "+BTFactory.getImplVersion()+" (API "+BTFactory.getAPIVersion()+")");
+ }
+
+ public static final boolean mkdirKeyFolder() {
+ BTFactory.initDirectBTLibrary();
+ boolean res = true;
+ {
+ final File file = new File(DBTConstants.CLIENT_KEY_PATH);
+ try {
+ if( !file.isDirectory() ) {
+ final boolean res2 = file.mkdirs();
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': mkdir: "+res2);
+ res = res && res2;
+ } else {
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': already exists");
+ }
+ } catch(final Throwable t) {
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': Caught "+t.getMessage());
+ res = false;
+ }
+ }
+ if( res ) {
+ final File file = new File(DBTConstants.SERVER_KEY_PATH);
+ try {
+ if( !file.isDirectory() ) {
+ final boolean res2 = file.mkdirs();
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': mkdir: "+res2);
+ res = res && res2;
+ } else {
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': already exists");
+ }
+ } catch(final Throwable t) {
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': Caught "+t.getMessage());
+ res = false;
+ }
+ }
+ return res;
+ }
+
+ /**
+ *
+ * @param file
+ * @param recursive
+ * @return true only if the file or the directory with content has been deleted, otherwise false
+ */
+ private static boolean delete(final File file, final boolean recursive) {
+ boolean rm_parent = true;
+ final File[] contents = file.listFiles();
+ if (contents != null) {
+ for (final File f : contents) {
+ if( f.isDirectory() && !Files.isSymbolicLink( f.toPath() ) ) {
+ if( recursive ) {
+ rm_parent = delete(f, true) && rm_parent;
+ } else {
+ // can't empty contents -> can't rm 'file'
+ rm_parent = false;
+ }
+ } else {
+ try {
+ rm_parent = f.delete() && rm_parent;
+ } catch( final Exception e ) {
+ e.printStackTrace();
+ rm_parent = false;
+ }
+ }
+ }
+ }
+ if( rm_parent ) {
+ try {
+ return file.delete();
+ } catch( final Exception e ) { e.printStackTrace(); }
+ }
+ return false;
+ }
+
+ public static final boolean rmKeyFolder() {
+ BTFactory.initDirectBTLibrary();
+ boolean res = true;
+ {
+ final File file = new File(DBTConstants.CLIENT_KEY_PATH);
+ try {
+ if( file.isDirectory() ) {
+ final boolean res2 = delete(file, false /* recursive */);
+ res = res2 && res;
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': delete: "+res2);
+ }
+ } catch(final Throwable t) {
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': Caught "+t.getMessage());
+ res = false;
+ }
+ }
+ if( res ) {
+ final File file = new File(DBTConstants.SERVER_KEY_PATH);
+ try {
+ if( file.isDirectory() ) {
+ final boolean res2 = delete(file, false /* recursive */);
+ res = res2 && res;
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': delete: "+res2);
+ }
+ } catch(final Throwable t) {
+ BTUtils.println(System.err, "****** KEY_PATH '"+file.toString()+"': Caught "+t.getMessage());
+ res = false;
+ }
+ }
+ return res;
+ }
+}
diff --git a/trial/java/trial/org/direct_bt/TestDBTClientServer00.java b/trial/java/trial/org/direct_bt/TestDBTClientServer00.java
new file mode 100644
index 00000000..161a65a5
--- /dev/null
+++ b/trial/java/trial/org/direct_bt/TestDBTClientServer00.java
@@ -0,0 +1,217 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package trial.org.direct_bt;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+import org.direct_bt.BTMode;
+import org.direct_bt.BTRole;
+import org.direct_bt.BTSecurityLevel;
+import org.direct_bt.BTAdapter;
+import org.direct_bt.BTException;
+import org.direct_bt.BTFactory;
+import org.direct_bt.BTManager;
+import org.direct_bt.BTUtils;
+import org.direct_bt.HCIStatusCode;
+import org.jau.net.EUI48;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * Basic client and server Bluetooth tests, requiring one BT adapter.
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class TestDBTClientServer00 extends BaseDBTClientServer {
+ static final boolean DEBUG = false;
+
+ @BeforeClass
+ public static final void setupAllLocal() {
+ BTFactory.initDirectBTLibrary();
+
+ final Class<?> ThisClazz = MethodHandles.lookup().lookupClass();
+ BTUtils.println(System.err, "++++ Test "+ThisClazz.getSimpleName()+".setupAllLocal()");
+
+ DBTUtils.printVersionInfo();
+ }
+
+ /**
+ * Testing BTManager bring up and
+ * - test that at least two adapter are present
+ * - validating basic default adapter status
+ */
+ @Test(timeout = 5000)
+ public final void test01_ManagerBringup() {
+ BTManager manager = null;
+ try {
+ manager = BTFactory.getDirectBTManager();
+ } catch (BTException | NoSuchMethodException | SecurityException
+ | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | ClassNotFoundException e) {
+ e.printStackTrace();
+ Assert.assertNull("Unable to instantiate DirectBT BluetoothManager: "+e.getMessage(), e);
+ }
+ if( null == manager ) {
+ return;
+ }
+ final List<BTAdapter> adapters = manager.getAdapters();
+ BTUtils.println(System.err, "Adapter: Count "+adapters.size()+": "+adapters.toString());
+ Assert.assertTrue("Adapter count not >= 1 but "+adapters.size(), adapters.size() >= 1);
+
+ for(final BTAdapter a : adapters) {
+ Assert.assertFalse( a.isInitialized() );
+ Assert.assertFalse( a.isPowered() );
+ Assert.assertEquals( BTRole.Master, a.getRole() ); // default role
+ Assert.assertTrue( 4 <= a.getBTMajorVersion() );
+ }
+ }
+
+ /**
+ * Testing start and stop advertising (server mode) using a full DBGattServer,
+ * having the adapter in BTRole::Slave.
+ *
+ * Thereafter start and stop discovery (client mode),
+ * having the adapter in BTRole::Client.
+ */
+ @Test(timeout = 5000)
+ public final void test10_ServerStartStop_and_ToggleDiscovery() {
+ BTManager manager = null;
+ try {
+ manager = BTFactory.getDirectBTManager();
+ } catch (BTException | NoSuchMethodException | SecurityException
+ | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | ClassNotFoundException e) {
+ e.printStackTrace();
+ Assert.assertNull("Unable to instantiate DirectBT BluetoothManager: "+e.getMessage(), e);
+ }
+ if( null == manager ) {
+ return;
+ }
+
+ final String serverName = "TestDBTCS00-T10";
+ final DBTServer00 server = new DBTServer00(EUI48.ALL_DEVICE, BTMode.DUAL, true /* SC */, serverName, BTSecurityLevel.NONE);
+
+ final BTManager.ChangedAdapterSetListener myChangedAdapterSetListener =
+ new BTManager.ChangedAdapterSetListener() {
+ @Override
+ public void adapterAdded(final BTAdapter adapter) {
+ if( null == server.getAdapter() ) {
+ if( server.initAdapter( adapter ) ) {
+ server.setAdapter(adapter);
+ BTUtils.println(System.err, "****** Adapter-Server ADDED__: InitOK: " + adapter);
+ return;
+ }
+ }
+ BTUtils.println(System.err, "****** Adapter ADDED__: Ignored: " + adapter);
+ }
+
+ @Override
+ public void adapterRemoved(final BTAdapter adapter) {
+ if( null != server.getAdapter() && adapter == server.getAdapter() ) {
+ server.setAdapter(null);
+ BTUtils.println(System.err, "****** Adapter-Server REMOVED: " + adapter);
+ return;
+ }
+ BTUtils.println(System.err, "****** Adapter REMOVED: Ignored " + adapter);
+ }
+ };
+
+ manager.addChangedAdapterSetListener(myChangedAdapterSetListener);
+ Assert.assertNotNull("No server adapter found", server.getAdapter());
+
+ //
+ // Server start
+ //
+ Assert.assertTrue( server.getAdapter().isInitialized() );
+ Assert.assertTrue( server.getAdapter().isPowered() );
+ Assert.assertEquals( BTRole.Master, server.getAdapter().getRole() );
+ Assert.assertTrue( 4 <= server.getAdapter().getBTMajorVersion() );
+ {
+ Assert.assertFalse(server.getAdapter().isAdvertising());
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+
+ Assert.assertEquals( HCIStatusCode.SUCCESS, server.startAdvertising(server.getAdapter(), "test10_startAdvertising") );
+ Assert.assertTrue(server.getAdapter().isAdvertising());
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+ Assert.assertEquals( BTRole.Slave, server.getAdapter().getRole() );
+ Assert.assertEquals( serverName, server.getAdapter().getName() );
+ }
+
+ //
+ // Server stop
+ //
+ {
+ Assert.assertTrue(server.getAdapter().isAdvertising());
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+
+ Assert.assertEquals( HCIStatusCode.SUCCESS, server.stopAdvertising(server.getAdapter(), "test10_stopAdvertising") );
+ Assert.assertFalse(server.getAdapter().isAdvertising());
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+ Assert.assertEquals( BTRole.Slave, server.getAdapter().getRole() ); // keeps role
+ }
+
+ //
+ // Now reuse adapter for client mode -> Start discovery + Stop Discovery
+ //
+ {
+ Assert.assertFalse(server.getAdapter().isAdvertising());
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+
+ final BTAdapter adapter = server.getAdapter();
+ {
+ final int r = adapter.removeAllStatusListener();
+ Assert.assertTrue("Not > 0 removed listener, but "+r, 0 < r );
+ }
+
+ Assert.assertEquals( HCIStatusCode.SUCCESS, adapter.startDiscovery() ); // pending action
+ while( !adapter.isDiscovering() ) {
+ try { Thread.sleep(100); } catch (final InterruptedException e) { e.printStackTrace(); }
+ }
+ Assert.assertFalse(server.getAdapter().isAdvertising());
+ Assert.assertTrue(server.getAdapter().isDiscovering());
+ Assert.assertEquals( BTRole.Master, server.getAdapter().getRole() ); // changed role
+
+ Assert.assertEquals( HCIStatusCode.SUCCESS, adapter.stopDiscovery() ); // pending action
+ while( adapter.isDiscovering() ) {
+ try { Thread.sleep(100); } catch (final InterruptedException e) { e.printStackTrace(); }
+ }
+ Assert.assertFalse(server.getAdapter().isAdvertising());
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+ Assert.assertEquals( BTRole.Master, server.getAdapter().getRole() ); // keeps role
+
+ }
+
+ final int count = manager.removeChangedAdapterSetListener(myChangedAdapterSetListener);
+ BTUtils.println(System.err, "****** EOL Removed ChangedAdapterSetCallback " + count);
+ }
+
+ public static void main(final String args[]) {
+ org.junit.runner.JUnitCore.main(TestDBTClientServer00.class.getName());
+ }
+}
diff --git a/trial/java/trial/org/direct_bt/TestDBTClientServer10.java b/trial/java/trial/org/direct_bt/TestDBTClientServer10.java
new file mode 100644
index 00000000..295c918a
--- /dev/null
+++ b/trial/java/trial/org/direct_bt/TestDBTClientServer10.java
@@ -0,0 +1,259 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package trial.org.direct_bt;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+import org.direct_bt.BTMode;
+import org.direct_bt.BTRole;
+import org.direct_bt.BTSecurityLevel;
+import org.direct_bt.AdapterStatusListener;
+import org.direct_bt.BTAdapter;
+import org.direct_bt.BTDevice;
+import org.direct_bt.BTDeviceRegistry;
+import org.direct_bt.BTException;
+import org.direct_bt.BTFactory;
+import org.direct_bt.BTManager;
+import org.direct_bt.BTSecurityRegistry;
+import org.direct_bt.BTUtils;
+import org.direct_bt.DiscoveryPolicy;
+import org.direct_bt.HCIStatusCode;
+import org.direct_bt.PairingMode;
+import org.direct_bt.SMPKeyBin;
+import org.jau.net.EUI48;
+import org.junit.Assert;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * Testing a full Bluetooth server and client lifecycle of operations, requiring two BT adapter:
+ * - start server advertising
+ * - start client discovery and connect to server when discovered
+ * - client/server processing of connection when ready
+ * - client disconnect
+ * - server stop advertising
+ * - security-level: NONE, ENC_ONLY freshly-paired and ENC_ONLY pre-paired
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class TestDBTClientServer10 extends BaseDBTClientServer {
+ static final boolean DEBUG = false;
+
+ @Test(timeout = 20000)
+ public final void test00_FullCycle_EncNone() {
+ test8x_fullCycle("00", BTSecurityLevel.NONE, false /* serverShallHaveKeys */, BTSecurityLevel.NONE, false /* clientShallHaveKeys */);
+ }
+
+ @Test(timeout = 20000)
+ public final void test01_FullCycle_EncOnlyNo1() {
+ test8x_fullCycle("01", BTSecurityLevel.ENC_ONLY, false /* serverShallHaveKeys */, BTSecurityLevel.ENC_ONLY, false /* clientShallHaveKeys */);
+ }
+
+ @Test(timeout = 30000)
+ public final void test02_FullCycle_EncOnlyNo2() {
+ test8x_fullCycle("02", BTSecurityLevel.ENC_ONLY, true /* serverShallHaveKeys */, BTSecurityLevel.ENC_ONLY, true /* clientShallHaveKeys */);
+ }
+
+ volatile BTDevice lastCompletedDevice = null;
+ volatile PairingMode lastCompletedDevicePairingMode = PairingMode.NONE;
+ volatile BTSecurityLevel lastCompletedDeviceSecurityLevel = BTSecurityLevel.NONE;
+
+ final void test8x_fullCycle(final String suffix,
+ final BTSecurityLevel secLevelServer, final boolean serverShallHaveKeys,
+ final BTSecurityLevel secLevelClient, final boolean clientShallHaveKeys)
+ {
+ BTManager manager = null;
+ try {
+ manager = BTFactory.getDirectBTManager();
+ } catch (BTException | NoSuchMethodException | SecurityException
+ | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | ClassNotFoundException e) {
+ e.printStackTrace();
+ Assert.assertNull("Unable to instantiate DirectBT BluetoothManager: "+e.getMessage(), e);
+ }
+ if( null == manager ) {
+ return;
+ }
+
+ final List<BTAdapter> adapters = manager.getAdapters();
+ BTUtils.println(System.err, "Adapter: Count "+adapters.size()+": "+adapters.toString());
+ Assert.assertTrue("Adapter count not >= 2 but "+adapters.size(), adapters.size() >= 2);
+
+ final String serverName = "TestDBTCS00-T"+suffix;
+ final DBTServer00 server = new DBTServer00(EUI48.ALL_DEVICE, BTMode.DUAL, true /* SC */, serverName, secLevelServer);
+
+ final DBTClient00 client = new DBTClient00(EUI48.ALL_DEVICE, BTMode.DUAL);
+ BTDeviceRegistry.addToWaitForDevices( serverName );
+ {
+ final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getOrCreate(serverName);
+ sec.sec_level = secLevelClient;
+ }
+ client.KEEP_CONNECTED = false; // default
+ client.REMOVE_DEVICE = false; // default and test side-effects
+ client.measurementsLeft.set(1);
+ client.discoveryPolicy = DiscoveryPolicy.PAUSE_CONNECTED_UNTIL_DISCONNECTED;
+
+ final BTManager.ChangedAdapterSetListener myChangedAdapterSetListener =
+ new BTManager.ChangedAdapterSetListener() {
+ @Override
+ public void adapterAdded(final BTAdapter adapter) {
+ if( null == server.getAdapter() ) {
+ if( server.initAdapter( adapter ) ) {
+ server.setAdapter(adapter);
+ BTUtils.println(System.err, "****** Adapter-Server ADDED__: InitOK: " + adapter);
+ return;
+ }
+ }
+ if( null == client.getAdapter() ) {
+ if( client.initAdapter( adapter ) ) {
+ client.setAdapter(adapter);
+ BTUtils.println(System.err, "****** Adapter-Client ADDED__: InitOK: " + adapter);
+ return;
+ }
+ }
+ BTUtils.println(System.err, "****** Adapter ADDED__: Ignored: " + adapter);
+ }
+
+ @Override
+ public void adapterRemoved(final BTAdapter adapter) {
+ if( null != server.getAdapter() && adapter == server.getAdapter() ) {
+ server.setAdapter(null);
+ BTUtils.println(System.err, "****** Adapter-Server REMOVED: " + adapter);
+ return;
+ }
+ if( null != client.getAdapter() && adapter == client.getAdapter() ) {
+ client.setAdapter(null);
+ BTUtils.println(System.err, "****** Adapter-Client REMOVED: " + adapter);
+ return;
+ }
+ BTUtils.println(System.err, "****** Adapter REMOVED: Ignored " + adapter);
+ }
+ };
+
+ manager.addChangedAdapterSetListener(myChangedAdapterSetListener);
+ Assert.assertNotNull("No server adapter found", server.getAdapter());
+ Assert.assertNotNull("No client adapter found", client.getAdapter());
+
+ lastCompletedDevice = null;
+ lastCompletedDevicePairingMode = PairingMode.NONE;
+ lastCompletedDeviceSecurityLevel = BTSecurityLevel.NONE;
+ final AdapterStatusListener clientAdapterStatusListener = new AdapterStatusListener() {
+ @Override
+ public void deviceReady(final BTDevice device, final long timestamp) {
+ lastCompletedDevice = device;
+ lastCompletedDevicePairingMode = device.getPairingMode();
+ lastCompletedDeviceSecurityLevel = device.getConnSecurityLevel();
+ BTUtils.println(System.err, "XXXXXX Client Ready: "+device);
+ }
+ };
+ Assert.assertTrue( client.getAdapter().addStatusListener(clientAdapterStatusListener) );
+
+ //
+ // Server start
+ //
+ Assert.assertTrue( server.getAdapter().isInitialized() );
+ Assert.assertTrue( server.getAdapter().isPowered() );
+ Assert.assertEquals( BTRole.Master, server.getAdapter().getRole() );
+ Assert.assertTrue( 4 <= server.getAdapter().getBTMajorVersion() );
+ {
+ Assert.assertFalse(server.getAdapter().isAdvertising());
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+
+ Assert.assertEquals( HCIStatusCode.SUCCESS, server.startAdvertising(server.getAdapter(), "test"+suffix+"_startAdvertising") );
+ Assert.assertTrue(server.getAdapter().isAdvertising());
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+ Assert.assertEquals( BTRole.Slave, server.getAdapter().getRole() );
+ Assert.assertEquals( serverName, server.getAdapter().getName() );
+ }
+
+ //
+ // Client start
+ //
+ Assert.assertTrue( client.getAdapter().isInitialized() );
+ Assert.assertTrue( client.getAdapter().isPowered() );
+ Assert.assertEquals( BTRole.Master, client.getAdapter().getRole() );
+ Assert.assertTrue( 4 <= client.getAdapter().getBTMajorVersion() );
+ {
+ Assert.assertFalse(client.getAdapter().isAdvertising());
+ Assert.assertFalse(client.getAdapter().isDiscovering());
+
+ Assert.assertEquals( HCIStatusCode.SUCCESS, client.startDiscovery(client.getAdapter(), "test"+suffix+"_startDiscovery") );
+ Assert.assertFalse(client.getAdapter().isAdvertising());
+ Assert.assertTrue(client.getAdapter().isDiscovering());
+ Assert.assertEquals( BTRole.Master, client.getAdapter().getRole() );
+ }
+
+ while( 1 > server.servedConnections.get() ||
+ 1 > client.completedMeasurements.get() ||
+ null == lastCompletedDevice )
+ {
+ try { Thread.sleep(500); } catch (final InterruptedException e) { e.printStackTrace(); }
+ }
+ Assert.assertEquals(1, server.servedConnections.get());
+ Assert.assertEquals(1, client.completedMeasurements.get());
+ Assert.assertNotNull(lastCompletedDevice);
+ Assert.assertEquals( HCIStatusCode.SUCCESS, client.stopDiscovery(client.getAdapter(), "test"+suffix+"_stopDiscovery") );
+
+ {
+ Assert.assertFalse(server.getAdapter().isAdvertising()); // stopped by connection
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+
+ // Stopping advertising wven if stopped must be OK!
+ Assert.assertEquals( HCIStatusCode.SUCCESS, server.stopAdvertising(server.getAdapter(), "test"+suffix+"_stopAdvertising") );
+ Assert.assertFalse(server.getAdapter().isAdvertising());
+ Assert.assertFalse(server.getAdapter().isDiscovering());
+ Assert.assertEquals( BTRole.Slave, server.getAdapter().getRole() ); // kept
+ }
+
+ final SMPKeyBin clientKeys = SMPKeyBin.read(DBTConstants.CLIENT_KEY_PATH, lastCompletedDevice, true /* verbose */);
+ Assert.assertTrue(clientKeys.isValid());
+ final BTSecurityLevel clientKeysSecLevel = clientKeys.getSecLevel();
+ Assert.assertEquals(secLevelClient, clientKeysSecLevel);
+ {
+ if( clientShallHaveKeys ) {
+ // Using encryption: pre-paired
+ Assert.assertEquals(PairingMode.PRE_PAIRED, lastCompletedDevicePairingMode);
+ Assert.assertEquals(BTSecurityLevel.ENC_ONLY, lastCompletedDeviceSecurityLevel); // pre-paired fixed level, no auth
+ } else if( BTSecurityLevel.NONE.value < secLevelClient.value ) {
+ // Using encryption: Newly paired
+ Assert.assertNotEquals(PairingMode.PRE_PAIRED, lastCompletedDevicePairingMode);
+ Assert.assertTrue("PairingMode client "+lastCompletedDevicePairingMode+" not > NONE", PairingMode.NONE.value < lastCompletedDevicePairingMode.value);
+ Assert.assertTrue("SecurityLevel client "+lastCompletedDeviceSecurityLevel+" not >= "+secLevelClient, secLevelClient.value <= lastCompletedDeviceSecurityLevel.value);
+ } else {
+ // No encryption: No pairing
+ Assert.assertEquals(PairingMode.NONE, lastCompletedDevicePairingMode);
+ Assert.assertEquals(BTSecurityLevel.NONE, lastCompletedDeviceSecurityLevel);
+ }
+ }
+
+ final int count = manager.removeChangedAdapterSetListener(myChangedAdapterSetListener);
+ BTUtils.println(System.err, "****** EOL Removed ChangedAdapterSetCallback " + count);
+ }
+
+ public static void main(final String args[]) {
+ org.junit.runner.JUnitCore.main(TestDBTClientServer10.class.getName());
+ }
+}
diff --git a/trial/java/trial/org/direct_bt/VersionInfo.java b/trial/java/trial/org/direct_bt/VersionInfo.java
new file mode 100644
index 00000000..c5b0c69e
--- /dev/null
+++ b/trial/java/trial/org/direct_bt/VersionInfo.java
@@ -0,0 +1,14 @@
+package trial.org.direct_bt;
+
+import java.io.IOException;
+
+import org.direct_bt.BTFactory;
+import org.direct_bt.DirectBTVersion;
+import org.jau.util.VersionUtil;
+
+public class VersionInfo {
+ public static void main(final String args[]) throws IOException {
+ BTFactory.main(args);
+ }
+
+}