001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.midp.jsr82emul;
027:
028: import java.io.IOException;
029: import java.util.Hashtable;
030: import java.util.Enumeration;
031:
032: import javax.microedition.io.SocketConnection;
033:
034: import com.sun.midp.security.SecurityToken;
035: import com.sun.midp.security.ImplicitlyTrustedClass;
036: import com.sun.midp.io.j2me.serversocket.Socket;
037: import com.sun.midp.main.Configuration;
038: import com.sun.midp.jsr082.BluetoothUtils;
039: import com.sun.midp.jsr082.SecurityInitializer;
040:
041: import javax.bluetooth.BluetoothConnectionException;
042:
043: /**
044: * Represents an emulation server used for JSR 82 emulation environment.
045: * It is not a part of JSR 82 implementation and is only used within
046: * JSR 82 emulation mode. The emulation mode allows running tests without
047: * real native Bluetooth libraries or hardware.
048: *
049: * In the emulation mode the server runs in a sepate thread or as a
050: * standalone application. It emulates Bluetooth ether, i.e. keeps
051: * information on services being advertised and "devices" that present.
052: *
053: * Actually it keeps TCP socket connections to clients.
054: */
055: public class EmulationServer implements Runnable {
056: /** Shows if emulation server is already running. */
057: private static boolean launched = false;
058:
059: /** The only instance of <code>EmulationServer</code>. */
060: private static EmulationServer instance = null;
061:
062: /**
063: * Inner class to request security token from SecurityInitializer.
064: * SecurityInitializer should be able to check this inner class name.
065: */
066: static private class SecurityTrusted implements
067: ImplicitlyTrustedClass {
068: };
069:
070: /** This class has a different security domain than the MIDlet suite. */
071: private static SecurityToken internalSecurityToken = SecurityInitializer
072: .requestToken(new SecurityTrusted());
073:
074: /** Server socket that accepts TCP clients connections. */
075: private static Socket serverSocket;
076:
077: // IMPL_NOTE: make it configurable.
078: /** Port to open server socket at. */
079: private static final int SOCKET_PORT = 1234;
080:
081: /**
082: * Duration of a delay that is applied prior to start inquiry in
083: * order to let all emulated devices update their device classes.
084: */
085: private static final int INQUIRY_DELAY = 2000;
086:
087: /** Keeps services being advertised in the ether. */
088: private Hashtable services = new Hashtable();
089:
090: /** Keeps devices that present in the ether. */
091: private Hashtable devices = new Hashtable();
092:
093: /** Default Bluetooth address wrapped by DeviceKey objects. */
094: private static final DeviceKey[] defaultAddr = {
095: new DeviceKey(
096: BluetoothUtils
097: .getAddressBytes(Configuration
098: .getProperty("com.sun.midp.jsr82emul.localBluetoothAddress"))),
099: new DeviceKey(
100: BluetoothUtils
101: .getAddressBytes(Configuration
102: .getProperty("com.sun.midp.jsr82emul.localBluetoothAddress2"))) };
103: /** Amount of default addresses. */
104: static final int ADDR_COUNT = 2;
105:
106: /** Value used to generate unique Bluetooth addresses. */
107: int nextAddr = 1;
108:
109: /**
110: * Launches the server in a standalone manner.
111: * @param args command line arguments
112: */
113: public static void main(String[] args) {
114: launch();
115: }
116:
117: /**
118: * Launches the server if not yet running.
119: */
120: public static synchronized void launch() {
121: if (launched) {
122: return;
123: }
124:
125: try {
126: serverSocket = new Socket();
127: serverSocket.open(SOCKET_PORT, internalSecurityToken);
128:
129: instance = new EmulationServer();
130: Thread thread = new Thread(instance);
131: thread.start();
132: } catch (IOException e) {
133: // server is already running within another isolate or under
134: // another VM control - nothing to do
135: } finally {
136: launched = true;
137: }
138: }
139:
140: /**
141: * Retrns the only instance if the server.
142: * @return the singleton instance of <code>EmulationServer</code>.
143: */
144: public static EmulationServer getInstance() {
145: launch();
146: return instance;
147: }
148:
149: /**
150: * The Runnable interface implementation.
151: */
152: public void run() {
153: while (true) {
154: try {
155: new ClientHandler((SocketConnection) serverSocket
156: .acceptAndOpen());
157: } catch (Throwable e) {
158: // ignoring
159: }
160: }
161: }
162:
163: /**
164: * Registers new device in the ether.
165: *
166: * @param deviceState state of device that defines its current discoverable
167: * mode and class of device
168: * @return Bluetooth addres for the registered device that identifies it
169: * in the emulated ether.
170: */
171: public synchronized byte[] registerDevice(DeviceState deviceState) {
172: DeviceKey key = null;
173: int i;
174:
175: for (i = 0; i < ADDR_COUNT; i++) {
176: if (!devices.containsKey(defaultAddr[i])) {
177: key = defaultAddr[i];
178: break;
179: }
180: }
181:
182: if (i == ADDR_COUNT) {
183: byte[] btaddr = new byte[Const.BTADDR_SIZE];
184: for (i = 0; i < 4; i++) {
185: btaddr[i] = (byte) (nextAddr >> (8 * i));
186: }
187: key = new DeviceKey(btaddr);
188: nextAddr = nextAddr % (Integer.MAX_VALUE - 1) + 1;
189: }
190:
191: devices.put(key, deviceState);
192: return key.getAddrBytes();
193: }
194:
195: /**
196: * Unregisters device from the emulated ether.
197: *
198: * @param btaddr Bluetooth address of the device to be unregistered
199: */
200: public synchronized void unregisterDevice(byte[] btaddr) {
201: devices.remove(new DeviceKey(btaddr));
202: }
203:
204: /**
205: * Performs inquiry (devices discovery).
206: * @param discoverable discoverable mode to search devices with
207: * @param btaddr Bluetooth address of device that performs inquiry - it
208: * should not discover itself.
209: * @return <code>InquiryResults</code> instance that represents inquiry
210: * results
211: */
212: public InquiryResults runInquiry(int discoverable, byte[] btaddr) {
213: try {
214: // Syncronizing classes of devices for the scenario like this:
215: // - make an action that should update class of device1;
216: // - start inquiry on device2 expecting updated class of device1.
217: // In this case class of device1 changes immediately locally but
218: // it takes time to update it on the emulation server and affect all
219: // inquiry operations. Waiting here to let class of device1 to become
220: // up-to-date.
221: Thread.sleep(INQUIRY_DELAY);
222: } catch (InterruptedException e) {
223: }
224:
225: InquiryResults res = new InquiryResults();
226: Enumeration addresses = devices.keys();
227: Enumeration states = devices.elements();
228:
229: while (addresses.hasMoreElements()) {
230: DeviceKey addr = (DeviceKey) addresses.nextElement();
231: DeviceState state = (DeviceState) states.nextElement();
232:
233: if (state.getDiscoverable() == discoverable
234: && !addr.equals(btaddr)) {
235: res.add(addr.getAddrBytes(), state.getCoD());
236: }
237: }
238:
239: return res;
240: }
241:
242: /**
243: * Registers a service in Bluetooth ether, i.e. starts advertising it.
244: *
245: * @param service service connection data provided by service holder
246: * @param btaddr Bluetooth address of service holder
247: * @return service key that identifies the service at emulation server,
248: * if registration succeeded, <code>null</code> otherwize
249: */
250: public ServiceKey registerService(ServiceConnectionData service,
251: byte[] btaddr) {
252:
253: ServiceKey key = new ServiceKey(btaddr, service.protocol,
254: service.port);
255:
256: Log.log("SERVER: registreing service " + key);
257:
258: ServiceConnectionData registered = (ServiceConnectionData) services
259: .get(key);
260:
261: if (registered != null) {
262: registered.setAccepting();
263: } else {
264: services.put(key, service);
265: }
266:
267: return key;
268: }
269:
270: /**
271: * Stops advertising the service represented by given record.
272: * @param key service key that represents the service to be unregistered
273: */
274: public void unregisterService(ServiceKey key) {
275: Log.log("SERVER: unregistreing service " + key);
276: services.remove(key);
277: }
278:
279: /**
280: * Retrieves emulation (TCP) connection URL for connecting to the
281: * service represented by given JSR 82 client connection string.
282: *
283: * @param client client connection parameters
284: * @return <code>ServiceConnectionData</code> instance that either
285: * contains connection properties or error code
286: */
287: public ServiceConnectionData connectToService(
288: ServiceConnectionData client) {
289:
290: ServiceKey key = new ServiceKey(client.address,
291: client.protocol, client.port);
292: ServiceConnectionData service = (ServiceConnectionData) services
293: .get(key);
294:
295: // Log.log("SERVER: requested connection to service " + key);
296:
297: if (service == null) {
298: // indicates error
299: client.error = BluetoothConnectionException.FAILED_NOINFO;
300: // Log.log("SERVER: service not found " + key);
301: } else {
302: // either accepts or sets error value
303: service.accept(client);
304: }
305:
306: return client;
307: }
308: }
309:
310: /**
311: * Represetnts key for services registry. A service is identified by
312: * Bluetooth address of host device, protocol type and PSM or channel
313: * id.
314: */
315: class ServiceKey {
316: /** Bluetooth address of the advertising server. */
317: DeviceKey btaddr;
318: /**
319: * Protocol this service is connectable thru. Is one of
320: * <code>BluetoothUrl.L2CAP, BluetoothUrl.RFCOMM</code
321: */
322: int protocol;
323: /** Channel in case of RFCOMM or PSM in case of L2CAP. */
324: int channelOrPsm;
325:
326: /**
327: * Constructs a key.
328: * @param btaddr value for <code>bluetoothAddress</code>
329: * @param protocol value for <code>protocol</code>
330: * @param channelOrPsm value for <code>channelOrPsm</code>
331: */
332: ServiceKey(byte[] btaddr, int protocol, int channelOrPsm) {
333: this .btaddr = new DeviceKey(btaddr);
334: this .protocol = protocol;
335: this .channelOrPsm = channelOrPsm;
336: }
337:
338: /**
339: * Checks if this key is equivalent to given one.
340: * @param obj a <code>ServiceKey</code> instance to be compared
341: * to this one
342: * @return true if this key is equal to the given one,
343: * false otherwise.
344: */
345: public boolean equals(Object obj) {
346: ServiceKey rec = (ServiceKey) obj;
347:
348: return btaddr.equals(rec.btaddr) && protocol == rec.protocol
349: && channelOrPsm == rec.channelOrPsm;
350: }
351:
352: /**
353: * Returns a proper hash code for using in Hashtable.
354: * @return hash code value for this instance
355: */
356: public int hashCode() {
357: return channelOrPsm;
358: }
359: }
360:
361: /**
362: * Wrapper for bluetooth address bytes that allows keeping
363: * and comparing Bluetooth addresses. Used as a device key
364: * in Hashtable.
365: */
366: class DeviceKey {
367: /** Bluetooth addres. */
368: private final byte[] addr = new byte[Const.BTADDR_SIZE];
369:
370: /**
371: * Creates instance with given addres.
372: * @param addr Bluetooth address
373: */
374: public DeviceKey(byte[] addr) {
375: System.arraycopy(addr, 0, this .addr, 0, Const.BTADDR_SIZE);
376: }
377:
378: /**
379: * Returns a proper hash code for using in Hashtable.
380: * @return hash code value for this instance
381: */
382: public int hashCode() {
383: return addr[0];
384: }
385:
386: /**
387: * Checks if this instance and given one wrap the same address.
388: * @param obj object to check for equity
389: * @return true if given object represents the same address,
390: * false otherwise
391: */
392: public boolean equals(Object obj) {
393: return equals(((DeviceKey) obj).addr);
394: }
395:
396: /**
397: * Checks if this instance wraps the same address as given one.
398: * @param addr Bluetooth address to check for equity
399: * @return true if addresses are equal, false otherwise
400: */
401: public boolean equals(byte[] addr) {
402: for (int i = 0; i < Const.BTADDR_SIZE; i++) {
403: if (addr[i] != this .addr[i]) {
404: return false;
405: }
406: }
407:
408: return true;
409: }
410:
411: /**
412: * Returns bytes representation of wrapped Bluetooth address.
413: * @return Bluetooth address bytes
414: */
415: public byte[] getAddrBytes() {
416: return addr;
417: }
418: }
|