FileDocCategorySizeDatePackage
Protocol.javaAPI DocJ2ME MIDP 2.020027Thu Nov 07 12:02:22 GMT 2002com.sun.midp.io.j2me.datagram

Protocol.java

/*
 * @(#)Protocol.java	1.52 02/10/14 @(#)
 *
 * Copyright (c) 1999-2002 Sun Microsystems, Inc.  All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 */

package com.sun.midp.io.j2me.datagram;

import java.io.*;

import javax.microedition.io.*;

import com.sun.cldc.io.ConnectionBaseInterface;
import com.sun.cldc.io.GeneralBase;

import com.sun.midp.io.*;

import com.sun.midp.io.j2me.push.*;

import com.sun.midp.midlet.*;

import com.sun.midp.security.*;

/**
 * This is the default "datagram://" protocol for J2ME that maps onto UDP.
 *
 * @author  Nik Shaylor
 * @version 1.1 11/19/99
 */
public class Protocol implements UDPDatagramConnection,
    ConnectionBaseInterface {

    /** This class has a different security domain than the MIDlet suite */
    private static SecurityToken classSecurityToken;
    
    /** Initialize the native network. */
    static {
        NetworkConnectionBase.initializeNativeNetwork();
    }

    /**
     * Initializes the security token for this class, so it can
     * perform actions that a normal MIDlet Suite cannot.
     *
     * @param token security token for this class.
     */
    public static void initSecurityToken(SecurityToken token) {
	if (classSecurityToken != null) {
	    return;
	}
	
	classSecurityToken = token;
    }

    /** Used by native code to hold the file descriptor handle. */
    private int handle;

    /** Machine name from the URL connection string. */
    private String host;

    /** Port number from the URL connection string. */
    private int port;

    /** Open flag to indicate if the connection is currently open. */
    private boolean open;

    /**
     * Open a connection to a target.
     * <p>
     * The name string for this protocol should be:
     * "//[address:][port]"
     *
     * @param name       the target of the connection
     * @param mode       a flag that is <code>true</code> if the caller
     *                   intends to write to the connection, ignored
     * @param timeouts   a flag to indicate that the called
     *                   wants timeout exceptions, ignored
     * @return this connection
     * @exception IllegalArgumentException if a parameter is invalid
     * @throws IOException if an I/O operation failed
     * @exception SecurityException if a caller is not authorized for UDP
     */
    public Connection openPrim(String name, int mode, boolean timeouts)
            throws IOException {
        int incommingPort = 0;
        Scheduler scheduler = Scheduler.getScheduler();
        MIDletSuite midletSuite = scheduler.getMIDletSuite();

        // parse name into host and port
        HttpUrl url = new HttpUrl("datagram", name);

        /*
         * Since we reused the <code>HttpUrl</code> 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");
        }

        host = url.host;
        port = url.port;

        if (name.charAt(0) != '/' || name.charAt(1) != '/') {
            throw new IllegalArgumentException(
                      "Protocol must start with slash slash");
        }

	/*
         * If 'host' == null then we are a server endpoint at
         * port 'port'.
         *
         * If 'host' != null we are a client endpoint at a port
         * decided by the system and the default address for
         * datagrams to be send is 'host':'port'
	 *
	 * If 'host' and 'port' are omitted in 
	 * the name, then 
	 * the application is requesting a system assigned port
	 * number.
         */
        if (host == null) {
            try {
                // When asking permission use Internet protocol name.
                midletSuite.checkForPermission(Permissions.UDP_SERVER,
                    "UDP:" + name);
            } catch (SecurityException e) {
                // Give back the connection to AMS
                PushRegistryImpl.checkInConnectionInternal(
                    classSecurityToken, "datagram:" + name);
                    
                throw e;
            } catch (InterruptedException ie) {
                throw new InterruptedIOException(
                    "Interrupted while trying to ask the user permission");
            }

            if (port > 0) {
                incommingPort = port;
            }
        } else {
            if (port < 0) {
                throw new IllegalArgumentException("Missing port number");
            }

            try {
                // When asking permission use Internet protocol name.
                midletSuite.checkForPermission(Permissions.UDP,
                                               "UDP:" + name);
            } catch (InterruptedException ie) {
                throw new InterruptedIOException(
                    "Interrupted while trying to ask the user permission");
            }
        }

	/* Check the mode parameter. (See NetworkConnectionAdapter). */
        switch (mode) {
        case Connector.READ:
        case Connector.WRITE:
        case Connector.READ_WRITE:
            break;

        default:
            throw new IllegalArgumentException("Illegal mode");
        }

	String root = midletSuite.getStorageName();

        byte[] asciiStorage = Util.toCString(root);

        open0(incommingPort, asciiStorage);
        registerCleanup();
        open = true;
        return this;
    }

    /**
     * Ensure that the connection is open.
     * @exception  IOException  if the connection was closed
     */
    void ensureOpen() throws IOException {
        if (!open) {
            throw new IOException("Connection closed");
        }
    }

    /**
     * Get the maximum length a datagram can be.
     *
     * @return  the length
     * @exception  IOException  if the connection was closed
     */
    public int getMaximumLength() throws IOException {
        ensureOpen();
        return getMaximumLength0();
    }

    /**
     * Get the nominal length of a datagram.
     *
     * @return    address      the length
     * @exception  IOException  if the connection was closed
     */
    public int getNominalLength() throws IOException {
        ensureOpen();
        return getNominalLength0();
    }

    /**
     * Send a datagram.
     *
     * @param     dgram        a datagram
     * @exception IOException  if an I/O error occurs
     */
    public void send(Datagram dgram) throws IOException {
        synchronized (dgram) {
            int length;
            int ipNumber;
            int port;

            ensureOpen();

            length = dgram.getLength();

            // allow zero length datagrams to be sent
            if (length < 0) {
                throw new IOException("Bad datagram length");
            }

            if (dgram instanceof DatagramObject) {
                DatagramObject dh = (DatagramObject)dgram;

                ipNumber = dh.ipNumber;
                if (ipNumber == 0) {
                    throw new IOException(
                        "No address in datagram");
                }

                port = dh.port;
            } else {
                // address is a datagram url
                String addr;
                HttpUrl url;
                String host;

                addr = dgram.getAddress();
                if (addr == null) {
                    throw new IOException(
                         "No address in datagram");
                }

                url = new HttpUrl(addr);
                host = url.host;
                port = url.port;

                if (host == null) {
                    throw new IOException("Missing host");
                }
            
                if (port == -1) {
                    throw new IOException("Missing port");
                }

                ipNumber = getIpNumber(Util.toCString(host));

                if (ipNumber == -1) {
                    throw new IOException("Invalid host");
                }
            }

            while (true) {
                int res;

                try {
                    res = send0(ipNumber, port, dgram.getData(),
                                dgram.getOffset(), length);
                } finally {
                    if (!open) {
                        throw new InterruptedIOException("Socket closed");
                    }
                }

                if (res == dgram.getLength()) {
                    break;
                }

                if (res != 0) {
                    throw new IOException("Failed to send datagram");
                }

                /* Wait a while for I/O to become ready */
                GeneralBase.iowait(); 
            }
        }
    }

    /**
     * Receive a datagram.
     *
     * @param     dgram        a datagram
     * @exception IOException  if an I/O error occurs
     */
    public synchronized void receive(Datagram dgram)
        throws IOException {

        synchronized (dgram) {
            int length;
            long res;
            int count;
            int ipNumber;
            String host;
            int port;
            String addr;

            ensureOpen();

            length = dgram.getLength();

            if (length <= 0) {
                throw new IOException("Bad datagram length");
            }

            while (true) {
                try {
                    res = receive0(dgram.getData(), dgram.getOffset(),
                                   length);
                } finally {
                    if (!open) {
                        throw new InterruptedIOException("Socket closed");
                    }
                }

                // check res, not count so we can receive zero length datagrams
                if (res != 0) {
                    break;
                }

                /* Wait a while for I/O to become ready */
                GeneralBase.iowait(); 
            }

            count = ((int)res) & 0xffff;

            /*
             * There should be another field for bytes received so
             * the datagram can be reused without an extra effort, but
             * to be consistant with J2SE DatagramSocket we shrink the buffer
             * length.
             */
            dgram.setLength(count);

            ipNumber = (int)((res >> 32));
            host = getHostByAddr(ipNumber).trim();
            port = (int)((res >> 16)) & 0xffff;
            addr = "datagram://" + host + ":" + port;

            if (dgram instanceof DatagramObject) {
                // save this data for sending back a message
                DatagramObject dh = (DatagramObject)dgram;
                dh.address = addr;
                dh.ipNumber = ipNumber;
                dh.port = port;
            } else {
                dgram.setAddress("datagram://" + host + ":" + port);
            }
        }
    }

    /**
     * Close the connection to the target.
     *
     * @exception IOException  if an I/O error occurs
     */
    public void close() throws IOException {
        if (open) {
            open = false;
            close0();
        } 

    }

    /**
     * Get a new datagram object.
     *
     * @param  size            the length of the buffer to be allocated
     *                         for the datagram
     * @return                 a new datagram
     * @exception IOException  if an I/O error occurs
     * @exception IllegalArgumentException if the length is negative
     *                                     or larger than the buffer
    */
    public Datagram newDatagram(int size) throws IOException {

        Datagram dgram;

        ensureOpen();

        if (size < 0) {
            throw new IllegalArgumentException("Size is negative");
        }

	byte[] buf = new byte[size];

        dgram = new DatagramObject(buf, size);

        if (host != null) {
	    try {
		dgram.setAddress("datagram://" + host + ":" + port);
	    } catch (IllegalArgumentException iae) {
		// Intercept a bad address, here.
		// It'll be caught on send if used.
	    }
        }

        return dgram;
    }

    /**
     * Get a new datagram object.
     *
     * @param  size            the length of the buffer to be allocated
     *                         for the datagram
     * @param     addr         the address to which the datagram must go
     * @return                 a new datagram
     * @exception IOException  if an I/O error occurs
     * @exception IllegalArgumentException if the length is negative or
     *                         larger than the buffer, or if the address 
     *                         parameter is invalid
     */
    public Datagram newDatagram(int size, String addr) throws IOException {
        Datagram dgram = createDatagram(true, null, size);
        dgram.setAddress(addr); // override the address
        return dgram;
    }

    /**
     * Get a new datagram object.
     *
     * @param  buf             the buffer to be used in the datagram
     * @param  size            the length of the buffer to be allocated
     *                         for the datagram
     * @return                 a new datagram
     * @exception IOException  if an I/O error occurs
     * @exception IllegalArgumentException if the length is negative or
     *                         larger than the buffer, or if the address
     *                         or buffer parameters is invalid
     */
    public Datagram newDatagram(byte[] buf, int size) throws IOException {
        return createDatagram(false, buf, size);
    }

    /**
     * Get a new datagram object.
     *
     * @param  buf             the buffer to be used in the datagram
     * @param  size            the length of the buffer to be allocated
     *                         for the datagram
     * @param     addr         the address to which the datagram must go
     * @exception IOException  if an I/O error occurs
     * @return                 a new datagram
     */
    public Datagram newDatagram(byte[] buf, int size, String addr)
        throws IOException {

        Datagram dgram = createDatagram(false, buf, size);
        dgram.setAddress(addr); // override the address
        return dgram;
    }

    /**
     * Create a new datagram object with error checking.
     * If there is a <code>host</code> associated with the connection,
     * set the address of the datagram.
     *
     * @param  createBuffer    if true the buffer is created
     * @param  buf             the buffer to be used in the datagram
     * @param  size            the length of the buffer to be allocated
     *                         for the datagram
     * @return                 a new datagram
     * @exception IOException  if an I/O error occurs, or the connection 
     *                         was closed
     * @exception IllegalArgumentException if the length is negative or
     *                         larger than the buffer, or if the address
     *                         or buffer parameters is invalid
     */
    private Datagram createDatagram(boolean createBuffer, byte[] buf, int size)
        throws IOException {

        Datagram dgram;

        ensureOpen();

        if (size < 0) {
            throw new IllegalArgumentException("Size is negative");
        }

        if (createBuffer) {
            buf = new byte[size];
        } else if (buf == null) {
            throw new IllegalArgumentException("Buffer is invalid");
        } else if (size > buf.length) {
            throw new
                IllegalArgumentException("Size bigger than the buffer");
        }

        dgram = new DatagramObject(buf, size);

        if (host != null) {
            dgram.setAddress("datagram://" + host + ":" + port);
        }

        return dgram;
    }

    /**
     * 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 reasssignment.</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();
    }

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

    /**
     * Open a connection to a target, and fillin the handle field.
     *
     * @param port       port to listen on, or 0 to have one selected
     * @param storage    name of current suite storage
     *
     * @exception IOException  if some other kind of I/O error occurs
     *  or if reserved by another suite
     */
    private native void open0(int port, byte[] storage) throws IOException;

    /**
     * Send a datagram, handle field is accessed by the native code.
     *
     * @param ipNumber raw IPv4 address
     * @param port UDP port
     * @param buf the data buffer
     * @param off the offset into the data buffer
     * @param len the length of the data in the buffer
     * @return number of bytes to send
     * @exception IOException  if an I/O error occurs
     */
    private native int send0(int ipNumber, int port, byte[] buf, int off,
        int len) throws IOException;

    /**
     * Receive a datagram, handle field is accessed by the native code.
     *
     * @param buf the data buffer
     * @param off the offset into the data buffer
     * @param len the length of the data in the buffer
     * @return upper 32 bits: raw IPv4 address, middle 16 bits: port, bytes
     *         in datagram
     * @exception IOException  if an I/O error occurs
     */
    private native long receive0(byte[] buf, int off, int len)
        throws IOException;

    /**
     * Close the native socket, handle field is accessed by the native code.
     *
     * @exception IOException  if an I/O error occurs
     */
    private native void close0()
        throws IOException;

    /** Register this object's native cleanup function. */
    private native void registerCleanup();

    /**
     * Get a hostname for a raw IPv4 address.
     *
     * @param ipn raw IPv4 address
     * @return hostname or the dot notation if not found
     */
    static native String getHostByAddr(int ipn);

    /**
     * Get a raw IPv4 address for hostname.
     *
     * @param szHost hostname as ASCII chars with zero terminator
     * @return raw IPv4 address or -1 if there was an error
     */
    static native int getIpNumber(byte[] szHost);

    /**
     * Get the maximum length of a datagram.
     *
     * @return maximum length of a datagram
     *
     * @exception IOException  if an I/O error occurs
     */
    native int getMaximumLength0()
        throws IOException;

    /**
     * Get the nominal length of a datagram.
     *
     * @return nomimal length of a datagram
     *
     * @exception IOException  if an I/O error occurs
     */
    native int getNominalLength0()
        throws IOException;

    /**
     * Native finalizer.
     */
    private native void finalize();

    /**
     * Get the requested IP number.
     *
     * @return     the IP address as a String
     */
    private native String getHost0();

    /**
     * Get the requested port number.
     *
     * @return     the port number of the requested end point
     */
    private native int getPort0();
}