FileDocCategorySizeDatePackage
ConnectionEmul.javaAPI DocphoneME MR2 API (J2ME)22219Wed May 02 18:00:30 BST 2007com.sun.midp.jsr82emul

ConnectionEmul.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.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.DataInputStream;
import javax.microedition.io.SocketConnection;
import javax.microedition.io.Connector;
import javax.bluetooth.BluetoothConnectionException;
import com.sun.midp.io.BluetoothUrl;
import com.sun.midp.jsr082.BluetoothUtils;
import com.sun.kvem.jsr082.bluetooth.BCC;

/**
 * Emulates JSR 82 connection.
 */
public class ConnectionEmul extends EmulationClient 
        implements EmulUnit {
    /** Processes open request in a separate thread. */
    class Opener extends RunnableProcessor {
        /** Processes open request. */
        protected void process() {
            try {
                open();
            } catch (IOException e) {
                Log.log("Processing CONN_OPEN FAILED "); 
                notifyOpen(handle, false, -1, -1); 
                close();
            }
            
            if (!interrupted) {
                notifyOpen(handle, true, serviceData.receiveMTU, 
                    serviceData.transmitMTU);
            }
            opener = null;
        }
    }    
   
    /** Options of service to connect to. */
    ServiceConnectionData serviceData;
    
    /** Opener object needed to perform open operation in a separate thread. */
    private Opener opener;
    /** Socket connection that emulates JSR82 one. */
    private SocketConnection socketConnection;
    /** Protocol dependent data sener. */
    private Sender sender;
    /** Protocol dependent data receiver. */
    private Receiver receiver;
        
    /** Handle that identifies this connection emulation. */
    public int handle;
    
    /**
     * Opens client-side connection. Requests connection with given client
     * connection string from emulation server
     *
     * @return handle value
     * @exception IOException if communication to emulation server 
     *        or a service fails.
     */
    public int open() throws IOException {
        ServiceConnectionData connData;
        
        connect();
        
        messenger.sendBytes(toServer, Messenger.CONNECT_TO_SERVICE, 
            serviceData.toByteArray(
            ServiceConnectionData.CONN_REQUEST_DATA));
        
        messenger.receive(fromServer);
        
        if (messenger.getCode() != Messenger.SERVICE_AT) {
            throw new EmulationException(
                "Unexpected emulation server response " + 
                messenger.getCode());
        }
            
        connData = new ServiceConnectionData(
            messenger.getBytes(), ServiceConnectionData.CONNECTION_DATA);
        
        // no need to communicate with emulation sever any more
        // since a direct connection is open
        disconnect();
        
        if (connData.error != -1) {
            throw new BluetoothConnectionException(connData.error);
        }
        
        // vice versa to server side properties
        serviceData.receiveMTU = connData.transmitMTU;
        serviceData.transmitMTU = connData.receiveMTU;
        
        // applying bitwise operation to get unsigned values from bytes
        socketConnection = (SocketConnection) 
            new com.sun.midp.io.j2me.socket.Protocol().openPrim(
                internalSecurityToken, "//" +
                    (connData.address[0] & 0xff) + "." +
                    (connData.address[1] & 0xff) + "." +
                    (connData.address[2] & 0xff) + "." +
                    (connData.address[3] & 0xff) +
                    ":" + connData.socketPort);
        
        connData.address = DeviceEmul.getLocalDeviceEmul().address;
        sender = Sender.create(socketConnection, 
                    serviceData.protocol, handle);
        
        // It is important to use blocking sender method here
        sender.plainSend(connData.toByteArray(
            ServiceConnectionData.CLIENT_DATA));
        
        return handle;
    }

    /**
     * Opens server-side connection, i.e. assigns this emulation with already 
     * open server-side socket connection. 
     * @param socketConnection already open server-side socket connection.
     * @return handle value
     * @exception IOException if communication to emulation server 
     *        or a service fails.
     */
    public int open(SocketConnection socketConnection) 
            throws IOException {
        
        this.socketConnection = socketConnection;
        
        receiver = Receiver.create(socketConnection, 
                    serviceData.protocol, handle);
        
        // Calling blocking method intentionally here
        ServiceConnectionData connData = new ServiceConnectionData(
            receiver.forceReceive(ServiceConnectionData.CLIENT_DATA_SIZE),
            ServiceConnectionData.CLIENT_DATA);
        
        serviceData.receiveMTU = connData.receiveMTU;
        serviceData.transmitMTU = connData.transmitMTU;
        serviceData.address = connData.address;
        
        return handle;
    }

    /** Close this connection emulation. */
    public void close() {
        if (opener != null) {
            opener.interrupt();
            opener = null;
        }
        
        if (sender != null) {
            sender.close();
            sender = null;
        }
        
        if (receiver != null) {
            receiver.close();
            receiver = null;
        }
        
        if (socketConnection != null) {
            try {
                socketConnection.close();
            } catch (IOException e) {
                // ignoring
            }
            socketConnection = null;
        }
       
        ConnRegistry.getInstance().unregister(handle);
    }
    
    /**
     * Sends data to the other side of the emulated connection.
     *
     * @param len amount of bytes to send; given amount of bytes are
     *        sent from output buffer.
     * @exception IOException  if an I/O error occurs
     */
    private void send(int len) {
        byte[] buf = new byte[len];
        
        if (getOutData(handle, buf)) {
            if (sender == null) {
                sender = Sender.create(socketConnection, 
                    serviceData.protocol, handle);
            }
            sender.send(buf);
        }
        
        buf = null;
    }

    /**
     * Reads data from emulated connection.
     */
    private void receive() {
        if (receiver == null) {
            receiver = Receiver.create(socketConnection, 
                    serviceData.protocol, handle);
        }
        
        receiver.receive();
    }
    
    /** 
     * Returns actually established ReceiveMTU.
     * @return established ReceiveMTU.
     */
    public int getReceiveMTU() {
        return serviceData.receiveMTU;
    }
    
    /** 
     * Returns actually established TransmitMTU.
     * @return established TransmitMTU.
     */
    public int getTransmitMTU() {
        return serviceData.transmitMTU;
    }
    
    /** 
     * Returns Bluetooth address of other side of this connection.
     * @return Bluetooth address of the other side
     */
    public String getRemoteAddress() {
        return BluetoothUtils.getAddressString(serviceData.address);
    }
    
    /**
     * Constructs an instance with given handle. Handle is an 
     * integer value that identifies correspondence between connections
     * in porting layer nd ConnectionEmul instances. In case of client 
     * side connection handle is already defined in native 
     * <code>create_client()</code> functions. In case of server side
     * it is generated in another constructor.
     *
     * @param handle handle receieved form native layer.
     */
    public ConnectionEmul(int handle) {
        this.handle = handle;
        ConnRegistry.getInstance().register(this.handle, this);
    }
    
    /**
     * Constructs an instance at server side.
     * @param serviceData connection options passed by notifier that 
     *        creates this connection.
     */
    public ConnectionEmul(ServiceConnectionData serviceData) {
        this(getHandle(serviceData.protocol));
        this.serviceData = serviceData;
    }
    
    /** Request code for Open operation. */
    final static int CONN_OPEN = 0;
    /** Request code for Close operation. */
    final static int CONN_CLOSE = 1; 
    /** Request code for Init operation. */
    final static int CONN_INIT = 2;
    /** Request code for Send operation. */
    final static int CONN_SEND = 3;
    /** Request code for Receive operation. */
    final static int CONN_RECEIVE = 4;
    
    /**
     * Processes request from porting layer to emulation.
     * @param request packed request to process
     */
    public void process(BytePack request) {
        
        switch (request.extract()) {
        case CONN_OPEN:
            Log.log("Processing CONN_OPEN " + handle); 
            serviceData.address = request.extractBytes(Const.BTADDR_SIZE);
            serviceData.port = request.extractInt();
            opener = new Opener();
            opener.start();
            break;
            
        case CONN_CLOSE:
            Log.log("Processing CONN_CLOSE " + handle); 
            close();
            break;
        
        case CONN_INIT:
            Log.log("Processing CONN_INIT " + handle); 
            serviceData = new ServiceConnectionData(
                request.extractBytes(ServiceConnectionData.SERVER_DATA_SIZE),
                ServiceConnectionData.SERVER_DATA);
            break;
        
        case CONN_SEND:
            Log.log("Processing CONN_SEND " + handle); 
            send(request.extractInt());
            break;
        
        case CONN_RECEIVE:
            Log.log("Processing CONN_RECEIVE " + handle); 
            receive();
            break;
            
        default:
            Log.log("Processing CONN_UNKNOWN " + handle); 
            throw new EmulationException("Unknown connection request");
        }
    }

    /**
     * Retrieves new handle for a newly created connection at server side.
     * @param protocol integer protocol identifier
     * @return integral handle for a newly created connection
     */
    private static native int getHandle(int protocol);
    
    /**
     * Retrieves bytes from output native buffer for sending.
     * @param handle this connection handle
     * @param buf buffer to copy data to, it is implied that
     *        exactly <code>buf.length</code> to be sent from
     *        the native buffer
     * @return <code>true</code> if there is data to be sent,
     *         <code>false<code> otherwise
     */
    private static native boolean getOutData(int handle, byte[] buf);
    
    /** 
     * Notifies porting layer on receive operation completion.
     * @param handle connection handle
     * @param bytes bytes received
     * @param len amount of bytes received, <code>Const.CONN_FAILURE</code> 
     *        if receiving failed, <code>Const.CONN_ENDOF_INP</code> if
     *        there is nothing to receive due reaching end of input stream.
     */
    static native void notifyReceived(int handle, byte[] bytes, int len);
    
    /** 
     * Notifies porting layer on send operation completion.
     * @param handle connection handle
     * @param len amount of bytes sent, <code>CONN_FAILURE</code> if sending
     *        failed
     */
    static native void notifySent(int handle, int len);
    
    /** 
     * Notifies porting layer on open operation completion.
     * @param handle this connection handle
     * @param success <code>true</code> if opened successfully, 
     *        <code>false</code> otherwize
     * @param receiveMTU receiveMTU of L2CAP connection established,
     *        ignored for RFCOMM
     * @param transmitMTU transmitMTU of L2CAP connection established,
     *        ignored for RFCOMM
     */
    private static native void notifyOpen(int handle, boolean success,
        int receiveMTU, int transmitMTU);
}

/**
 * Implements simple send methods that just call 
 * proper stream methods from a separate thread.
 */
class Sender extends RunnableProcessor {
    /** Socket output stream. */
    private OutputStream out;
    /** Data being sent. */
    protected byte[] outbuf;
    /** Emulated connection handle. */
    int handle;
    
    /**
     * Creates proper instance depending on protocol
     * @param protocol protocol (as in <code>BluetoothUrl</code>) to create
     *        sernder for
     * @param socketConnection open socet connection to use for sending
     * @param handle handle of emulated connection to create sender for
     * @return instance created
     */
    static Sender create(SocketConnection socketConnection, int protocol,
                int handle) {
        
        Sender res = null;
        try {
            res = protocol == BluetoothUrl.L2CAP ? 
                new L2CAPSender(socketConnection, handle) :
                new Sender(socketConnection, handle);
        } catch (IOException e) {
            ConnectionEmul.notifySent(handle, Const.CONN_FAILURE);
        }
        
        return res;
    }
    
    /**
     * Initiates streams.
     * @param socketConnection open socet connection to use. 
     * @param handle handle of emulated connection this sender works for
     * Exception IOException if I/O error occured while opening streams
     */
    Sender(SocketConnection socketConnection, int handle) throws IOException {
        super(true);
        this.handle = handle;
        out = socketConnection.openDataOutputStream();
    }
    
    /** Processes send request. */
    public void process() {
        try {
            sendImpl();
        } catch (IOException e) {
            outbuf = null;
        }
        
        if (!interrupted) {
            int sent = outbuf == null ? Const.CONN_FAILURE : outbuf.length;
            ConnectionEmul.notifySent(handle, sent);
        }
        outbuf = null;
    }
    
    /** Closes assigned streams. */
    void close() {
        interrupt();
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
                // ignoring
            }
            out = null;
        }
    }
    
    /**
     * Queues givem data for sending.
     * @param buf bytes to send.
     */
    void send(byte[] buf) {
        if (outbuf != null) {
            throw new EmulationException("Unexpected sending conflict");
        }
        outbuf = buf;
        start();
    }

    /**
     * Sends data to the other side of the emulated connection.
     * @exception IOException  if an I/O error occurs
     */
    protected void sendImpl() throws IOException {
        plainSend(outbuf);
    }
    
    /**
     * Sends data by simple OutputStream.write(), ignoring all other 
     * logic impied by Sender and subclasses.
     * @param buf the data to send
     */
    void plainSend(byte[] buf) throws IOException {
        if (out != null) {
            out.write(buf, 0, buf.length);
            out.flush();
        }
    }
}

/**
 * Reloads send methods in L2CAP-specific way. The L2CAP spacific 
 * is discarding bytes sent in a time by one side and not received by 
 * another.
 */
class L2CAPSender extends Sender {
    /** 
     * Creates an instance.
     * @param socketConnection open socet connection to use. 
     * @param handle handle of emulated connection this sender works for
     * Exception IOException if I/O error occured while opening streams
     */
    L2CAPSender(SocketConnection socketConnection, int handle) 
            throws IOException {
        super(socketConnection, handle);
    }
    
    /**
     * Sends data to the other side of the emulated connection.
     * @exception IOException  if an I/O error occurs
     */
    protected void sendImpl() throws IOException {
        byte[] lenbytes = new byte[2];
        lenbytes[0] = (byte)(outbuf.length & 0xff);
        lenbytes[1] = (byte)((outbuf.length >> 8) & 0xff);
        
        plainSend(lenbytes);
        plainSend(outbuf);
    }

}


/**
 * Implements simple receive methods that just call 
 * proper I/O streams methods from a separate thread.
 */
class Receiver extends RunnableProcessor {
    /** Socket input stream. */
    private DataInputStream in;
    /** Byes received. */
    byte[] inbuf;
    /** Handle of emulated connection this receiver works for. */
    int handle;
    
    /**
     * Creates proper instance depending on protocol
     * @param protocol protocol (as in <code>BluetoothUrl</code>) to create
     *        receiver for
     * @param socketConnection open socket connection to use for receiving
     * @param handle handle of emulated connection to create receiver for
     * @return instance created
     * Exception IOException if I/O error occured while opening streams
     */
    static Receiver create(SocketConnection socketConnection, int protocol,
                int handle) {
        
         Receiver res = null;
         try {
            res = protocol == BluetoothUrl.L2CAP ? 
                new L2CAPReceiver(socketConnection, handle) :
                new Receiver(socketConnection, handle);
        } catch (IOException e) {
            ConnectionEmul.notifyReceived(handle, null, Const.CONN_FAILURE);
        }
        
        return res;
    }
    
    /**
     * Initiates streams.
     * @param socketConnection open socet connection to use. 
     * @param handle handle of emulated connection this receiver works for
     * Exception IOException if I/O error occured while opening streams
     */
    Receiver(SocketConnection socketConnection, int handle) 
            throws IOException {
        super(true);
        this.handle = handle;
        in = socketConnection.openDataInputStream();
    }
    
    /** Processes receive request. */
    public void process() {
        int len;
        try {
            len = receiveImpl();
        } catch (Throwable e) {
            len = Const.CONN_FAILURE;
        }
        
        if (!interrupted) {
            ConnectionEmul.notifyReceived(handle, inbuf, len);
        }
        inbuf = null;
    }
    
    /** Closes assigned stream. */
    void close() {
        interrupt();
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
                // ignoring
            }
            
            in = null;
        }
    }
    
    /** Initiates receving data. */
    void receive() {
        start();
    }

    /**
     * Reads data from emulated connection to given buffer.
     * @return total number of bytes read into the buffer,
     *         <code>-1</code> if there is nothing to read.
     *         
     * @exception  IOException  if an I/O error occurs.
     */
    protected int receiveImpl() throws IOException {
        if (interrupted) {
            throw new IOException();
        }
        
        int len = in.available();
        if (len >= 0) {
            if (len == 0) {
                len = 1;
            }
            
            inbuf = new byte[len];
            len = in.read(inbuf, 0, len);
        }
        
        return len;
    }
    
    /**
     * Reads exactly requested amount of bytes form input stream.
     * Blocks until they appear.
     * @param len amount of bytes to read
     * @return newly created byte array that contains read bytes
     * @exception IOException if reading fails or connection is closed 
     *            while waiting for input
     */
    byte[] forceReceive(int len) throws IOException {
        byte[] bytes = new byte[len];
        int offset = 0;
        in.readFully(bytes); 

        return bytes;
    }
}

/**
 * Reloads receive methods in L2CAP-specific way. The L2CAP specific 
 * is discarding bytes sent in a time by one side and not received by 
 * another.
 */
class L2CAPReceiver extends Receiver {
    /** 
     * Creates an instance.
     * @param socketConnection open socet connection to use. 
     * @param handle handle of emulated connection this receiver works for
     * Exception IOException if I/O error occured while opening streams
     */
    L2CAPReceiver(SocketConnection socketConnection, int handle) 
            throws IOException {
        super(socketConnection, handle);
    }
    
    /**
     * Reads data from emulated connection. It receves exact amount
     * of bytes that is sent by other side, if requested amount is less
     * than actually read, last read bytes are discarded. 
     *
     * @return total number of bytes read into <code>inbuf</code>
     * @exception IOException  if an I/O error occurs
     */
    protected int receiveImpl() throws IOException {
        if (interrupted) {
            throw new InterruptedIOException();
        }
        
        try {
            inbuf = forceReceive(2);
        } catch (IOException e) {
            // It is OK, just the end of stream reached
            return Const.CONN_ENDOF_INP;
        }
        
        int packLen = (inbuf[0] & 0xff) | ((inbuf[1] & 0xff) << 8);
        inbuf = forceReceive(packLen);
        
        return packLen;
    }
}