View Javadoc

1   /**
2    *  BlueCove - Java library for Bluetooth
3    *  Copyright (C) 2006-2008 Vlad Skarzhevskyy
4    *
5    *  Licensed to the Apache Software Foundation (ASF) under one
6    *  or more contributor license agreements.  See the NOTICE file
7    *  distributed with this work for additional information
8    *  regarding copyright ownership.  The ASF licenses this file
9    *  to you under the Apache License, Version 2.0 (the
10   *  "License"); you may not use this file except in compliance
11   *  with the License.  You may obtain a copy of the License at
12   *
13   *    http://www.apache.org/licenses/LICENSE-2.0
14   *
15   *  Unless required by applicable law or agreed to in writing,
16   *  software distributed under the License is distributed on an
17   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   *  KIND, either express or implied.  See the License for the
19   *  specific language governing permissions and limitations
20   *  under the License.
21   *
22   *  @author vlads
23   *  @version $Id: TestResponderClient.java 2607 2008-12-17 23:51:33Z skarzhevskyy $
24   */
25  package net.sf.bluecove;
26  
27  import java.io.IOException;
28  import java.util.Enumeration;
29  import java.util.Hashtable;
30  import java.util.Vector;
31  
32  import javax.bluetooth.BluetoothStateException;
33  import javax.bluetooth.DiscoveryAgent;
34  import javax.bluetooth.LocalDevice;
35  import javax.bluetooth.UUID;
36  import javax.microedition.io.Connection;
37  import javax.microedition.io.Connector;
38  
39  import org.bluecove.tester.log.Logger;
40  import org.bluecove.tester.util.IOUtils;
41  import org.bluecove.tester.util.RuntimeDetect;
42  import org.bluecove.tester.util.StringUtils;
43  import org.bluecove.tester.util.TimeUtils;
44  
45  import net.sf.bluecove.se.JavaSECommon;
46  import net.sf.bluecove.util.BluetoothTypesInfo;
47  import net.sf.bluecove.util.CollectionUtils;
48  import net.sf.bluecove.util.CountStatistic;
49  import net.sf.bluecove.util.IntVar;
50  import net.sf.bluecove.util.TimeStatistic;
51  
52  /**
53   * 
54   */
55  public class TestResponderClient extends TestResponderCommon implements Runnable, CanShutdown {
56  
57  	public static int countSuccess = 0;
58  
59  	public static FailureLog failure = new FailureLog("Client failure");
60  
61  	public static int discoveryCount = 0;
62  
63  	public static int connectionCount = 0;
64  
65  	public static int discoveryDryCount = 0;
66  
67  	public static int discoverySuccessCount = 0;
68  
69  	public static long lastSuccessfulDiscovery;
70  
71  	public static int countConnectionThreads = 0;
72  
73  	private int connectedConnectionsExpect = 1;
74  
75  	private int connectionLogPrefixLength = 0;
76  
77  	private int connectedConnectionsInfo = 1;
78  
79  	private Vector concurrentConnections = new Vector();
80  
81  	public static CountStatistic concurrentStatistic = new CountStatistic();
82  
83  	public static TimeStatistic connectionDuration = new TimeStatistic();
84  
85  	public static CountStatistic connectionRetyStatistic = new CountStatistic();
86  
87  	public Thread thread;
88  
89  	private Object threadLocalBluetoothStack;
90  
91  	boolean stoped = false;
92  
93  	TestClientConfig config;
94  
95  	boolean isRunning = false;
96  
97  	boolean runStressTest = false;
98  
99  	TestClientBluetoothInquirer bluetoothInquirer;
100 
101 	public static Hashtable recentDeviceNames = new Hashtable/* <BTAddress,Name> */();
102 
103 	boolean configured = false;
104 
105 	static int sdAttrRetrievableMax = 255;
106 
107 	public static synchronized void clear() {
108 		countSuccess = 0;
109 		failure.clear();
110 		discoveryCount = 0;
111 		concurrentStatistic.clear();
112 		connectionDuration.clear();
113 	}
114 
115 	public TestResponderClient() throws BluetoothStateException {
116 		this(true);
117 	}
118 
119 	public TestResponderClient(boolean logLocalDevice) throws BluetoothStateException {
120 
121 		config = new TestClientConfig();
122 
123 		if (logLocalDevice) {
124 			TestResponderCommon.startLocalDevice();
125 		}
126 		threadLocalBluetoothStack = Configuration.threadLocalBluetoothStack;
127 
128 		String v = LocalDevice.getProperty("bluetooth.sd.attr.retrievable.max");
129 		if (v != null) {
130 			sdAttrRetrievableMax = Integer.valueOf(v).intValue();
131 			if ((sdAttrRetrievableMax > 7) && (RuntimeDetect.isJ2ME)) {
132 				sdAttrRetrievableMax = 7;
133 			}
134 		}
135 
136 	}
137 
138 	static boolean isMultiProtocol() {
139 		return ((Configuration.supportL2CAP) && Configuration.testL2CAP.booleanValue() && Configuration.testRFCOMM
140 				.booleanValue());
141 	}
142 
143 	public void connectAndTest(String serverURL, String urlArgs, IntVar firstCase, IntVar lastCase,
144 			TestResponderClientConnection connectionHandler) {
145 		String deviceAddress = BluetoothTypesInfo.extractBluetoothAddress(serverURL);
146 		String deviceName = niceDeviceName(deviceAddress);
147 		long start = System.currentTimeMillis();
148 		Logger.debug("connect:" + deviceName + " " + serverURL);
149 		String logPrefix = "";
150 		if (isMultiProtocol()) {
151 			deviceName = connectionHandler.protocolID() + " " + deviceName;
152 		}
153 		if (connectedConnectionsExpect > 1) {
154 			logPrefix = "[" + StringUtils.padRight(deviceName, connectionLogPrefixLength, ' ') + "] ";
155 		}
156 		for (int testType = firstCase.getValue(); (!stoped) && (runStressTest || testType <= lastCase.getValue()); testType++) {
157 			Connection conn = null;
158 			ConnectionHolder c = null;
159 			TestStatus testStatus = new TestStatus();
160 			testStatus.pairBTAddress = deviceAddress;
161 			TestTimeOutMonitor monitor = null;
162 			long connectionStartTime = 0;
163 			try {
164 				if (!runStressTest) {
165 					Logger.debug(logPrefix + "test #" + testType + " connects");
166 				} else {
167 					testType = Configuration.STERSS_TEST_CASE.getValue();
168 				}
169 				int connectionOpenTry = 0;
170 				while ((conn == null) && (!stoped)) {
171 					try {
172 						conn = Connector.open(serverURL + urlArgs, Connector.READ_WRITE, true);
173 					} catch (IOException e) {
174 						connectionOpenTry++;
175 						if ((stoped) || (connectionOpenTry > CommunicationTester.clientConnectionOpenRetry)) {
176 							throw e;
177 						}
178 						Logger.debug(logPrefix + "Connector error", e);
179 						Thread.sleep(Configuration.clientSleepOnConnectionRetry);
180 						Logger.debug(logPrefix + "connect retry:" + connectionOpenTry);
181 						String cCount = LocalDevice.getProperty("bluecove.connections");
182 						if ((cCount != null) && (!"0".equals(cCount))) {
183 							Logger.debug(logPrefix + "has connections:" + cCount);
184 						}
185 					}
186 				}
187 				if (stoped) {
188 					return;
189 				}
190 				c = connectionHandler.connected(conn);
191 
192 				connectionStartTime = System.currentTimeMillis();
193 				connectionRetyStatistic.add(connectionOpenTry);
194 				connectionCount++;
195 
196 				c.registerConcurrent(concurrentConnections);
197 				c.concurrentNotify();
198 
199 				if (connectedConnectionsInfo < c.concurrentCount) {
200 					connectedConnectionsInfo = c.concurrentCount;
201 					Logger.info(logPrefix + "now connected:" + connectedConnectionsInfo);
202 					synchronized (TestResponderClient.this) {
203 						TestResponderClient.this.notifyAll();
204 					}
205 				}
206 				c.active();
207 				monitor = TestTimeOutMonitor.create(logPrefix + "test #" + testType, c,
208 						Configuration.clientTestTimeOutSec);
209 				if (!runStressTest) {
210 					Logger.debug(logPrefix + "run test #" + testType);
211 				} else {
212 					Logger.debug(logPrefix + "connected:" + connectionCount);
213 					if (connectionCount % 5 == 0) {
214 						Logger.debug("Test time " + TimeUtils.secSince(start));
215 					}
216 				}
217 				if (testType > Consts.TEST_SERVER_TERMINATE) {
218 					Configuration.setLastServerURL(serverURL);
219 				}
220 
221 				connectionHandler.executeTest(testType, testStatus);
222 
223 				if (monitor.isShutdownCalled()) {
224 					failure.addFailure(deviceName + " test #" + testType + " " + testStatus.getName()
225 							+ " terminated by  by TimeOut");
226 				} else if (testStatus.isError) {
227 					failure.addFailure(deviceName + " test #" + testType + " " + testStatus.getName());
228 				} else if (testStatus.isSuccess) {
229 					countSuccess++;
230 					Logger.debug(logPrefix + "test #" + testType + " " + testStatus.getName() + ": OK");
231 				} else if (testStatus.streamClosed) {
232 					Logger.debug(logPrefix + "see server log");
233 				} else {
234 					connectionHandler.replySuccess(logPrefix, testType, testStatus);
235 					countSuccess++;
236 					Logger.debug(logPrefix + "test #" + testType + " " + testStatus.getName() + ": OK");
237 				}
238 				if (connectionCount % 5 == 0) {
239 					Logger.info("*Success:" + countSuccess + " Failure:" + failure.countFailure);
240 				}
241 				Configuration.setLastServerURL(serverURL);
242 
243 				// Delay to see if many connections are made.
244 				if ((connectedConnectionsExpect > 1) && (connectedConnectionsInfo < connectedConnectionsExpect)) {
245 					synchronized (TestResponderClient.this) {
246 						try {
247 							TestResponderClient.this.wait(3 * 1000);
248 						} catch (InterruptedException e) {
249 							break;
250 						}
251 					}
252 					Logger.debug(logPrefix + "concurrentCount " + c.concurrentCount);
253 				}
254 			} catch (Throwable e) {
255 				if (!stoped) {
256 					if ((monitor != null) && (monitor.isShutdownCalled())) {
257 						failure.addFailure(deviceName + " test #" + testType + " " + testStatus.getName()
258 								+ " terminated by  by TimeOut");
259 					} else if (testType < Consts.TEST_SERVER_TERMINATE) {
260 						failure.addFailure(deviceName + " test #" + testType + " " + testStatus.getName(), e);
261 					}
262 				}
263 				Logger.error(deviceName + " test #" + testType + " " + testStatus.getName(), e);
264 			} finally {
265 				if (connectionStartTime != 0) {
266 					connectionDuration.add(TimeUtils.since(connectionStartTime));
267 				}
268 				if (monitor != null) {
269 					monitor.finish();
270 				}
271 				if (c != null) {
272 					c.disconnected();
273 					if (c.concurrentCount != 0) {
274 						concurrentStatistic.add(c.concurrentCount);
275 					}
276 					c.shutdown();
277 				} else {
278 					IOUtils.closeQuietly(conn);
279 				}
280 			}
281 			if ((Configuration.clientTestStopOnErrorCount != 0)
282 					&& (failure.countFailure >= Configuration.clientTestStopOnErrorCount)) {
283 				shutdown();
284 			}
285 			// Let the server restart
286 			if (!stoped) {
287 				try {
288 					Thread.sleep(Configuration.clientSleepBetweenConnections);
289 				} catch (InterruptedException e) {
290 					break;
291 				}
292 			}
293 		}
294 		if (!Configuration.clientContinuous.booleanValue()) {
295 			connectionHandler.sendStopServerCmd(serverURL);
296 		}
297 	}
298 
299 	private boolean connectAndTest(String serverURL) {
300 		if (BluetoothTypesInfo.isRFCOMM(serverURL)) {
301 			if (Configuration.testRFCOMM.booleanValue()) {
302 				TestResponderClientRFCOMM.connectAndTest(this, serverURL);
303 				return true;
304 			}
305 		} else if (BluetoothTypesInfo.isL2CAP(serverURL)) {
306 			if (Configuration.supportL2CAP) {
307 				if (Configuration.testL2CAP.booleanValue()) {
308 					TestResponderClientL2CAP.connectAndTest(this, serverURL);
309 					return true;
310 				}
311 			} else {
312 				Logger.warn("Can't test L2CAP on this stack");
313 			}
314 		} else {
315 			Logger.warn("No tests for connection type " + serverURL);
316 		}
317 		return false;
318 	}
319 
320 	private class ClientConnectionTread extends Thread {
321 
322 		String url;
323 
324 		boolean started;
325 
326 		ClientConnectionTread(String url) {
327 			// CLDC_1_0 super("ClientConnectionTread" +
328 			// (++countConnectionThreads));
329 			++countConnectionThreads;
330 
331 			this.url = url;
332 		}
333 
334 		public void run() {
335 			RuntimeDetect.cldcStub.setThreadLocalBluetoothStack(threadLocalBluetoothStack);
336 			started = connectAndTest(url);
337 		}
338 	}
339 
340 	private void connectAndTest(Vector urls) {
341 		int numberOfURLs = urls.size();
342 		if ((!Configuration.clientTestConnectionsMultipleThreads) || (numberOfURLs == 1)) {
343 			connectedConnectionsExpect = 1;
344 			connectedConnectionsInfo = 1;
345 			for (Enumeration en = urls.elements(); en.hasMoreElements();) {
346 				if (stoped) {
347 					break;
348 				}
349 				String url = (String) en.nextElement();
350 				connectAndTest(url);
351 			}
352 		} else {
353 			connectedConnectionsExpect = numberOfURLs;
354 			connectedConnectionsInfo = 1;
355 
356 			connectionLogPrefixLength = 0;
357 			for (Enumeration en = urls.elements(); en.hasMoreElements();) {
358 				String deviceAddress = BluetoothTypesInfo.extractBluetoothAddress((String) en.nextElement());
359 				String deviceName = niceDeviceName(deviceAddress);
360 				if (deviceName.length() > connectionLogPrefixLength) {
361 					connectionLogPrefixLength = deviceName.length();
362 				}
363 			}
364 
365 			if (isMultiProtocol()) {
366 				connectionLogPrefixLength += 3;
367 			}
368 
369 			Logger.debug("start " + numberOfURLs + " threads");
370 			Vector threads = new Vector();
371 			for (Enumeration en = urls.elements(); en.hasMoreElements();) {
372 				ClientConnectionTread t = new ClientConnectionTread((String) en.nextElement());
373 				t.start();
374 				threads.addElement(t);
375 			}
376 			int connectedConnectionsStartedExpect = 0;
377 			for (Enumeration en = threads.elements(); en.hasMoreElements();) {
378 				ClientConnectionTread t = (ClientConnectionTread) en.nextElement();
379 				if (t.started) {
380 					connectedConnectionsStartedExpect++;
381 				}
382 				try {
383 					t.join();
384 				} catch (InterruptedException e) {
385 					break;
386 				}
387 			}
388 			if (connectedConnectionsInfo < connectedConnectionsStartedExpect) {
389 				if (!stoped) {
390 					failure.addFailure("Fails to establish " + connectedConnectionsStartedExpect
391 							+ " connections same time");
392 					Logger.error("Fails to establish " + connectedConnectionsStartedExpect + " connections same time");
393 				}
394 			} else {
395 				if (connectedConnectionsInfo > 1) {
396 					Logger.info("Established " + connectedConnectionsInfo + " connections same time");
397 				}
398 			}
399 		}
400 	}
401 
402 	public void configured() {
403 		synchronized (this) {
404 			configured = true;
405 			this.notifyAll();
406 		}
407 	}
408 
409 	public long lastActivityTime() {
410 		return lastSuccessfulDiscovery;
411 	}
412 
413 	public void run() {
414 		synchronized (this) {
415 			while (!configured) {
416 				try {
417 					this.wait();
418 				} catch (InterruptedException e) {
419 					return;
420 				}
421 			}
422 		}
423 		RuntimeDetect.cldcStub.setThreadLocalBluetoothStack(threadLocalBluetoothStack);
424 
425 		Logger.debug(config.logID + "Client started..." + TimeUtils.timeNowToString());
426 		isRunning = true;
427 		try {
428 			bluetoothInquirer = new TestClientBluetoothInquirer(config);
429 			Switcher.clientStarted(this);
430 
431 			int startTry = 0;
432 			if (config.connectURL != null) {
433 				if (!config.connectURL.equals("")) {
434 					bluetoothInquirer.serverURLs.addElement(config.connectURL);
435 				}
436 			} else if (config.connectDevice != null) {
437 				Configuration.clientContinuousDiscoveryDevices = false;
438 				bluetoothInquirer.devices.addElement(new RemoteDeviceIheritance(config.connectDevice));
439 			}
440 
441 			while (!stoped) {
442 				if ((config.connectURL != null) && (config.connectURL.equals(""))) {
443 					try {
444 						bluetoothInquirer.serverURLs.removeAllElements();
445 						DiscoveryAgent discoveryAgent = LocalDevice.getLocalDevice().getDiscoveryAgent();
446 						UUID uuid = (config.searchOnlyBluecoveUuid) ? Configuration.blueCoveUUID()
447 								: Configuration.discoveryUUID;
448 						String url = discoveryAgent.selectService(uuid, Configuration.getRequiredSecurity(), false);
449 						if (url != null) {
450 							Logger.debug(config.logID + "selectService service found " + url);
451 							bluetoothInquirer.serverURLs.addElement(url);
452 						} else {
453 							Logger.debug(config.logID + "selectService service not found");
454 						}
455 					} catch (BluetoothStateException e) {
456 						Logger.error(config.logID + "Cannot selectService", e);
457 					}
458 				} else if ((!bluetoothInquirer.hasServers())
459 						|| (Configuration.clientContinuousDiscovery.booleanValue() && (config.connectURL == null))
460 						|| (!Configuration.clientTestConnections)) {
461 					if (!bluetoothInquirer.runDeviceInquiry()) {
462 						if (stoped) {
463 							break;
464 						}
465 						startTry++;
466 						try {
467 							Thread.sleep(Configuration.clientSleepOnDeviceInquiryError);
468 						} catch (Exception e) {
469 							break;
470 						}
471 						if (startTry < 3) {
472 							continue;
473 						}
474 						Switcher.yield(this);
475 					} else {
476 						startTry = 0;
477 					}
478 					while (bluetoothInquirer.inquiring) {
479 						try {
480 							Thread.sleep(1000);
481 						} catch (Exception e) {
482 							break;
483 						}
484 					}
485 				}
486 				if ((Configuration.clientTestConnections) && (bluetoothInquirer.hasServers())) {
487 					discoveryDryCount = 0;
488 					discoverySuccessCount++;
489 					lastSuccessfulDiscovery = System.currentTimeMillis();
490 					if (!config.discoveryOnce) {
491 						connectAndTest(bluetoothInquirer.serverURLs);
492 					}
493 				} else {
494 					discoveryDryCount++;
495 					if ((discoveryDryCount % 5 == 0) && (lastSuccessfulDiscovery != 0)) {
496 						Logger.debug(config.logID + "No services " + discoveryDryCount + " times for "
497 								+ TimeUtils.secSince(lastSuccessfulDiscovery) + " " + discoverySuccessCount);
498 					}
499 				}
500 				Logger.info(config.logID + "*Success:" + countSuccess + " Failure:" + failure.countFailure);
501 				if ((countSuccess + failure.countFailure > 0) && (!Configuration.clientContinuous.booleanValue())) {
502 					break;
503 				}
504 				if (stoped || config.discoveryOnce || config.connectOnce) {
505 					break;
506 				}
507 				Switcher.yield(this);
508 			}
509 		} catch (Throwable e) {
510 			if (!stoped) {
511 				Logger.error(config.logID + "client error ", e);
512 			}
513 		} finally {
514 			Switcher.clientEnds(this);
515 			config.connectURL = null;
516 			isRunning = false;
517 			Logger.info(config.logID + "Client finished! " + TimeUtils.timeNowToString());
518 			Switcher.yield(this);
519 		}
520 	}
521 
522 	public boolean isAnyServiceFound() {
523 		if (bluetoothInquirer != null) {
524 			return bluetoothInquirer.anyServicesFound;
525 		} else {
526 			return false;
527 		}
528 	}
529 
530 	public void shutdown() {
531 		Logger.info(config.logID + "shutdownClient");
532 		stoped = true;
533 		if (bluetoothInquirer != null) {
534 			bluetoothInquirer.shutdown();
535 		}
536 		if (RuntimeDetect.cldcStub != null) {
537 			RuntimeDetect.cldcStub.interruptThread(thread);
538 		}
539 		Vector concurrentConnectionsCopy = CollectionUtils.copy(concurrentConnections);
540 		for (Enumeration iter = concurrentConnectionsCopy.elements(); iter.hasMoreElements();) {
541 			ConnectionHolder t = (ConnectionHolder) iter.nextElement();
542 			t.shutdown();
543 		}
544 	}
545 
546 	public static void main(String[] args) {
547 		JavaSECommon.initOnce();
548 		try {
549 			(new TestResponderClient()).run();
550 			// System.exit(0);
551 		} catch (Throwable e) {
552 			Logger.error("start error ", e);
553 		}
554 	}
555 
556 }