/** * BlueCove - Java library for Bluetooth * Copyright (C) 2006-2009 Vlad Skarzhevskyy * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * * @author vlads * @version $Id$ */ package net.sf.bluecove; import java.io.DataOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import java.util.Vector; import javax.bluetooth.BluetoothStateException; import javax.bluetooth.DataElement; import javax.bluetooth.DiscoveryAgent; import javax.bluetooth.LocalDevice; import javax.bluetooth.RemoteDevice; import javax.bluetooth.ServiceRecord; import javax.bluetooth.ServiceRegistrationException; import javax.bluetooth.UUID; import javax.microedition.io.Connector; import javax.microedition.io.StreamConnection; import javax.microedition.io.StreamConnectionNotifier; import org.bluecove.tester.log.Logger; import org.bluecove.tester.util.IOUtils; import org.bluecove.tester.util.RuntimeDetect; import org.bluecove.tester.util.TimeUtils; import net.sf.bluecove.se.JavaSECommon; import net.sf.bluecove.util.BluetoothTypesInfo; import net.sf.bluecove.util.CollectionUtils; import net.sf.bluecove.util.CountStatistic; import net.sf.bluecove.util.TimeStatistic; public class TestResponderServer implements CanShutdown, Runnable { public static int countSuccess = 0; public static TimeStatistic allServerDuration = new TimeStatistic(); public static FailureLog failure = new FailureLog("Server failure"); public static int countConnection = 0; public static int countRunningConnections = 0; public static int concurrentConnectionsMax = 0; public Thread thread; private Object threadLocalBluetoothStack; private long lastActivityTime; private boolean stoped = false; boolean isRunning = false; public static boolean discoverable = false; public static long discoverableStartTime = 0; public static long connectorOpenTime = 0; private StreamConnectionNotifier serverConnection; private TestTimeOutMonitor monitorServer; private TestResponderServerL2CAP responderL2CAPServerThread = null; private TestResponderServerOBEX responderOBEXServer = null; private Vector concurrentConnectionRunnable = new Vector(); public static CountStatistic concurrentStatistic = new CountStatistic(); public static TimeStatistic connectionDuration = new TimeStatistic(); public static int updateVariableCount = 0; private class ServerConnectionRunnable implements Runnable { long connectionStartTime; int concurrentCount = 0; ConnectionHolderStream c = new ConnectionHolderStream(); boolean isRunning = true; private String name; ServerConnectionRunnable(StreamConnection conn) { name = "ServerConnectionTread" + (++countConnection); c.conn = conn; connectionStartTime = System.currentTimeMillis(); synchronized (concurrentConnectionRunnable) { concurrentConnectionRunnable.addElement(this); } } String getName() { return this.name; } private void concurrentNotify() { synchronized (concurrentConnectionRunnable) { int concurNow = concurrentConnectionRunnable.size(); setConcurrentCount(concurNow); if (concurNow > 1) { // Update all other working Threads for (Enumeration iter = concurrentConnectionRunnable.elements(); iter.hasMoreElements();) { ServerConnectionRunnable t = (ServerConnectionRunnable) iter.nextElement(); t.setConcurrentCount(concurNow); } } } } private void setConcurrentCount(int concurNow) { if (concurrentCount < concurNow) { concurrentCount = concurNow; } } private void runEcho(InputStream is, char firstChar) { int receivedCount = 1; StringBuffer buf = new StringBuffer(); char cBuf[] = new char[50]; int cBufIdx = 0; boolean cBufHasBinary = false; buf.append(firstChar); cBuf[cBufIdx] = firstChar; cBufIdx++; try { RemoteDevice device = RemoteDevice.getRemoteDevice(c.conn); boolean authorized = false; try { authorized = device.isAuthorized(c.conn); } catch (Throwable blucoveIgnoe) { } Logger.debug("connected:" + device.getBluetoothAddress() + (device.isAuthenticated() ? " Auth" : "") + (authorized ? " Authz" : "") + (device.isEncrypted() ? " Encr" : "")); c.os = c.conn.openOutputStream(); c.os.write(firstChar); OutputStream os = c.os; int i; while ((i = is.read()) != -1) { receivedCount++; c.os.write(i); char c = (char) i; cBuf[cBufIdx] = c; cBufIdx++; if ((c == '\n') || (cBufIdx > 40)) { if (cBufHasBinary) { buf.append(" ["); for (int k = 0; k < cBufIdx; k++) { buf.append(Integer.toHexString(cBuf[k])).append(' '); } buf.append("]"); } Logger.debug("|" + buf.toString()); os.flush(); buf = new StringBuffer(); cBufIdx = 0; cBufHasBinary = false; } else { buf.append(c); if (c < ' ') { cBufHasBinary = true; } } } } catch (Throwable e) { Logger.debug("echo error", e); } finally { if (buf.length() != 0) { Logger.debug("|" + buf.toString()); } Logger.debug("echo received " + receivedCount); } } public void run() { int testType = 0; TestStatus testStatus = new TestStatus(); TestTimeOutMonitor monitorConnection = null; try { c.is = c.conn.openInputStream(); countRunningConnections++; concurrentNotify(); if (concurrentConnectionsMax < countRunningConnections) { concurrentConnectionsMax = countRunningConnections; Logger.info("now connected:" + countRunningConnections); } int isTest = c.is.read(); if (isTest == -1) { Logger.debug("EOF received"); return; } if (isTest != Consts.SEND_TEST_START) { Logger.debug("not a test client connected, will echo"); runEcho(c.is, (char) isTest); return; } testType = c.is.read(); if (isTest == -1) { Logger.debug("EOF received"); return; } if (testType == Consts.TEST_SERVER_TERMINATE) { Logger.info("Stop requested"); shutdown(); return; } testStatus.setName(testType); Logger.debug("run test# " + testType); monitorConnection = TestTimeOutMonitor.create("test" + testType, c, Configuration.serverTestTimeOutSec); c.os = c.conn.openOutputStream(); c.active(); CommunicationTester.runTest(testType, true, c, testStatus); if (!testStatus.streamClosed) { Logger.debug("reply OK"); c.active(); if (testStatus.replyMessage != null) { c.os.write(Consts.SEND_TEST_REPLY_OK_MESSAGE); c.os.write(testType); DataOutputStream dos = new DataOutputStream(c.os); dos.writeUTF(testStatus.replyMessage.toString()); dos.flush(); } else { c.os.write(Consts.SEND_TEST_REPLY_OK); c.os.write(testType); } c.os.flush(); } monitorConnection.finish(); countSuccess++; Logger.debug("Test# " + testType + " " + testStatus.getName() + " ok"); if (!stoped) { try { Thread.sleep(Configuration.serverSleepB4ClosingConnection); } catch (InterruptedException e) { } } } catch (Throwable e) { if ((!stoped) && (testType < Consts.TEST_SERVER_TERMINATE)) { failure.addFailure("test " + testType + " " + testStatus.getName(), e); } Logger.error("Test# " + testType + " " + testStatus.getName() + " error", e); } finally { if (monitorConnection != null) { monitorConnection.finish(); } synchronized (concurrentConnectionRunnable) { concurrentConnectionRunnable.removeElement(this); } countRunningConnections--; concurrentStatistic.add(concurrentCount); connectionDuration.add(TimeUtils.since(connectionStartTime)); isRunning = false; c.shutdown(); synchronized (this) { notifyAll(); } } Logger.info("*Test Success:" + countSuccess + " Failure:" + failure.countFailure); } void shutdown() { if (isRunning) { c.shutdown(); } } } public TestResponderServer() throws BluetoothStateException { threadLocalBluetoothStack = Configuration.threadLocalBluetoothStack; TestResponderCommon.startLocalDevice(); } public void run() { stoped = false; isRunning = true; if (!Configuration.serverContinuous) { lastActivityTime = System.currentTimeMillis(); monitorServer = TestTimeOutMonitor.create("ServerUp", this, Consts.serverUpTimeOutSec); } RuntimeDetect.cldcStub.setThreadLocalBluetoothStack(threadLocalBluetoothStack); try { LocalDevice localDevice = LocalDevice.getLocalDevice(); try { if ((localDevice.getDiscoverable() == DiscoveryAgent.NOT_DISCOVERABLE) || (Configuration.testServerForceDiscoverable.booleanValue())) { setDiscoverable(); } } catch (Throwable ignore) { Logger.warn("Error: " + ignore.toString()); } if (Configuration.testRFCOMM.booleanValue()) { StringBuffer url = new StringBuffer(BluetoothTypesInfo.PROTOCOL_SCHEME_RFCOMM); url.append("://localhost:").append(Configuration.blueCoveUUID()); url.append(";name=").append(Consts.RESPONDER_SERVERNAME).append("_rf"); if (Configuration.useShortUUID.booleanValue()) { url.append("s"); } url.append(Configuration.serverURLParams()); serverConnection = (StreamConnectionNotifier) Connector.open(url.toString()); connectorOpenTime = System.currentTimeMillis(); Logger.info("ResponderServer started " + TimeUtils.timeNowToString()); if (Configuration.testServiceAttributes.booleanValue()) { ServiceRecord record = LocalDevice.getLocalDevice().getRecord(serverConnection); if (record == null) { Logger.warn("Bluetooth ServiceRecord is null"); } else { String initial = BluetoothTypesInfo.toString(record); boolean printAllVersion = true; if (printAllVersion) { Logger.debug("ServiceRecord\n" + initial); } buildServiceRecord(record); try { localDevice.updateRecord(record); Logger.debug("ServiceRecord updated\n" + BluetoothTypesInfo.toString(record)); } catch (Throwable e) { if (!printAllVersion) { Logger.debug("ServiceRecord\n" + initial); } Logger.error("Service Record update error", e); } } } } if (Configuration.supportL2CAP) { if (Configuration.testL2CAP.booleanValue()) { responderL2CAPServerThread = TestResponderServerL2CAP.startServer(); } } else { Logger.info("No L2CAP support"); } if (Configuration.testRFCOMM.booleanValue()) { try { responderOBEXServer = TestResponderServerOBEX.startServer(); } catch (Throwable noObex) { Logger.error("OBEX Service ", noObex); } } if (Configuration.testRFCOMM.booleanValue()) { boolean showServiceRecordOnce = true; while ((Configuration.testRFCOMM.booleanValue()) && (!stoped)) { if ((countConnection % 5 == 0) && (Configuration.testServiceAttributes.booleanValue())) { // Problems on SE // updateServiceRecord(); } Logger.info("Accepting RFCOMM connections"); if (showServiceRecordOnce) { Logger.debug("RfUrl:" + LocalDevice.getLocalDevice().getRecord(serverConnection).getConnectionURL( Configuration.getRequiredSecurity(), false)); } StreamConnection conn = serverConnection.acceptAndOpen(); if (!stoped) { Logger.info("Received RFCOMM connection"); if (countConnection % 5 == 0) { Logger.debug("Server up time " + TimeUtils.secSince(connectorOpenTime)); Logger.debug("max concurrent con " + concurrentConnectionsMax); } if (showServiceRecordOnce) { Logger.debug("ServiceRecord\n" + BluetoothTypesInfo.toString(LocalDevice.getLocalDevice().getRecord( serverConnection))); showServiceRecordOnce = false; } lastActivityTime = System.currentTimeMillis(); ServerConnectionRunnable r = new ServerConnectionRunnable(conn); Thread t = RuntimeDetect.cldcStub.createNamedThread(r, r.getName()); t.start(); if (!Configuration.serverAcceptWhileConnected.booleanValue()) { while (r.isRunning) { synchronized (r) { try { r.wait(); } catch (InterruptedException e) { break; } } } } } else { IOUtils.closeQuietly(conn); } Switcher.yield(this); } closeServer(); } } catch (Throwable e) { if (!stoped) { Logger.error("RFCOMM Server start error", e); } } finally { Logger.info("RFCOMM Server finished! " + TimeUtils.timeNowToString()); isRunning = false; } if (monitorServer != null) { monitorServer.finish(); } } public boolean isRunning() { return isRunning || ((responderL2CAPServerThread != null) && responderL2CAPServerThread.isRunning()) || ((responderOBEXServer != null) && (responderOBEXServer.isRunning())); } public static long avgServerDurationSec() { return allServerDuration.avgSec(); } public boolean hasRunningConnections() { return (countRunningConnections > 0); } public long lastActivityTime() { return lastActivityTime; } public static void clear() { countSuccess = 0; countConnection = 0; concurrentConnectionsMax = 0; allServerDuration.clear(); failure.clear(); concurrentStatistic.clear(); connectionDuration.clear(); } private void closeServer() { closeServerClientConnections(); if (serverConnection != null) { synchronized (this) { try { if (serverConnection != null) { serverConnection.close(); } Logger.debug("serverConnection closed"); } catch (Throwable e) { Logger.error("Server stop error", e); } } serverConnection = null; } TestResponderServerL2CAP t = responderL2CAPServerThread; responderL2CAPServerThread = null; if (t != null) { t.closeServer(); } if (responderOBEXServer != null) { responderOBEXServer.closeServer(); responderOBEXServer = null; } setNotDiscoverable(); } public void closeServerClientConnections() { Vector copy = CollectionUtils.copy(concurrentConnectionRunnable); for (Enumeration iter = copy.elements(); iter.hasMoreElements();) { ServerConnectionRunnable t = (ServerConnectionRunnable) iter.nextElement(); t.shutdown(); } if (responderL2CAPServerThread != null) { responderL2CAPServerThread.closeServerClientConnections(); } } public int countClientConnections() { int count = 0; synchronized (concurrentConnectionRunnable) { for (Enumeration iter = concurrentConnectionRunnable.elements(); iter.hasMoreElements();) { ServerConnectionRunnable t = (ServerConnectionRunnable) iter.nextElement(); if (t.isRunning) { count++; } } } if (responderL2CAPServerThread != null) { count += responderL2CAPServerThread.countClientConnections(); } return count; } public static boolean setDiscoverable() { return setDiscoverable(DiscoveryAgent.GIAC); } public static String discoverableModeString(int mode) { if (DiscoveryAgent.GIAC == mode) { return "GIAC"; } else if (DiscoveryAgent.LIAC == mode) { return "LIAC"; } else if (DiscoveryAgent.NOT_DISCOVERABLE == mode) { return "NOT_DISCOVERABLE"; } else { return "0x" + Integer.toHexString(mode); } } public static boolean setDiscoverable(int mode) { try { LocalDevice localDevice = LocalDevice.getLocalDevice(); boolean rc = localDevice.setDiscoverable(mode); if (!rc) { Logger.error("Set Discoverable " + discoverableModeString(mode) + " " + rc); } else { Logger.debug("Set Discoverable " + discoverableModeString(mode) + " " + rc); } discoverable = true; discoverableStartTime = System.currentTimeMillis(); return true; } catch (Throwable e) { Logger.error("Set Discoverable error", e); return false; } } public static void setNotDiscoverable() { try { allServerDuration.add(TimeUtils.since(discoverableStartTime)); LocalDevice localDevice = LocalDevice.getLocalDevice(); localDevice.setDiscoverable(DiscoveryAgent.NOT_DISCOVERABLE); Logger.debug("Set Not Discoverable"); discoverable = false; } catch (Throwable e) { Logger.error("set NotDiscoverable error", e); } } public void shutdown() { Logger.info("shutdownServer"); stoped = true; if (RuntimeDetect.cldcStub != null) { RuntimeDetect.cldcStub.interruptThread(thread); } closeServer(); } public void updateServiceRecord() { if (serverConnection == null) { return; } try { ServiceRecord record = LocalDevice.getLocalDevice().getRecord(serverConnection); if (record != null) { updateVariableServiceRecord(record); LocalDevice.getLocalDevice().updateRecord(record); } } catch (Throwable e) { Logger.error("updateServiceRecord", e); } } static void updateVariableServiceRecord(ServiceRecord record) { int data; Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); data = 1 + calendar.get(Calendar.MINUTE); data = (updateVariableCount << 8) + Integer.parseInt(String.valueOf(data), 16); Logger.debug("Var info: " + Integer.toHexString(data)); record.setAttributeValue(Consts.VARIABLE_SERVICE_ATTRIBUTE_BYTES_ID, new DataElement(DataElement.U_INT_4, data)); updateVariableCount ++; if (updateVariableCount > 0xFF) { updateVariableCount = 0; } } static void buildServiceRecord(ServiceRecord record) throws ServiceRegistrationException { String id = ""; try { if (Configuration.useServiceClassExtUUID.booleanValue()) { DataElement serviceClassIDList = record.getAttributeValue(BluetoothTypesInfo.ServiceClassIDList); if (serviceClassIDList == null) { serviceClassIDList = new DataElement(DataElement.DATSEQ); } serviceClassIDList.addElement(new DataElement(DataElement.UUID, Consts.uuidSrvClassExt)); setAttributeValue(record, BluetoothTypesInfo.ServiceClassIDList, serviceClassIDList); } if (Configuration.testAllServiceAttributes.booleanValue()) { id = "all"; ServiceRecordTester.addAllTestServiceAttributes(record); return; } id = "pub"; buildServiceRecordPub(record); id = "int"; record.setAttributeValue(Consts.TEST_SERVICE_ATTRIBUTE_INT_ID, new DataElement( Consts.TEST_SERVICE_ATTRIBUTE_INT_TYPE, Consts.TEST_SERVICE_ATTRIBUTE_INT_VALUE)); id = "long"; record.setAttributeValue(Consts.TEST_SERVICE_ATTRIBUTE_LONG_ID, new DataElement( Consts.TEST_SERVICE_ATTRIBUTE_LONG_TYPE, Consts.TEST_SERVICE_ATTRIBUTE_LONG_VALUE)); if (!Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) { id = "str"; record.setAttributeValue(Consts.TEST_SERVICE_ATTRIBUTE_STR_ID, new DataElement(DataElement.STRING, Consts.TEST_SERVICE_ATTRIBUTE_STR_VALUE)); } id = "url"; record.setAttributeValue(Consts.TEST_SERVICE_ATTRIBUTE_URL_ID, new DataElement(DataElement.URL, Consts.TEST_SERVICE_ATTRIBUTE_URL_VALUE)); id = "bytes"; record.setAttributeValue(Consts.TEST_SERVICE_ATTRIBUTE_BYTES_ID, new DataElement( Consts.TEST_SERVICE_ATTRIBUTE_BYTES_TYPE, Consts.TEST_SERVICE_ATTRIBUTE_BYTES_VALUE)); id = "variable"; updateVariableServiceRecord(record); id = "info"; record.setAttributeValue(Consts.SERVICE_ATTRIBUTE_BYTES_SERVER_INFO, new DataElement(DataElement.URL, ServiceRecordTester.getBTSystemInfo())); id = "update"; // LocalDevice.getLocalDevice().updateRecord(record); } catch (Throwable e) { Logger.error("ServiceRecord " + id, e); } } static void setAttributeValue(ServiceRecord record, int attrID, DataElement attrValue) { try { if (!record.setAttributeValue(attrID, attrValue)) { Logger.error("SrvReg attrID=" + attrID); } } catch (Exception e) { Logger.error("SrvReg attrID=" + attrID, e); } } static void buildServiceRecordPub(ServiceRecord record) throws ServiceRegistrationException { final short UUID_PUBLICBROWSE_GROUP = 0x1002; final short ATTR_BROWSE_GRP_LIST = 0x0005; // Add the service to the 'Public Browse Group' DataElement browseClassIDList = new DataElement(DataElement.DATSEQ); UUID browseClassUUID = new UUID(UUID_PUBLICBROWSE_GROUP); browseClassIDList.addElement(new DataElement(DataElement.UUID, browseClassUUID)); setAttributeValue(record, ATTR_BROWSE_GRP_LIST, browseClassIDList); } }