Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
DBTClientServer1x.java
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2022 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25package trial.org.direct_bt;
26
27import java.lang.reflect.InvocationTargetException;
28import java.util.Arrays;
29import java.util.List;
30
31import org.direct_bt.BTMode;
32import org.direct_bt.BTSecurityLevel;
33import org.direct_bt.AdapterSettings;
34import org.direct_bt.AdapterStatusListener;
35import org.direct_bt.BTAdapter;
36import org.direct_bt.BTDevice;
37import org.direct_bt.BTDeviceRegistry;
38import org.direct_bt.BTException;
39import org.direct_bt.BTFactory;
40import org.direct_bt.BTManager;
41import org.direct_bt.BTSecurityRegistry;
42import org.direct_bt.DiscoveryPolicy;
43import org.direct_bt.EIRDataTypeSet;
44import org.direct_bt.EInfoReport;
45import org.direct_bt.HCIStatusCode;
46import org.direct_bt.PairingMode;
47import org.direct_bt.SMPKeyBin;
48import org.jau.io.PrintUtil;
49import org.jau.net.EUI48;
50import org.jau.sys.Clock;
51import org.junit.Assert;
52
53/**
54 * Testing a full Bluetooth server and client lifecycle of operations, requiring two BT adapter:
55 * - start server advertising
56 * - start client discovery and connect to server when discovered
57 * - client/server processing of connection when ready
58 * - client disconnect
59 * - server stop advertising
60 * - security-level: NONE, ENC_ONLY freshly-paired and ENC_ONLY pre-paired
61 * - reuse server-adapter for client-mode discovery (just toggle on/off)
62 */
63public abstract class DBTClientServer1x extends BaseDBTClientServer {
64 // timeout check: timeout_value < test_duration + timeout_preempt_diff; // let's timeout here before our timeout timer
65 static final long timeout_preempt_diff = 500;
66
67 final Object mtx_sync = new Object();
68 BTDevice lastCompletedDevice = null;
69 PairingMode lastCompletedDevicePairingMode = PairingMode.NONE;
70 BTSecurityLevel lastCompletedDeviceSecurityLevel = BTSecurityLevel.NONE;
71 EInfoReport lastCompletedDeviceEIR = null;
72 int client_power_down_count = 0;
73 int client_power_up_count = 0;
74 boolean client_reset_at_ready = false;
75 boolean server_reset_at_ready = false;
76 boolean client_reset_test = false;
77 boolean server_reset_test = false;
78
79 final void set_client_reset_at_ready(final boolean v) { client_reset_at_ready = v; client_reset_test = v; }
80 final void set_server_reset_at_ready(final boolean v) { server_reset_at_ready = v; server_reset_test = v; }
81
82 final void test8x_fullCycle(final long timeout_value,
83 final String suffix, final int protocolSessionCount, final boolean server_client_order,
84 final boolean serverSC, final BTSecurityLevel secLevelServer, final ExpectedPairing serverExpPairing,
85 final BTSecurityLevel secLevelClient, final ExpectedPairing clientExpPairing)
86 {
87 final DBTServer01 server = new DBTServer01("S-"+suffix, EUI48.ALL_DEVICE, BTMode.DUAL, serverSC, secLevelServer);
88 final DBTClient01 client = new DBTClient01("C-"+suffix, EUI48.ALL_DEVICE, BTMode.DUAL);
89
90 server.setProtocolSessionsLeft( protocolSessionCount );
91
92 client.setProtocolSessionsLeft( protocolSessionCount );
93 client.setDisconnectDeviceed( true ); // default, auto-disconnect after work is done
94 client.setRemoveDevice( false ); // default, test side-effects
96
97 test8x_fullCycle(timeout_value, suffix,
98 DBTConstants.max_connections_per_session, true /* expSuccess */, server_client_order,
99 server,
100 secLevelServer, serverExpPairing, client,
101 secLevelClient, clientExpPairing);
102 }
103
104 final void test8x_fullCycle(final long timeout_value, final String suffix,
105 final int max_connections_per_session, final boolean expSuccess, final boolean server_client_order,
106 final DBTServerTest server,
107 final BTSecurityLevel secLevelServer, final ExpectedPairing serverExpPairing, final DBTClientTest client,
108 final BTSecurityLevel secLevelClient, final ExpectedPairing clientExpPairing)
109 {
110 final int protocolSessionCount = Math.min(server.getProtocolSessionsLeft(), client.getProtocolSessionsLeft());
111 final long t0 = Clock.currentTimeMillis();
112
113 BTManager manager = null;
114 try {
115 manager = BTFactory.getDirectBTManager();
116 } catch (BTException | NoSuchMethodException | SecurityException
117 | IllegalAccessException | IllegalArgumentException
118 | InvocationTargetException | ClassNotFoundException e) {
119 e.printStackTrace();
120 Assert.assertNull("Unable to instantiate Direct-BT BluetoothManager: "+e.getMessage(), e);
121 }
122 if( null == manager ) {
123 return;
124 }
125 {
126 final List<BTAdapter> adapters = manager.getAdapters();
127 PrintUtil.println(System.err, "Adapter: Count "+adapters.size()+": "+adapters.toString());
128 Assert.assertTrue("Adapter count not >= 2 but "+adapters.size(), adapters.size() >= 2);
129 }
130
131 final DBTEndpoint.ChangedAdapterSetListener myChangedAdapterSetListener =
132 DBTEndpoint.initChangedAdapterSetListener(manager, server_client_order ? Arrays.asList(server, client) : Arrays.asList(client, server));
133
134 final String serverName = server.getName();
136 {
137 final BTSecurityRegistry.Entry sec = BTSecurityRegistry.getOrCreate(serverName);
138 sec.sec_level = secLevelClient;
139 }
140
141 synchronized( mtx_sync ) {
142 lastCompletedDevice = null;
143 lastCompletedDevicePairingMode = PairingMode.NONE;
144 lastCompletedDeviceSecurityLevel = BTSecurityLevel.NONE;
145 lastCompletedDeviceEIR = null;
146 }
147 final AdapterStatusListener clientAdapterStatusListener = new AdapterStatusListener() {
148 @Override
149 public void adapterSettingsChanged(final BTAdapter adapter, final AdapterSettings oldmask,
150 final AdapterSettings newmask, final AdapterSettings changedmask, final long timestamp) {
151 final boolean initialSetting = oldmask.isEmpty();
152 if( !initialSetting && changedmask.isSet(AdapterSettings.SettingType.POWERED) ) {
153 synchronized( mtx_sync ) {
155 ++client_power_up_count;
156 } else {
157 ++client_power_down_count;
158 }
159 }
160 }
161 }
162
163 @Override
164 public void deviceReady(final BTDevice device, final long timestamp) {
165 synchronized( mtx_sync ) {
166 lastCompletedDevice = device;
167 lastCompletedDevicePairingMode = device.getPairingMode();
168 lastCompletedDeviceSecurityLevel = device.getConnSecurityLevel();
169 lastCompletedDeviceEIR = device.getEIR().clone();
170 PrintUtil.println(System.err, "XXXXXX Client Ready: "+device);
171 if( client_reset_at_ready ) {
172 client_reset_at_ready = false;
173 PrintUtil.fprintf_td(System.err, "XXXXXX Client Reset.0: %s\n", device.toString());
174 final HCIStatusCode rr = device.getAdapter().reset();
175 PrintUtil.fprintf_td(System.err, "XXXXXX Client Reset.X: %s: %s\n", rr.toString(), device.toString());
176 }
177 }
178 }
179 };
180 Assert.assertTrue( client.getAdapter().addStatusListener(clientAdapterStatusListener) );
181
182 //
183 // Server start
184 //
186 DBTServerTest.startAdvertising(server, false /* current_exp_advertising_state */, "test"+suffix+"_startAdvertising");
187
188 //
189 // Client start
190 //
192 DBTClientTest.startDiscovery(client, false /* current_exp_discovering_state */, "test"+suffix+"_startDiscovery");
193
194 long test_duration = 0;
195 boolean done = false;
196 boolean timeout = false;
197 boolean max_connections_hit = false;
198 do {
199 synchronized( mtx_sync ) {
200 done = ! ( protocolSessionCount > server.getProtocolSessionsDoneSuccess() ||
201 protocolSessionCount > client.getProtocolSessionsDoneSuccess() ||
202 null == lastCompletedDevice ||
203 lastCompletedDevice.getConnected() );
204 }
205 max_connections_hit = ( protocolSessionCount * max_connections_per_session ) <= server.getDisconnectCount();
206 test_duration = Clock.currentTimeMillis() - t0;
207 timeout = 0 < timeout_value && timeout_value <= test_duration + timeout_preempt_diff; // let's timeout here before our timeout timer
208 if( !done && !max_connections_hit && !timeout ) {
209 try { Thread.sleep(88); } catch (final InterruptedException e) { e.printStackTrace(); }
210 }
211 } while( !done && !max_connections_hit && !timeout );
212 test_duration = Clock.currentTimeMillis() - t0;
213
214 PrintUtil.fprintf_td(System.err, "\n\n");
215 PrintUtil.fprintf_td(System.err, "****** Test Stats: duration %d ms, timeout[hit %b, value %d ms], max_connections hit %b\n",
216 test_duration, timeout, timeout_value, max_connections_hit);
217 PrintUtil.fprintf_td(System.err, " Server ProtocolSessions[success %d/%d total, requested %d], disconnects %d of %d max\n",
218 server.getProtocolSessionsDoneSuccess(), server.getProtocolSessionsDoneTotal(), protocolSessionCount,
219 server.getDisconnectCount(), ( protocolSessionCount * max_connections_per_session ));
220 PrintUtil.fprintf_td(System.err, " Client ProtocolSessions[success %d/%d total, requested %d], disconnects %d of %d max, power[down %d, up %d]\n",
221 client.getProtocolSessionsDoneSuccess(), client.getProtocolSessionsDoneTotal(), protocolSessionCount,
222 client.getDisconnectCount(), ( protocolSessionCount * max_connections_per_session ),
223 client_power_down_count, client_power_up_count);
224 PrintUtil.fprintf_td(System.err, "\n\n");
225
226 if( expSuccess ) {
227 Assert.assertFalse( max_connections_hit );
228 Assert.assertFalse( timeout );
229
230 synchronized( mtx_sync ) {
231 Assert.assertTrue(protocolSessionCount <= server.getProtocolSessionsDoneTotal());
232 Assert.assertEquals(protocolSessionCount, server.getProtocolSessionsDoneSuccess());
233 Assert.assertTrue(protocolSessionCount <= client.getProtocolSessionsDoneTotal());
234 Assert.assertEquals(protocolSessionCount, client.getProtocolSessionsDoneSuccess());
235 Assert.assertNotNull(lastCompletedDevice);
236 Assert.assertNotNull(lastCompletedDeviceEIR);
237 Assert.assertFalse(lastCompletedDevice.getConnected());
238 Assert.assertTrue( ( 1 * max_connections_per_session ) > server.getDisconnectCount() );
239 if( client_reset_test ) {
240 Assert.assertEquals( 1, client_power_down_count );
241 Assert.assertEquals( 1, client_power_up_count );
242 } else {
243 Assert.assertEquals( 0, client_power_down_count );
244 Assert.assertEquals( 0, client_power_up_count );
245 }
246 }
247 }
248
249 //
250 // Client stop
251 //
252 final boolean current_exp_discovering_state = expSuccess ? true : client.getAdapter().isDiscovering();
253 DBTClientTest.stopDiscovery(client, current_exp_discovering_state, "test"+suffix+"_stopDiscovery");
254 client.close("test"+suffix+"_close");
255
256 //
257 // Server stop
258 //
259 DBTServerTest.stop(server, "test"+suffix+"_stopAdvertising");
260
261 if( expSuccess ) {
262 //
263 // Validating Security Mode
264 //
265 final SMPKeyBin clientKeys = SMPKeyBin.read(DBTConstants.CLIENT_KEY_PATH, lastCompletedDevice, true /* verbose */);
266 Assert.assertTrue(clientKeys.toString(), clientKeys.isValid());
267 final BTSecurityLevel clientKeysSecLevel = clientKeys.getSecLevel();
268 Assert.assertEquals(secLevelClient, clientKeysSecLevel);
269 {
270 if( ExpectedPairing.PREPAIRED == clientExpPairing && BTSecurityLevel.NONE.value < secLevelClient.value ) {
271 // Using encryption: pre-paired
272 Assert.assertEquals(PairingMode.PRE_PAIRED, lastCompletedDevicePairingMode);
273 Assert.assertEquals(BTSecurityLevel.ENC_ONLY, lastCompletedDeviceSecurityLevel); // pre-paired fixed level, no auth
274 } else if( ExpectedPairing.NEW_PAIRING == clientExpPairing && BTSecurityLevel.NONE.value < secLevelClient.value ) {
275 // Using encryption: Newly paired
276 Assert.assertNotEquals(PairingMode.PRE_PAIRED, lastCompletedDevicePairingMode);
277 Assert.assertTrue("PairingMode client "+lastCompletedDevicePairingMode+" not > NONE", PairingMode.NONE.value < lastCompletedDevicePairingMode.value);
278 Assert.assertTrue("SecurityLevel client "+lastCompletedDeviceSecurityLevel+" not >= "+secLevelClient, secLevelClient.value <= lastCompletedDeviceSecurityLevel.value);
279 } else if( ExpectedPairing.DONT_CARE == clientExpPairing && BTSecurityLevel.NONE.value < secLevelClient.value ) {
280 // Any encryption, any pairing
281 Assert.assertTrue("PairingMode client "+lastCompletedDevicePairingMode+" not > NONE", PairingMode.NONE.value < lastCompletedDevicePairingMode.value);
282 Assert.assertTrue("SecurityLevel client "+lastCompletedDeviceSecurityLevel+" not >= "+secLevelClient, secLevelClient.value <= lastCompletedDeviceSecurityLevel.value);
283 } else {
284 // No encryption: No pairing
285 Assert.assertEquals(PairingMode.NONE, lastCompletedDevicePairingMode);
286 Assert.assertEquals(BTSecurityLevel.NONE, lastCompletedDeviceSecurityLevel);
287 }
288 }
289
290 //
291 // Validating EIR
292 //
293 synchronized( mtx_sync ) {
294 PrintUtil.println(System.err, "lastCompletedDevice.connectedEIR: "+lastCompletedDeviceEIR.toString());
295 Assert.assertNotEquals(0, lastCompletedDeviceEIR.getEIRDataMask().mask);
296 Assert.assertTrue( lastCompletedDeviceEIR.isSet(EIRDataTypeSet.DataType.FLAGS) );
297 Assert.assertTrue( lastCompletedDeviceEIR.isSet(EIRDataTypeSet.DataType.SERVICE_UUID) );
298 Assert.assertTrue( lastCompletedDeviceEIR.isSet(EIRDataTypeSet.DataType.NAME) );
299 Assert.assertTrue( lastCompletedDeviceEIR.isSet(EIRDataTypeSet.DataType.CONN_IVAL) );
300 Assert.assertEquals(serverName, lastCompletedDeviceEIR.getName());
301 {
302 final EInfoReport eir = lastCompletedDevice.getEIR().clone();
303 PrintUtil.println(System.err, "lastCompletedDevice.currentEIR: "+eir.toString());
304 Assert.assertEquals(0, eir.getEIRDataMask().mask);
305 Assert.assertEquals(0, eir.getName().length());
306 }
307 }
308
309 //
310 // Now reuse adapter for client mode -> Start discovery + Stop Discovery
311 //
312 {
313 final BTAdapter adapter = server.getAdapter();
314 { // if( false ) {
315 adapter.removeAllStatusListener();
316 }
317
318 DBTEndpoint.startDiscovery(adapter, false /* current_exp_discovering_state */);
319
320 DBTEndpoint.stopDiscovery(adapter, true /* current_exp_discovering_state */);
321 }
322 }
323 final int count = manager.removeChangedAdapterSetListener(myChangedAdapterSetListener);
324 PrintUtil.println(System.err, "****** EOL Removed ChangedAdapterSetCallback " + count);
325 }
326}
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2020 Gothel Software e.K.
boolean isSet(final SettingType bit)
BTAdapter status listener for remote BTDevice discovery events: Added, updated and removed; as well a...
Application toolkit providing BT device registration of processed and awaited devices.
static void addToWaitForDevices(final String addrOrNameSub)
One stop BTManager API entry point.
Definition: BTFactory.java:52
static synchronized BTManager getDirectBTManager()
Returns an initialized BluetoothManager instance using the DirectBT implementation.
Definition: BTFactory.java:472
Application toolkit providing BT security setup and its device association on a pattern matching basi...
static BTSecurityRegistry.Entry getOrCreate(final String addrOrNameSub)
Determines whether the given addrOrNameSub is a EUI48Sub or just a name and retrieves an entry.
Bit mask of 'Extended Inquiry Response' (EIR) data fields, indicating a set of related data.
Collection of 'Extended Advertising Data' (EAD), 'Advertising Data' (AD) or 'Extended Inquiry Respons...
final boolean isSet(final EIRDataTypeSet.DataType bit)
final EIRDataTypeSet getEIRDataMask()
native String toString(final boolean includeServices)
native String getName()
Storage for SMP keys including required connection parameter per local adapter and remote device.
Definition: SMPKeyBin.java:75
static SMPKeyBin read(final String fname, final boolean verbose_)
Create a new SMPKeyBin instance based upon stored file denoted by fname.
Definition: SMPKeyBin.java:258
final BTSecurityLevel getSecLevel()
Definition: SMPKeyBin.java:365
final boolean isValid()
Definition: SMPKeyBin.java:428
final String toString()
Definition: SMPKeyBin.java:470
This central BTRole::Master participant works with DBTServer00.
void setRemoveDevice(final boolean v)
Set remove device when disconnecting.
void setDisconnectDeviceed(final boolean v)
Set disconnect after processing.
void setDiscoveryPolicy(final DiscoveryPolicy v)
Set DiscoveryPolicy.
void setProtocolSessionsLeft(final int v)
Testing a full Bluetooth server and client lifecycle of operations, requiring two BT adapter:
static final int max_connections_per_session
static final String CLIENT_KEY_PATH
This peripheral BTRole::Slave test participant works with DBTClient00.
void setProtocolSessionsLeft(final int v)
Bits representing 'BTAdapter setting' data fields.
Bluetooth adapter operating mode.
Definition: BTMode.java:34
DUAL
Dual Bluetooth mode, i.e.
Definition: BTMode.java:38
Bluetooth Security Level.
NONE
No encryption and no authentication.
ENC_ONLY
Encryption and no authentication (no MITM).
Discovery policy defines the BTAdapter discovery mode after connecting a remote BTDevice:
PAUSE_CONNECTED_UNTIL_DISCONNECTED
Pause discovery until all connected BTDevice become disconnected, effectively until AdapterStatusList...
Each enum represents a 'Extended Inquiry Response' (EIR) data field type bit.
BT Core Spec v5.2: Vol 1, Part F Controller Error Codes: 1.3 List of Error Codes.
Bluetooth secure pairing mode.
NONE
No pairing mode, implying no secure connections, no encryption and no MITM protection.
PRE_PAIRED
Reusing encryption keys from previous pairing.
BTAdapter represents one local Bluetooth Controller.
Definition: BTAdapter.java:48
HCIStatusCode reset()
Reset the adapter.
int removeAllStatusListener()
Remove all AdapterStatusListener from the list.
boolean addStatusListener(final AdapterStatusListener listener)
Add the given AdapterStatusListener to the list if not already present.
boolean isDiscovering()
Returns true if the meta discovering state is not ScanType#NONE.
BTDevice represents one remote Bluetooth device.
Definition: BTDevice.java:47
BTAdapter getAdapter()
Returns the adapter on which this device was discovered or connected.
BTSecurityLevel getConnSecurityLevel()
Return the BTSecurityLevel, determined when the connection is established.
boolean getConnected()
Returns the connected state of the device.
EInfoReport getEIR()
Return the merged advertised EInfoReport for this remote device.
PairingMode getPairingMode()
Returns the current PairingMode used by the device.
A thread safe singleton handler of the BTAdapter manager, e.g.
Definition: BTManager.java:36
List< BTAdapter > getAdapters()
Returns a list of BluetoothAdapters available in the system.
int removeChangedAdapterSetListener(final ChangedAdapterSetListener l)
Remove the given ChangedAdapterSetListener from this manager.
HCIStatusCode startDiscovery(String msg)
HCIStatusCode stopDiscovery(String msg)
static void startDiscovery(final BTAdapter adapter, final boolean current_exp_discovering_state)
String getName()
Return name of this endpoint, which becomes the adapter's name.
static void checkInitializedState(final DBTEndpoint endp)
void close(final String msg)
static void stopDiscovery(final BTAdapter adapter, final boolean current_exp_discovering_state)
BTAdapter getAdapter()
Return the adapter for this endpoint.
static ChangedAdapterSetListener initChangedAdapterSetListener(final BTManager manager, final List< DBTEndpoint > endpts)
HCIStatusCode startAdvertising(String msg)
static void stop(final DBTServerTest server, final String msg)