FileDocCategorySizeDatePackage
EmulationServer.javaAPI DocphoneME MR2 API (J2ME)13942Wed May 02 18:00:32 BST 2007com.sun.midp.jsr82emul

EmulationServer.java

/*
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package com.sun.midp.jsr82emul;

import java.io.IOException;
import java.util.Hashtable;
import java.util.Enumeration;

import javax.microedition.io.SocketConnection;

import com.sun.midp.security.SecurityToken;
import com.sun.midp.security.ImplicitlyTrustedClass;
import com.sun.midp.io.j2me.serversocket.Socket;
import com.sun.midp.main.Configuration;
import com.sun.midp.jsr082.BluetoothUtils;
import com.sun.midp.jsr082.SecurityInitializer;

import javax.bluetooth.BluetoothConnectionException;

/**
 * Represents an emulation server used for JSR 82 emulation environment. 
 * It is not a part of JSR 82 implementation and is only used within 
 * JSR 82 emulation mode. The emulation mode allows running tests without
 * real native Bluetooth libraries or hardware.
 * 
 * In the emulation mode the server runs in a sepate thread or as a 
 * standalone application. It emulates Bluetooth ether, i.e. keeps 
 * information on services being advertised and "devices" that present.
 *
 * Actually it keeps TCP socket connections to clients.
 */
public class EmulationServer implements Runnable {
    /** Shows if emulation server is already running. */
    private static boolean launched = false;
    
    /** The only instance of <code>EmulationServer</code>. */
    private static EmulationServer instance = null;

    /**
     * Inner class to request security token from SecurityInitializer.
     * SecurityInitializer should be able to check this inner class name.
     */
    static private class SecurityTrusted
        implements ImplicitlyTrustedClass {};

    /** This class has a different security domain than the MIDlet suite. */
    private static SecurityToken internalSecurityToken =
        SecurityInitializer.requestToken(new SecurityTrusted());
    
    /** Server socket that accepts TCP clients connections. */
    private static Socket serverSocket;
    
    // IMPL_NOTE: make it configurable.
    /** Port to open server socket at. */
    private static final int SOCKET_PORT = 1234;
    
    /** 
     * Duration of a delay that is applied prior to start inquiry in 
     * order to let all emulated devices update their device classes.
     */
    private static final int INQUIRY_DELAY = 2000;
    
    /** Keeps services being advertised in the ether. */
    private Hashtable services = new Hashtable();
    
    /** Keeps devices that present in the ether. */
    private Hashtable devices = new Hashtable();
    
    /** Default Bluetooth address wrapped by DeviceKey objects. */
    private static final DeviceKey[] defaultAddr = {
        new DeviceKey(BluetoothUtils.getAddressBytes(Configuration.getProperty(
            "com.sun.midp.jsr82emul.localBluetoothAddress"))),
        new DeviceKey(BluetoothUtils.getAddressBytes(Configuration.getProperty(
            "com.sun.midp.jsr82emul.localBluetoothAddress2"))) };
    /** Amount of default addresses. */
    static final int ADDR_COUNT = 2;
    
    /** Value used to generate unique Bluetooth addresses. */
    int nextAddr = 1;
    
    /**
     * Launches the server in a standalone manner.
     * @param args command line arguments
     */
    public static void main(String[] args) {
        launch();
    }
    
    /**
     * Launches the server if not yet running.
     */
    public static synchronized void launch() {
        if (launched) {
            return;
        }
        
        try {
            serverSocket = new Socket();
            serverSocket.open(SOCKET_PORT, internalSecurityToken);
            
            instance = new EmulationServer();
            Thread thread = new Thread(instance);
            thread.start();        
        } catch (IOException e) {
            // server is already running within another isolate or under
            // another VM control - nothing to do
        } finally {
            launched = true;
        }
    }
    
    /** 
     * Retrns the only instance if the server. 
     * @return the singleton instance of <code>EmulationServer</code>.
     */
    public static EmulationServer getInstance() {
        launch();
        return instance;
    }

    /**
     * The Runnable interface implementation.
     */
    public void run() {
        while (true) {
            try {
                new ClientHandler(
                        (SocketConnection)serverSocket.acceptAndOpen());
            } catch (Throwable e) {
                // ignoring
            }
        }
    }
    
    /** 
     * Registers new device in the ether.
     *
     * @param deviceState state of device that defines its current discoverable
     * mode and class of device
     * @return Bluetooth addres for the registered device that identifies it 
     * in the emulated ether. 
     */
    public synchronized byte[] registerDevice(DeviceState deviceState) {
        DeviceKey key = null;
        int i;
        
        for (i = 0; i < ADDR_COUNT; i++) {
            if (!devices.containsKey(defaultAddr[i])) {
                key = defaultAddr[i];
                break;
            }
        }
        
        if (i == ADDR_COUNT) {
            byte[] btaddr = new byte[Const.BTADDR_SIZE];
            for (i = 0; i < 4; i++) {
                btaddr[i] = (byte) (nextAddr >> (8 * i));
            }
            key = new DeviceKey(btaddr);
            nextAddr = nextAddr % (Integer.MAX_VALUE - 1) + 1;
        }
        
        devices.put(key, deviceState);
        return key.getAddrBytes();
    }
    
    /**
     * Unregisters device from the emulated ether.
     *
     * @param btaddr Bluetooth address of the device to be unregistered
     */
    public synchronized void unregisterDevice(byte[] btaddr) {
        devices.remove(new DeviceKey(btaddr));
    }
    
    /**
     * Performs inquiry (devices discovery).
     * @param discoverable discoverable mode to search devices with
     * @param btaddr Bluetooth address of device that performs inquiry - it
     *        should not discover itself.
     * @return <code>InquiryResults</code> instance that represents inquiry
     *        results
     */
    public InquiryResults runInquiry(int discoverable, byte[] btaddr) {
        try {
            // Syncronizing classes of devices for the scenario like this:
            //     - make an action that should update class of device1;
            //     - start inquiry on device2 expecting updated class of device1.
            // In this case class of device1 changes immediately locally but
            // it takes time to update it on the emulation server and affect all
            // inquiry operations. Waiting here to let class of device1 to become
            // up-to-date. 
            Thread.sleep(INQUIRY_DELAY);
        } catch (InterruptedException e) {}
        
        InquiryResults res = new InquiryResults();
        Enumeration addresses = devices.keys();
        Enumeration states = devices.elements();
                    
        while (addresses.hasMoreElements()) {
            DeviceKey addr = (DeviceKey) addresses.nextElement();
            DeviceState state = (DeviceState) states.nextElement();
             
            if (state.getDiscoverable() == discoverable && 
                    !addr.equals(btaddr)) {
                res.add(addr.getAddrBytes(), state.getCoD());
            }
        }
        
        return res;
    }
    
    /** 
     * Registers a service in Bluetooth ether, i.e. starts advertising it.
     *
     * @param service service connection data provided by service holder
     * @param btaddr Bluetooth address of service holder
     * @return service key that identifies the service at emulation server,
     *        if registration succeeded, <code>null</code> otherwize
     */
    public ServiceKey registerService(
            ServiceConnectionData service, byte[] btaddr) {
        
        ServiceKey key = new ServiceKey(
            btaddr, service.protocol, service.port);
        
        Log.log("SERVER: registreing service " + key);
        
        ServiceConnectionData registered = 
            (ServiceConnectionData) services.get(key);
            
        if (registered != null) {
            registered.setAccepting();
        } else {
            services.put(key, service);
        }
        
        return key;
    }
    
    /** 
     * Stops advertising the service represented by given record.
     * @param key service key that represents the service to be unregistered
     */
    public void unregisterService(ServiceKey key) {
        Log.log("SERVER: unregistreing service " + key);
        services.remove(key);
    }
    
    /** 
     * Retrieves emulation (TCP) connection URL for connecting to the
     * service represented by given JSR 82 client connection string.
     *
     * @param client client connection parameters
     * @return <code>ServiceConnectionData</code> instance that either
     *         contains connection properties or error code
     */
    public ServiceConnectionData connectToService(
            ServiceConnectionData client) {
        
        ServiceKey key = new ServiceKey(client.address, 
            client.protocol, client.port);
        ServiceConnectionData service = 
            (ServiceConnectionData) services.get(key);
        
        // Log.log("SERVER: requested connection to service " + key);
        
        if (service == null) {
            // indicates error
            client.error = BluetoothConnectionException.FAILED_NOINFO;
            // Log.log("SERVER: service not found " + key);
        } else {
            // either accepts or sets error value
            service.accept(client);
        }
        
        return client;
    }
}

/**
 * Represetnts key for services registry. A service is identified by
 * Bluetooth address of host device, protocol type and PSM or channel
 * id.
 */
class ServiceKey {
    /** Bluetooth address of the advertising server. */
    DeviceKey btaddr;
    /** 
     * Protocol this service is connectable thru. Is one of 
     * <code>BluetoothUrl.L2CAP, BluetoothUrl.RFCOMM</code
     */
    int protocol;
    /** Channel in case of RFCOMM or PSM in case of L2CAP. */
    int channelOrPsm;
    
    /** 
     * Constructs a key.
     * @param btaddr value for <code>bluetoothAddress</code>
     * @param protocol value for <code>protocol</code>
     * @param channelOrPsm value for <code>channelOrPsm</code> 
     */
    ServiceKey(byte[] btaddr, int protocol, int channelOrPsm) {
        this.btaddr = new DeviceKey(btaddr);
        this.protocol = protocol;
        this.channelOrPsm = channelOrPsm;
    }
        
    /** 
     * Checks if this key is equivalent to given one.
     * @param obj a <code>ServiceKey</code> instance to be compared
     *        to this one
     * @return true if this key is equal to the given one,
     *        false otherwise.
     */
    public boolean equals(Object obj) {
        ServiceKey rec = (ServiceKey) obj; 
        
        return btaddr.equals(rec.btaddr) &&
            protocol == rec.protocol &&
            channelOrPsm == rec.channelOrPsm;
        }
        
    /** 
     * Returns a proper hash code for using in Hashtable.
     * @return hash code value for this instance
     */
    public int hashCode() {
        return channelOrPsm;
    }
}
    
/** 
 * Wrapper for bluetooth address bytes that allows keeping 
 * and comparing Bluetooth addresses. Used as a device key
 * in Hashtable.
 */
class DeviceKey {
    /** Bluetooth addres. */
    private final byte[] addr = new byte[Const.BTADDR_SIZE];
    
    /**
     * Creates instance with given addres. 
     * @param addr Bluetooth address
     */
    public DeviceKey(byte[] addr) {
        System.arraycopy(addr, 0, this.addr, 0, Const.BTADDR_SIZE);
    }
    
    /** 
     * Returns a proper hash code for using in Hashtable.
     * @return hash code value for this instance
     */
    public int hashCode() {
        return addr[0];
    }
    
    /** 
     * Checks if this instance and given one wrap the same address. 
     * @param obj object to check for equity
     * @return true if given object represents the same address,
     *        false otherwise
     */
    public boolean equals(Object obj) {
        return equals(((DeviceKey)obj).addr);
    }
        
    /** 
     * Checks if this instance wraps the same address as given one. 
     * @param addr Bluetooth address to check for equity
     * @return true if addresses are equal, false otherwise
     */
    public boolean equals(byte[] addr) {
        for (int i = 0; i < Const.BTADDR_SIZE; i++) {
            if (addr[i] != this.addr[i]) {
                return false;
            }
        }
        
        return true;        
    }
    
    /** 
     * Returns bytes representation of wrapped Bluetooth address. 
     * @return Bluetooth address bytes
     */
    public byte[] getAddrBytes() {
        return addr;
    }
}