/**
* 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.IOException;
import java.io.InterruptedIOException;
import java.util.Enumeration;
import java.util.Vector;
import javax.bluetooth.L2CAPConnection;
import javax.bluetooth.L2CAPConnectionNotifier;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.microedition.io.Connector;
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.util.BluetoothTypesInfo;
import net.sf.bluecove.util.CollectionUtils;
/**
*
*/
public class TestResponderServerL2CAP extends Thread {
private Object threadLocalBluetoothStack;
private L2CAPConnectionNotifier serverConnection;
private boolean isStoped = false;
private boolean isRunning = false;
private Vector concurrentConnectionsThreads = new Vector();
private class ServerConnectionTread extends Thread {
L2CAPConnection channel;
boolean isRunning;
ServerConnectionTread(L2CAPConnection channel) {
this.channel = channel;
this.isRunning = true;
synchronized (concurrentConnectionsThreads) {
concurrentConnectionsThreads.addElement(this);
}
}
public void run() {
try {
receive(channel);
if ((!isStoped) && (isRunning)) {
try {
Thread.sleep(Configuration.serverSleepB4ClosingConnection);
} catch (InterruptedException e) {
}
}
} finally {
this.isRunning = false;
IOUtils.closeQuietly(channel);
synchronized (concurrentConnectionsThreads) {
concurrentConnectionsThreads.removeElement(this);
}
}
}
void shutdown() {
if (isRunning) {
IOUtils.closeQuietly(channel);
}
}
}
private TestResponderServerL2CAP() {
threadLocalBluetoothStack = Configuration.threadLocalBluetoothStack;
}
public static TestResponderServerL2CAP startServer() {
TestResponderServerL2CAP srv = new TestResponderServerL2CAP();
srv.start();
return srv;
}
public boolean isRunning() {
return isRunning;
}
public void run() {
isStoped = false;
try {
runCreateConnectionNotifier();
} catch (Throwable e) {
Logger.error("L2CAP Server start error", e);
isStoped = true;
return;
}
try {
runAcceptAndOpen();
} catch (Throwable e) {
Logger.error("L2CAP Server run error", e);
} finally {
close();
Logger.info("L2CAP Server finished! " + TimeUtils.timeNowToString());
isRunning = false;
}
}
private void runCreateConnectionNotifier() throws IOException {
RuntimeDetect.cldcStub.setThreadLocalBluetoothStack(threadLocalBluetoothStack);
StringBuffer url = new StringBuffer(BluetoothTypesInfo.PROTOCOL_SCHEME_L2CAP);
url.append("://localhost:").append(Configuration.blueCoveL2CAPUUID());
url.append(";name=").append(Consts.RESPONDER_SERVERNAME).append("_l2");
if (Configuration.useShortUUID.booleanValue()) {
url.append("s");
}
url.append(Configuration.serverURLParams());
url.append(";TransmitMTU=").append(TestResponderCommon.receiveMTU_max);
url.append(";ReceiveMTU=").append(TestResponderCommon.receiveMTU_max);
if ((RuntimeDetect.isBlueCove) && (Configuration.bluecovepsm != null)
&& (Configuration.bluecovepsm.length() > 0)) {
url.append(";bluecovepsm=").append(Configuration.bluecovepsm);
}
serverConnection = (L2CAPConnectionNotifier) Connector.open(url.toString());
if (Configuration.testServiceAttributes.booleanValue()) {
ServiceRecord record = LocalDevice.getLocalDevice().getRecord(serverConnection);
if (record == null) {
Logger.warn("Bluetooth ServiceRecord is null");
} else {
TestResponderServer.buildServiceRecord(record);
try {
LocalDevice.getLocalDevice().updateRecord(record);
Logger.debug("L2CAP ServiceRecord updated");
} catch (Throwable e) {
Logger.error("L2CAP Service Record update error", e);
}
}
}
}
private void runAcceptAndOpen() {
int errorCount = 0;
isRunning = true;
boolean showServiceRecordOnce = true;
serviceRunLoop: while (!isStoped) {
L2CAPConnection channel;
try {
Logger.info("Accepting L2CAP connections");
if (showServiceRecordOnce) {
Logger.debug("L2Url:"
+ LocalDevice.getLocalDevice().getRecord(serverConnection).getConnectionURL(
Configuration.getRequiredSecurity(), false));
showServiceRecordOnce = false;
}
channel = serverConnection.acceptAndOpen();
} catch (InterruptedIOException e) {
isStoped = true;
break;
} catch (Throwable e) {
Logger.error("acceptAndOpen ", e);
if (!(isStoped) && (errorCount > 3)) {
isStoped = true;
Logger.error("L2CAP Server stoped, too many errors");
}
if (isStoped) {
break serviceRunLoop;
}
errorCount++;
continue;
}
errorCount = 0;
Logger.info("Received L2CAP connection");
if (!Configuration.serverAcceptWhileConnected.booleanValue()) {
try {
receive(channel);
if (!isStoped) {
try {
Thread.sleep(Configuration.serverSleepB4ClosingConnection);
} catch (InterruptedException e) {
}
}
} finally {
IOUtils.closeQuietly(channel);
}
} else {
ServerConnectionTread t = new ServerConnectionTread(channel);
t.start();
}
}
}
void receive(L2CAPConnection channel) {
try {
int receiveLengthMax = channel.getReceiveMTU();
byte[] buffer = new byte[receiveLengthMax];
int receivedLength = channel.receive(buffer);
if (receivedLength == 0) {
Logger.debug("a zero length L2CAP packet is received");
} else {
Logger.debug("received L2CAP packet", buffer, 0, receivedLength);
processData(channel, buffer, receivedLength);
}
} catch (Throwable e) {
if (isStoped) {
return;
}
Logger.error("L2CAP receive", e);
}
}
private void processData(L2CAPConnection channel, byte[] buffer, int receivedLength) throws IOException {
if ((receivedLength < 3) || (buffer[0] != Consts.SEND_TEST_START)) {
Logger.debug("not a test client connected, will echo");
runEcho(channel, buffer, receivedLength);
return;
}
int testType = buffer[1];
TestStatus testStatus = new TestStatus(testType);
ConnectionHolderL2CAP c = new ConnectionHolderL2CAP(channel);
TestTimeOutMonitor monitorConnection = TestTimeOutMonitor.create("test" + testType, c,
Configuration.serverTestTimeOutSec);
byte[] initialData = new byte[receivedLength - CommunicationTesterL2CAP.INITIAL_DATA_PREFIX_LEN];
System.arraycopy(buffer, CommunicationTesterL2CAP.INITIAL_DATA_PREFIX_LEN, initialData, 0, receivedLength
- CommunicationTesterL2CAP.INITIAL_DATA_PREFIX_LEN);
try {
CommunicationTesterL2CAP.runTest(testType, true, c, initialData, testStatus);
TestResponderServer.countSuccess++;
Logger.debug("Test# " + testType + " " + testStatus.getName() + " ok");
} catch (Throwable e) {
if (!isStoped) {
TestResponderServer.failure.addFailure("test " + testType + " " + testStatus.getName(), e);
}
Logger.error("Test# " + testType + " " + testStatus.getName() + " error", e);
} finally {
monitorConnection.finish();
}
}
private void runEcho(L2CAPConnection channel, byte[] buffer, int receivedLength) throws IOException {
RemoteDevice device = RemoteDevice.getRemoteDevice(channel);
boolean authorized = false;
try {
authorized = device.isAuthorized(channel);
} catch (Throwable blucoveIgnoe) {
}
Logger.debug("connected:" + device.getBluetoothAddress() + (device.isAuthenticated() ? " Auth" : "")
+ (authorized ? " Authz" : "") + (device.isEncrypted() ? " Encr" : ""));
Logger.debug("ReceiveMTU=" + channel.getReceiveMTU() + " TransmitMTU=" + channel.getTransmitMTU());
echo(channel, buffer, receivedLength);
mainLoop: while (true) {
while (!channel.ready()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break mainLoop;
}
}
int receiveMTU = channel.getReceiveMTU();
byte[] data = new byte[receiveMTU];
int length = channel.receive(data);
echo(channel, data, length);
}
}
private void echo(L2CAPConnection channel, byte[] buffer, int receivedLength) throws IOException {
boolean cBufHasBinary = false;
int messageLength = receivedLength;
for (int k = 0; k < receivedLength; k++) {
char c = (char) buffer[k];
if (c < ' ') {
if ((c == '\n') && (k == receivedLength - 1)) {
messageLength = receivedLength - 1;
break;
}
cBufHasBinary = true;
break;
}
}
String message;
if (messageLength != 0) {
message = new String(buffer, 0, messageLength);
} else {
message = "";
}
StringBuffer buf = new StringBuffer(message);
if (cBufHasBinary) {
buf.append(" [");
for (int k = 0; k < receivedLength; k++) {
buf.append(Integer.toHexString(buffer[k])).append(' ');
}
buf.append("]");
}
buf.append(" (").append(receivedLength).append(")");
Logger.debug("|" + buf.toString());
byte[] reply = new byte[receivedLength];
if (receivedLength != 0) {
System.arraycopy(buffer, 0, reply, 0, receivedLength);
}
channel.send(reply);
}
void close() {
try {
if (serverConnection != null) {
serverConnection.close();
}
Logger.debug("L2CAP ServerConnection closed");
} catch (Throwable e) {
Logger.error("L2CAP Server stop error", e);
}
}
void closeServer() {
isStoped = true;
close();
}
public void closeServerClientConnections() {
Vector copy = CollectionUtils.copy(concurrentConnectionsThreads);
for (Enumeration iter = copy.elements(); iter.hasMoreElements();) {
ServerConnectionTread t = (ServerConnectionTread) iter.nextElement();
t.shutdown();
}
}
public int countClientConnections() {
int count = 0;
synchronized (concurrentConnectionsThreads) {
for (Enumeration iter = concurrentConnectionsThreads.elements(); iter.hasMoreElements();) {
ServerConnectionTread t = (ServerConnectionTread) iter.nextElement();
if (t.isRunning) {
count++;
}
}
}
return count;
}
}