/*
*
*
* Copyright 1990-2009 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.io.j2me.socket;
import java.io.IOException;
import java.io.InterruptedIOException;
import javax.microedition.io.Connection;
import javax.microedition.io.Connector;
import javax.microedition.io.SocketConnection;
import javax.microedition.io.ConnectionNotFoundException;
import com.sun.j2me.security.AccessController;
import com.sun.j2me.security.InterruptedSecurityException;
import com.sun.midp.io.NetworkConnectionBase;
import com.sun.midp.io.HttpUrl;
import com.sun.midp.io.Util;
import com.sun.midp.main.Configuration;
import com.sun.midp.security.Permissions;
import com.sun.midp.security.SecurityToken;
import com.sun.midp.security.ImplicitlyTrustedClass;
import com.sun.midp.security.SecurityInitializer;
import com.sun.midp.suspend.NetworkSubsystem;
import com.sun.midp.suspend.Subsystem;
import com.sun.midp.suspend.StateTransitionException;
/** Connection to the J2ME socket API. */
public class Protocol extends NetworkConnectionBase
implements SocketConnection, Subsystem {
/** TCP client permission name. */
private static final String CLIENT_PERMISSION_NAME =
"javax.microedition.io.Connector.socket";
/** Class registered in SecurityInitializer. */
private static class SecurityTrusted implements ImplicitlyTrustedClass {}
/**Security token for provileged access to internal API's. */
private static SecurityToken classSecurityToken =
SecurityInitializer.requestToken(new SecurityTrusted());
/** Size of the read ahead buffer, default is no buffering. */
private static int bufferSize;
/**
* Handle to native socket object. This is set and get only by
* native code.
*/
private int handle = -1;
/** Lock object for reading from the socket */
private final Object readerLock = new Object();
/** Lock object for writing to the socket */
private final Object writerLock = new Object();
/**
* Class initializer
*/
static {
/* See if a read ahead / write behind buffer size has been specified */
bufferSize = Configuration.getNonNegativeIntProperty(
"com.sun.midp.io.j2me.socket.buffersize", bufferSize);
}
/** Hostname */
private String host;
/** TCP port */
private int port;
/** Shutdown output flag, true if output has been shutdown. */
private boolean outputShutdown;
/** True if the owner of this connection is trusted. */
private boolean ownerTrusted;
/** Byte array that represents the IP address */
byte[] ipBytes = new byte[4];
/** Creates a buffered TCP client connection. */
public Protocol() {
// use the default buffer size
super(bufferSize);
}
/**
* Open a client or server socket connection.
* <p>
* The name string for this protocol should be:
* "socket://<name or IP number>:<port number>
* <p>
* We allow "socket://:nnnn" to mean an inbound server socket connection.
*
* @param name the target for the connection
* @param mode I/O access mode
* @param timeouts a flag to indicate that the caller wants
* timeout exceptions
*
* @return client or server TCP socket connection
*
* @exception IOException if an I/O error occurs.
* @exception ConnectionNotFoundException if the host cannot be connected
* to
* @exception IllegalArgumentException if the name is malformed
*/
public Connection openPrim(String name, int mode, boolean timeouts)
throws IOException {
return open(null, name, mode);
}
/**
* Make sure the calling call has the com.sun.midp permission set to
* "allowed" and open a connection to a target.
* Used by internal classes only so they can do work on behalf of suites
* that do not have the directly use this protocol.
*
* @param token security token of the calling class
* @param name URL for the connection, without the
* without the protocol part
*
* @return this Connection object
*
* @exception IllegalArgumentException If a parameter is invalid.
* @exception ConnectionNotFoundException If the connection cannot
* be found.
* @exception IOException If some other kind of I/O error occurs.
*/
public Connection openPrim(SecurityToken token, String name)
throws IOException {
return open(token, name, Connector.READ_WRITE);
}
/**
* Open a client or server socket connection.
* <p>
* The name string for this protocol should be:
* "socket://<name or IP number>:<port number>
* <p>
* We allow "socket://:nnnn" to mean an inbound server socket connection.
*
* @param token security token of the calling class
* @param name the target for the connection
* @param mode I/O access mode
*
* @return client or server TCP socket connection
*
* @exception IOException if an I/O error occurs.
* @exception ConnectionNotFoundException if the host cannot be connected
* to
* @exception IllegalArgumentException if the name is malformed
*/
private Connection open(SecurityToken token, String name, int mode)
throws IOException {
HttpUrl url;
ServerSocket serverSocket;
if (name.charAt(0) != '/' || name.charAt(1) != '/') {
throw new IllegalArgumentException(
"Protocol must start with \"//\"");
}
url = new HttpUrl("socket", name); // parse name into host and port
/*
* Since we reused the HttpUrl parser, we must make sure that
* there was nothing past the authority in the URL.
*/
if (url.path != null || url.query != null || url.fragment != null) {
throw new IllegalArgumentException("Malformed address");
}
NetworkSubsystem.getInstance(classSecurityToken).ensureActive();
host = url.host;
port = url.port;
/*
* If 'host' == null then we are a server endpoint at
* port 'port'.
*/
if (host != null) {
checkForPermission(name, token);
initStreamConnection(mode);
// this will call the connect method which uses the host and port
connect();
return this;
}
// We allow "socket://:nnnn" to mean an inbound TCP server socket.
try {
serverSocket = (ServerSocket)Class.forName(
"com.sun.midp.io.j2me.serversocket.Socket").newInstance();
} catch (Exception e) {
throw new ConnectionNotFoundException("Connection not supported");
}
serverSocket.open(port, token);
return (Connection)serverSocket;
}
/**
* Connect to a server.
* @exception IOException if an I/O error occurs.
* @exception ConnectionNotFoundException if the host cannot be connected
* to
* @exception IllegalStateException if there is no hostname
* @exception IllegalArgumentException if the name is malformed
*/
private void connect() throws IOException {
byte[] szHost;
byte[] szIpBytes = null;
int result;
// Max length of IPv4 address is 4
// IMPL NOTE: IPv6 needs to have an address of length =16
if (handle != -1) {
// This method should only be called once.
// IMPL NOTE: should use something other than handle for this check
throw new RuntimeException("Illegal state for operation");
}
/*
* The host and port were set by overriding the openPrim method of
* our super class.
*/
if (port < 0) {
throw new IllegalArgumentException("Missing port number");
}
result = getIpNumber0(host, ipBytes);
if (result == -1) {
throw new
ConnectionNotFoundException("Could not resolve hostname");
}
/*
* JTWI security check, untrusted MIDlets cannot open port 80 or
* 8080 or 443. This is so they cannot perform HTTP and HTTPS
* requests on server without using the system code. The
* system HTTP code will add a "UNTRUSTED/1.0" to the user agent
* field for untrusted MIDlets.
*/
if (!ownerTrusted && (port == 80 || port == 8080 || port == 443)) {
throw new SecurityException(
"Target port denied to untrusted applications");
}
open0(ipBytes, port);
NetworkSubsystem.getInstance(classSecurityToken).
registerSubsystem(this);
}
/**
* Create a Java connection object from an open TCP socket.
* This method is only used by com.sun.midp.io.j2me.serversocket.Socket;
*
* @param token either <code>null</code> for normal processing or
* a security token with special privileges
*
* @exception IOException if an I/O error occurs
*/
public void open(SecurityToken token) throws IOException {
try {
// The connection needs to be open to call getAddress.
connectionOpen = true;
checkForPermission(getAddress(), token);
} catch (Exception e) {
connectionOpen = false;
try {
close0();
} catch (IOException ioe) {
// ignore
}
if (e instanceof IOException) {
throw (IOException)e;
}
throw (RuntimeException)e;
}
}
/**
* Check for the required permission.
*
* @param name name of resource to insert into the permission question
* @param token either security token with special privileges if pecified
* by caller or <code>null</code>
*
* @exception InterruptedIOException if another thread interrupts the
* calling thread while this method is waiting to preempt the
* display.
*/
private void checkForPermission(String name, SecurityToken token)
throws InterruptedIOException {
if (token != null) {
token.checkIfPermissionAllowed(CLIENT_PERMISSION_NAME);
/* Any caller with the com.sun.midp permission is trusted. */
ownerTrusted = true;
return;
}
name = "TCP" + ":" + name;
try {
AccessController.checkPermission(CLIENT_PERMISSION_NAME, name);
} catch (InterruptedSecurityException ise) {
throw new InterruptedIOException(
"Interrupted while trying to ask the user permission");
}
try {
AccessController.
checkPermission(AccessController.TRUSTED_APP_PERMISSION_NAME);
ownerTrusted = true;
} catch (SecurityException se) {
ownerTrusted = false;
}
}
/**
* Notify blocked Java threads waiting for an input data
* that all InputStream instances of the connection are closed
*/
protected void notifyClosedInput() {
if (iStreams == 0) {
notifyClosedInput0();
}
};
/**
* Notify blocked Java threads trying to output data
* that all OutputStream instances of the connection are closed
*/
protected void notifyClosedOutput() {
if (iStreams == 0) {
notifyClosedOutput0();
}
}
/**
* Wake up Java threads waiting for input data on the connection
* to let them know all the input streams have been closed
*/
private native void notifyClosedInput0();
/**
* Wake up Java threads waiting to output data over the connection
* to let them know all the output streams have been closed
*/
private native void notifyClosedOutput0();
/**
* Disconnect from the server.
*
* @exception IOException if an I/O error occurs.
*/
public void disconnect() throws IOException {
/*
* Only shutdown or close of the sending side of a connection is
* defined in the TCP spec.
*
* The receiver can only abort (reset) the entire connection to stop
* the a sender from sending. InputStream close already causes
* an reads to fail so no native action is needed.
*
* Shutdown the output gracefully closes the sending side of the
* TCP connection by sending all pending data and the FIN flag.
*/
if (!outputShutdown) {
shutdownOutput0();
}
try {
close0();
} catch (IOException ioe) {
// ignore
}
NetworkSubsystem.getInstance(classSecurityToken).
unregisterSubsystem(this);
}
/**
* Reads up to <code>len</code> bytes of data from the input stream into
* an array of bytes, blocks until at least one byte is available.
* Sets the <code>eof</code> field of the connection when the native read
* returns -1.
*
* @param b the buffer into which the data is read.
* @param off the start offset in array <code>b</code>
* at which the data is written.
* @param len the maximum number of bytes to read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @exception IOException if an I/O error occurs.
*/
protected int nonBufferedRead(byte b[], int off, int len)
throws IOException {
int bytesRead;
for (;;) {
try {
/*
* Multiple threads blocked on read operation may
* return results interleaved arbitrarily. From an
* application perspective, the results would be
* indeterministic. So "reader locks" are introduced
* for "read" operation from the same handle.
*/
synchronized (readerLock) {
bytesRead = read0(b, off, len);
}
} finally {
if (iStreams == 0) {
throw new InterruptedIOException("Stream closed");
}
}
if (bytesRead == -1) {
eof = true;
return -1;
}
if (bytesRead != 0) {
return bytesRead;
}
}
}
/**
* Returns the number of bytes that can be read (or skipped over) from
* this input stream without blocking by the next caller of a method for
* this input stream. The next caller might be the same thread or
* another thread.
*
* @return the number of bytes that can be read from this input stream
* without blocking.
* @exception IOException if an I/O error occurs.
*/
public int available() throws IOException {
if (count > 0) {
/*
* The next read will only return the bytes in the buffer,
* so only return the number of bytes in the buffer.
* While available can return a number less than than the next
* read will get, it should not return more.
*/
return count;
}
// The buffer is empty, so the next read will go directly to native
return available0();
}
/**
* Writes <code>len</code> bytes from the specified byte array
* starting at offset <code>off</code> to this output stream.
* <p>
* Polling the will be done by our super class.
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @return number of bytes written
* @exception IOException if an I/O error occurs. In particular,
* an <code>IOException</code> is thrown if the output
* stream is closed.
*/
public int writeBytes(byte b[], int off, int len)
throws IOException {
/*
* Multiple threads blocked on write operation may return results
* interleaved arbitrarily. From an application perspective, the
* results would be indeterministic. So "writer locks" are
* introduced for "write" operation to the same socket.
*/
synchronized (writerLock) {
return write0(b, off, len);
}
}
/**
* Called once by the child output stream. The output side of the socket
* will be shutdown and then the parent method will be called.
*
* @exception IOException if the subclass throws one
*/
protected void closeOutputStream() throws IOException {
/*
* Shutdown the output gracefully closes the sending side of the
* TCP connection by sending all pending data and the FIN flag.
*/
shutdownOutput0();
outputShutdown = true;
super.closeOutputStream();
}
/**
* Check a socket option to make sure it's a valid option.
*
* @param option socket option identifier (KEEPALIVE, LINGER,
* SNDBUF, RCVBUF, or DELAY)
* @exception IllegalArgumentException if the value is not
* valid (e.g. negative value)
*
* @see #getSocketOption
* @see #setSocketOption
*/
private void checkOption(byte option)
throws IllegalArgumentException {
if (option == SocketConnection.KEEPALIVE
|| option == SocketConnection.LINGER
|| option == SocketConnection.SNDBUF
|| option == SocketConnection.RCVBUF
|| option == SocketConnection.DELAY) {
return;
}
throw new IllegalArgumentException("Unsupported Socket Option");
}
/**
* Set a socket option for the connection.
* <P>
* Options inform the low level networking code about intended
* usage patterns that the application will use in dealing with
* the socket connection.
* </P>
*
* @param option socket option identifier (KEEPALIVE, LINGER,
* SNDBUF, RCVBUF, or DELAY)
* @param value numeric value for specified option (must be positive)
* @exception IllegalArgumentException if the value is not
* valid (e.g. negative value)
* @exception IOException if the connection was closed
*
* @see #getSocketOption
*/
public void setSocketOption(byte option, int value)
throws IllegalArgumentException, IOException {
checkOption(option);
if (value < 0) {
throw new IllegalArgumentException("Illegal Socket Option Value");
}
ensureOpen();
setSockOpt0(option, value);
}
/**
* Get a socket option for the connection.
*
* @param option socket option identifier (KEEPALIVE, LINGER,
* SNDBUF, RCVBUF, or DELAY)
* @return positive numeric value for specified option or -1 if the
* value is not available.
* @exception IllegalArgumentException if the option identifier is
* not valid
* @exception IOException if the connection was closed
* @see #setSocketOption
*/
public int getSocketOption(byte option)
throws IllegalArgumentException, IOException {
checkOption(option);
ensureOpen();
return getSockOpt0(option);
}
/**
* Gets the local address to which the socket is bound.
*
* <P>The host address(IP number) that can be used to connect to this
* end of the socket connection from an external system.
* Since IP addresses may be dynamically assigned a remote application
* will need to be robust in the face of IP number reassignment.</P>
* <P> The local hostname (if available) can be accessed from
* <code>System.getProperty("microedition.hostname")</code>
* </P>
*
* @return the local address to which the socket is bound.
* @exception IOException if the connection was closed
* @see ServerSocketConnection
*/
public String getLocalAddress() throws IOException {
ensureOpen();
return getHost0(true);
}
/**
* Returns the local port to which this socket is bound.
*
* @return the local port number to which this socket is connected.
* @exception IOException if the connection was closed
* @see ServerSocketConnection
*/
public int getLocalPort() throws IOException {
ensureOpen();
return getPort0(true);
}
/**
* Gets the remote address to which the socket is bound.
* The address can be either the remote host name or the IP
* address(if available).
*
* @return the remote address to which the socket is bound.
* @exception IOException if the connection was closed
*/
public String getAddress() throws IOException {
ensureOpen();
return getHost0(false);
}
/**
* Returns the remote port to which this socket is bound.
*
* @return the remote port number to which this socket is connected.
* @exception IOException if the connection was closed
*/
public int getPort() throws IOException {
ensureOpen();
return getPort0(false);
}
/**
* Perform actions required on system resume. Makes nothing.
* @throws StateTransitionException
*/
public void resume() throws StateTransitionException {}
/**
* Disconnects connection within system suspend procedure.
*/
public void suspend() {
try {
disconnect();
} catch (IOException e) {
// ignoring to proceed system pause
}
}
/**
* Opens a TCP connection to a server.
*
* @param szIpBytes raw IPv4 address of host
* @param port TCP port at host
*
* @exception IOException if an I/O error occurs.
*/
private native void open0(byte[] szIpBytes, int port)
throws IOException;
/**
* Reads from the open socket connection.
*
* @param b the buffer into which the data is read.
* @param off the start offset in array <code>b</code>
* at which the data is written.
* @param len the maximum number of bytes to read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* @exception IOException if an I/O error occurs.
*/
private native int read0(byte b[], int off, int len)
throws IOException;
/**
* Writes to the open socket connection.
*
* @param b the buffer of the data to write
* @param off the start offset in array <code>b</code>
* at which the data is written.
* @param len the number of bytes to write.
* @return the total number of bytes written
* @exception IOException if an I/O error occurs.
*/
private native int write0(byte b[], int off, int len)
throws IOException;
/**
* Gets the number of bytes that can be read without blocking.
*
* @return number of bytes that can be read without blocking
* @exception IOException if an I/O error occurs.
*/
private native int available0()
throws IOException;
/**
* Closes the socket connection.
*
* @exception IOException if an I/O error occurs when closing the
* connection.
*/
private native void close0()
throws IOException;
/**
* Native finalizer
*/
private native void finalize();
/**
* Gets a byte array that represents an IPv4 or IPv6 address
*
* @param sHost the hostname to lookup
* @param ipBytes_out Output array that receives the
* bytes of IP address
* @return number of bytes copied to ipBytes_out or -1 for an error
*/
private native int getIpNumber0(String sHost, byte[] ipBytes_out);
/**
* Gets the requested IP number.
*
* @param local <tt>true</tt> to get the local host IP address, or
* <tt>false</tt> to get the remote host IP address
* @return the IP address as a dotted-quad <tt>String</tt>
* @exception IOException if an I/O error occurs.
*/
private native String getHost0(boolean local)
throws IOException;
/**
* Gets the requested port number.
*
* @param local <tt>true</tt> to get the local port number, or
* <tt>false</tt> to get the remote port number
* @return the port number
* @exception IOException if an I/O error occurs.
*/
private native int getPort0(boolean local)
throws IOException;
/**
* Gets the requested socket option.
*
* @param option socket option to retrieve
* @return value of the socket option
* @exception IOException if an I/O error occurs.
*/
private native int getSockOpt0(int option)
throws IOException;
/**
* Sets the requested socket option.
*
* @param option socket option to set
* @param value the value to set <tt>option</tt> to
* @exception IOException if an I/O error occurs.
*/
private native void setSockOpt0(int option, int value)
throws IOException;
/**
* Shuts down the output side of the connection. Any error that might
* result from this operation is ignored.
*/
private native void shutdownOutput0();
}