/*
 * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 in the LICENSE file that
 * accompanied this code).
 *
 * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package sslnpn.ssl;

import java.io.*;
import java.nio.channels.SocketChannel;
import java.net.*;

import javax.net.ssl.*;

/**
 * Abstract base class for SSLSocketImpl. Its purpose is to house code with
 * no SSL related logic (or no logic at all). This makes SSLSocketImpl shorter
 * and easier to read. It contains a few constants and static methods plus
 * overridden java.net.Socket methods.
 *
 * Methods are defined final to ensure that they are not accidentally
 * overridden in SSLSocketImpl.
 *
 * @see javax.net.ssl.SSLSocket
 * @see SSLSocketImpl
 *
 */
abstract class BaseSSLSocketImpl extends SSLSocket {

    /*
     * Normally "self" is "this" ... but not when this connection is
     * layered over a preexisting socket.  If we're using an existing
     * socket, we delegate some actions to it.  Else, we delegate
     * instead to "super".  This is important to ensure that we don't
     * recurse infinitely ... e.g. close() calling itself, or doing
     * I/O in terms of our own streams.
     */
    final Socket self;

    BaseSSLSocketImpl() {
        super();
        this.self = this;
    }

    BaseSSLSocketImpl(Socket socket) {
        super();
        this.self = socket;
    }

    //
    // CONSTANTS AND STATIC METHODS
    //

    /**
     * TLS requires that a close_notify warning alert is sent before the
     * connection is closed in order to avoid truncation attacks. Some
     * implementations (MS IIS and others) don't do that. The property
     * below controls whether we accept that or treat it as an error.
     *
     * The default is "false", i.e. tolerate the broken behavior.
     */
    private final static String PROP_NAME =
                                "com.sun.net.ssl.requireCloseNotify";

    final static boolean requireCloseNotify =
                                Debug.getBooleanProperty(PROP_NAME, false);

    //
    // MISC SOCKET METHODS
    //

    /**
     * Returns the unique {@link java.nio.SocketChannel SocketChannel} object
     * associated with this socket, if any.
     * @see java.net.Socket#getChannel
     */
    public final SocketChannel getChannel() {
        if (self == this) {
            return super.getChannel();
        } else {
            return self.getChannel();
        }
    }

    /**
     * Binds the address to the socket.
     * @see java.net.Socket#bind
     */
    public void bind(SocketAddress bindpoint) throws IOException {
        /*
         * Bind to this socket
         */
        if (self == this) {
            super.bind(bindpoint);
        } else {
            // If we're binding on a layered socket...
            throw new IOException(
                "Underlying socket should already be connected");
        }
    }

    /**
     * Returns the address of the endpoint this socket is connected to
     * @see java.net.Socket#getLocalSocketAddress
     */
    public SocketAddress getLocalSocketAddress() {
        if (self == this) {
            return super.getLocalSocketAddress();
        } else {
            return self.getLocalSocketAddress();
        }
    }

    /**
     * Returns the address of the endpoint this socket is connected to
     * @see java.net.Socket#getRemoteSocketAddress
     */
    public SocketAddress getRemoteSocketAddress() {
        if (self == this) {
            return super.getRemoteSocketAddress();
        } else {
            return self.getRemoteSocketAddress();
        }
    }

    /**
     * Connects this socket to the server.
     *
     * This method is either called on an unconnected SSLSocketImpl by the
     * application, or it is called in the constructor of a regular
     * SSLSocketImpl. If we are layering on top on another socket, then
     * this method should not be called, because we assume that the
     * underlying socket is already connected by the time it is passed to
     * us.
     *
     * @param   endpoint the <code>SocketAddress</code>
     * @throws  IOException if an error occurs during the connection
     */
    public final void connect(SocketAddress endpoint) throws IOException {
        connect(endpoint, 0);
    }

    /**
     * Returns the connection state of the socket.
     * @see java.net.Socket#isConnected
     */
    public final boolean isConnected() {
        if (self == this) {
            return super.isConnected();
        } else {
            return self.isConnected();
        }
    }

    /**
     * Returns the binding state of the socket.
     * @see java.net.Socket#isBound
     */
    public final boolean isBound() {
        if (self == this) {
            return super.isBound();
        } else {
            return self.isBound();
        }
    }

    //
    // CLOSE RELATED METHODS
    //

    /**
     * The semantics of shutdownInput is not supported in TLS 1.0
     * spec. Thus when the method is called on an SSL socket, an
     * UnsupportedOperationException will be thrown.
     *
     * @throws UnsupportedOperationException
     */
    public final void shutdownInput() throws IOException {
        throw new UnsupportedOperationException("The method shutdownInput()" +
                   " is not supported in SSLSocket");
    }

    /**
     * The semantics of shutdownOutput is not supported in TLS 1.0
     * spec. Thus when the method is called on an SSL socket, an
     * UnsupportedOperationException will be thrown.
     *
     * @throws UnsupportedOperationException
     */
    public final void shutdownOutput() throws IOException {
        throw new UnsupportedOperationException("The method shutdownOutput()" +
                   " is not supported in SSLSocket");

    }

    /**
     * Returns the input state of the socket
     * @see java.net.Socket#isInputShutdown
     */
    public final boolean isInputShutdown() {
        if (self == this) {
            return super.isInputShutdown();
        } else {
            return self.isInputShutdown();
        }
    }

    /**
     * Returns the output state of the socket
     * @see java.net.Socket#isOutputShutdown
     */
    public final boolean isOutputShutdown() {
        if (self == this) {
            return super.isOutputShutdown();
        } else {
            return self.isOutputShutdown();
        }
    }

    /**
     * Ensures that the SSL connection is closed down as cleanly
     * as possible, in case the application forgets to do so.
     * This allows SSL connections to be implicitly reclaimed,
     * rather than forcing them to be explicitly reclaimed at
     * the penalty of prematurly killing SSL sessions.
     */
    protected final void finalize() throws Throwable {
        try {
            close();
        } catch (IOException e1) {
            try {
                if (self == this) {
                    super.close();
                }
            } catch (IOException e2) {
                // ignore
            }
        } finally {
            // We called close on the underlying socket above to
            // make doubly sure all resources got released.  We
            // don't finalize self in the case of overlain sockets,
            // that's a different object which the GC will finalize
            // separately.

            super.finalize();
        }
    }

    //
    // GET ADDRESS METHODS
    //

    /**
     * Returns the address of the remote peer for this connection.
     */
    public final InetAddress getInetAddress() {
        if (self == this) {
            return super.getInetAddress();
        } else {
            return self.getInetAddress();
        }
    }

    /**
     * Gets the local address to which the socket is bound.
     *
     * @return the local address to which the socket is bound.
     * @since   JDK1.1
     */
    public final InetAddress getLocalAddress() {
        if (self == this) {
            return super.getLocalAddress();
        } else {
            return self.getLocalAddress();
        }
    }

    /**
     * Returns the number of the remote port that this connection uses.
     */
    public final int getPort() {
        if (self == this) {
            return super.getPort();
        } else {
            return self.getPort();
        }
    }

    /**
     * Returns the number of the local port that this connection uses.
     */
    public final int getLocalPort() {
        if (self == this) {
            return super.getLocalPort();
        } else {
            return self.getLocalPort();
        }
    }

    //
    // SOCKET OPTION METHODS
    //

    /**
     * Enables or disables the Nagle optimization.
     * @see java.net.Socket#setTcpNoDelay
     */
    public final void setTcpNoDelay(boolean value) throws SocketException {
        if (self == this) {
            super.setTcpNoDelay(value);
        } else {
            self.setTcpNoDelay(value);
        }
    }

    /**
     * Returns true if the Nagle optimization is disabled.  This
     * relates to low-level buffering of TCP traffic, delaying the
     * traffic to promote better throughput.
     *
     * @see java.net.Socket#getTcpNoDelay
     */
    public final boolean getTcpNoDelay() throws SocketException {
        if (self == this) {
            return super.getTcpNoDelay();
        } else {
            return self.getTcpNoDelay();
        }
    }

    /**
     * Assigns the socket's linger timeout.
     * @see java.net.Socket#setSoLinger
     */
    public final void setSoLinger(boolean flag, int linger)
            throws SocketException {
        if (self == this) {
            super.setSoLinger(flag, linger);
        } else {
            self.setSoLinger(flag, linger);
        }
    }

    /**
     * Returns the socket's linger timeout.
     * @see java.net.Socket#getSoLinger
     */
    public final int getSoLinger() throws SocketException {
        if (self == this) {
            return super.getSoLinger();
        } else {
            return self.getSoLinger();
        }
    }

    /**
     * Send one byte of urgent data on the socket.
     * @see java.net.Socket#sendUrgentData
     * At this point, there seems to be no specific requirement to support
     * this for an SSLSocket. An implementation can be provided if a need
     * arises in future.
     */
    public final void sendUrgentData(int data) throws SocketException {
        throw new SocketException("This method is not supported "
                        + "by SSLSockets");
    }

    /**
     * Enable/disable OOBINLINE (receipt of TCP urgent data) By default, this
     * option is disabled and TCP urgent data received on a socket is silently
     * discarded.
     * @see java.net.Socket#setOOBInline
     * Setting OOBInline does not have any effect on SSLSocket,
     * since currently we don't support sending urgent data.
     */
    public final void setOOBInline(boolean on) throws SocketException {
        throw new SocketException("This method is ineffective, since"
                + " sending urgent data is not supported by SSLSockets");
    }

    /**
     * Tests if OOBINLINE is enabled.
     * @see java.net.Socket#getOOBInline
     */
    public final boolean getOOBInline() throws SocketException {
        throw new SocketException("This method is ineffective, since"
                + " sending urgent data is not supported by SSLSockets");
    }

    /**
     * Returns the socket timeout.
     * @see java.net.Socket#getSoTimeout
     */
    public final int getSoTimeout() throws SocketException {
        if (self == this) {
            return super.getSoTimeout();
        } else {
            return self.getSoTimeout();
        }
    }

    public final void setSendBufferSize(int size) throws SocketException {
        if (self == this) {
            super.setSendBufferSize(size);
        } else {
            self.setSendBufferSize(size);
        }
    }

    public final int getSendBufferSize() throws SocketException {
        if (self == this) {
            return super.getSendBufferSize();
        } else {
            return self.getSendBufferSize();
        }
    }

    public final void setReceiveBufferSize(int size) throws SocketException {
        if (self == this) {
            super.setReceiveBufferSize(size);
        } else {
            self.setReceiveBufferSize(size);
        }
    }

    public final int getReceiveBufferSize() throws SocketException {
        if (self == this) {
            return super.getReceiveBufferSize();
        } else {
            return self.getReceiveBufferSize();
        }
    }

    /**
     * Enable/disable SO_KEEPALIVE.
     * @see java.net.Socket#setKeepAlive
     */
    public final void setKeepAlive(boolean on) throws SocketException {
        if (self == this) {
            super.setKeepAlive(on);
        } else {
            self.setKeepAlive(on);
        }
    }

    /**
     * Tests if SO_KEEPALIVE is enabled.
     * @see java.net.Socket#getKeepAlive
     */
    public final boolean getKeepAlive() throws SocketException {
        if (self == this) {
            return super.getKeepAlive();
        } else {
            return self.getKeepAlive();
        }
    }

    /**
     * Sets traffic class or type-of-service octet in the IP header for
     * packets sent from this Socket.
     * @see java.net.Socket#setTrafficClass
     */
    public final void setTrafficClass(int tc) throws SocketException {
        if (self == this) {
            super.setTrafficClass(tc);
        } else {
            self.setTrafficClass(tc);
        }
    }

    /**
     * Gets traffic class or type-of-service in the IP header for packets
     * sent from this Socket.
     * @see java.net.Socket#getTrafficClass
     */
    public final int getTrafficClass() throws SocketException {
        if (self == this) {
            return super.getTrafficClass();
        } else {
            return self.getTrafficClass();
        }
    }

    /**
     * Enable/disable SO_REUSEADDR.
     * @see java.net.Socket#setReuseAddress
     */
    public final void setReuseAddress(boolean on) throws SocketException {
        if (self == this) {
            super.setReuseAddress(on);
        } else {
            self.setReuseAddress(on);
        }
    }

    /**
     * Tests if SO_REUSEADDR is enabled.
     * @see java.net.Socket#getReuseAddress
     */
    public final boolean getReuseAddress() throws SocketException {
        if (self == this) {
            return super.getReuseAddress();
        } else {
            return self.getReuseAddress();
        }
    }

    /**
     * Sets performance preferences for this socket.
     *
     * @see java.net.Socket#setPerformancePreferences(int, int, int)
     */
    public void setPerformancePreferences(int connectionTime,
            int latency, int bandwidth) {
        if (self == this) {
            super.setPerformancePreferences(
                connectionTime, latency, bandwidth);
        } else {
            self.setPerformancePreferences(
                connectionTime, latency, bandwidth);
        }
    }

}
