/*
*
*
* 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;
}
}
|