/*
 *   
 *
 * Copyright  1990-2006 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.ssl;

import java.io.*;

import javax.microedition.io.*;

import javax.microedition.pki.*;

import com.sun.cldc.io.*;

import com.sun.midp.ssl.*;

import com.sun.midp.io.*;

import com.sun.midp.midlet.*;

import com.sun.midp.publickeystore.*;

import com.sun.midp.security.*;

/**
 * This class implements the necessary functionality
 * for an SSL connection.
 */
public class Protocol implements SecureConnection, ConnectionBaseInterface {

    /**
     * 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 classSecurityToken =
        SecurityInitializer.requestToken(new SecurityTrusted());

    /** Underlying TCP connection. */
    private com.sun.midp.io.j2me.socket.Protocol tcpConnection;

    /** Underlying SSL connection. */
    private SSLStreamConnection sslConnection;

    /**
     * Connect to the underlying secure socket transport.
     *
     * @param name       the target of the connection
     * @param mode       a flag that is true if the caller
     *                   intends to write to the connection, ignored
     * @param timeouts   a flag to indicate that the called
     *                   wants timeout exceptions, ignored
     *
     * @return SSL/TCP stream connection
     *
     * @exception IOException is thrown if the connection cannot be opened
     * @exception IllegalArgumentException if the name is bad
     */
    public Connection openPrim(String name, int mode, boolean timeouts)
            throws IOException {
        Scheduler scheduler = Scheduler.getScheduler();
        MIDletSuite midletSuite = scheduler.getMIDletSuite();
        HttpUrl url;
        OutputStream tcpOutputStream;
        InputStream tcpInputStream;

        if (tcpConnection != null) {
            // This method should only be called once.
            throw new RuntimeException("Illegal state for operation");
        }

        try {
            midletSuite.checkForPermission(Permissions.SSL, "ssl:" + name);
        } catch (InterruptedException ie) {
            throw new InterruptedIOException(
                "Interrupted while trying to ask the user permission");
        }

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

        url = new HttpUrl("ssl", 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");
        }

        /*
         * JTWI security check, untrusted MIDlets cannot open port 443.
         * This is so they cannot perform 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 (!midletSuite.isTrusted() && url.port == 443) {
            throw new SecurityException(
                "Target port denied to untrusted applications");
        }
            
        tcpConnection = new com.sun.midp.io.j2me.socket.Protocol();
        tcpConnection.openPrim(classSecurityToken, "//" + url.authority);
        try {
            tcpOutputStream = tcpConnection.openOutputStream();
            try {
                tcpInputStream = tcpConnection.openInputStream();

                /*
                 * Porting note: This would be the place to connect to a 
                 *               SOCKS proxy if desired.
                 */

                try {
                    // Get the SSLStreamConnection
                    sslConnection = new SSLStreamConnection(url.host, url.port,
                                    tcpInputStream, tcpOutputStream,
                                    WebPublicKeyStore.getTrustedKeyStore());
                } catch (IOException e) {
                    tcpInputStream.close();
                    throw e;
                }    
            } catch (IOException e) {
                tcpOutputStream.close();
                throw e;
            }    
        } catch (IOException e) {
            tcpConnection.close();
            throw e;
        }

        return this;
    }

    /**
     * Close the connection to the target.
     *
     * @exception IOException  If an I/O error occurs
     */
    public void close() throws IOException {
        try {
            sslConnection.close();
        } finally {
            tcpConnection.close();
        }
    }

    /**
     * Returns an input stream.
     *
     * @return     an input stream for writing bytes to this port.
     * @exception  IOException  if an I/O error occurs when creating the
     *                          output stream.
     */
    public InputStream openInputStream() throws IOException {
        return sslConnection.openInputStream();
    }

    /**
     * Open and return a data input stream for a connection.
     *
     * @return                 An input stream
     * @exception IOException  If an I/O error occurs
     */
    public DataInputStream openDataInputStream() throws IOException {
        return sslConnection.openDataInputStream();
    }

    /**
     * Returns an output stream.
     *
     * @return     an output stream for writing bytes to this port.
     * @exception  IOException  if an I/O error occurs when creating the
     *                          output stream.
     */
    public OutputStream openOutputStream() throws IOException {
        return sslConnection.openOutputStream();
    }

    /**
     * Open and return a data output stream for a connection.
     *
     * @return                 An input stream
     * @exception IOException  If an I/O error occurs
     */
    public DataOutputStream openDataOutputStream() throws IOException {
        return sslConnection.openDataOutputStream();
    }

    /**
     * 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 {
	tcpConnection.setSocketOption(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 {
	return tcpConnection.getSocketOption(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 {
	return tcpConnection.getLocalAddress();
    }

    /**
     * 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 {
	return tcpConnection.getLocalPort(); 
    }

    /**
     * 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 {
	return tcpConnection.getAddress();
    }

    /**
     * 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 {
	return tcpConnection.getPort(); 
    }

    /**
     * Return the security information associated with this connection.
     * If the connection is still in <CODE>Setup</CODE> state then
     * the connection is initiated to establish the secure connection
     * to the server.  The method returns when the connection is
     * established and the <CODE>Certificate</CODE> supplied by the
     * server has been validated.
     * The <CODE>SecurityInfo</CODE> is only returned if the
     * connection has been successfully made to the server.
     *
     * @return the security information associated with this open connection.
     *
     * @exception CertificateException if the <code>Certificate</code>
     * supplied by the server cannot be validated.
     * The <code>CertificateException</code> will contain
     * the information about the error and indicate the certificate in the
     * validation chain with the error.
     * @exception IOException if an arbitrary connection failure occurs
     */
    public SecurityInfo getSecurityInfo() throws IOException {
        return sslConnection.getSecurityInfo();
    }
}
