/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.xnet.provider.jsse;

import java.io.IOException;
import java.io.InputStream;
import javax.net.ssl.SSLException;

/**
 * This class provides input data stream functionality
 * for SSLSocket. It accumulates the application data
 * received by SSL protocol.
 */
public final class SSLSocketInputStream
        extends InputStream {

    // The size of the internal data buffer.
    // It should not be less than maximum data chunk enclosed
    // in one ssl packet.
    private final int size = SSLRecordProtocol.MAX_DATA_LENGTH;

    // Internal buffer accumulating the received application data
    private byte[] buffer = new byte[size];

    // position of the next byte to read from the buffer
    private int pos;

    // position of the last byte to read + 1
    private int end;

    // the ssl socket owning the stream
    private final SSLSocketImpl owner;

    // the flag indicating that the end of the (owner's) input stream
    // has been reached
    private boolean end_reached = false;

    /**
     * Creates the application data input stream for specified socket.
     * @param   owner the socket which will provide this input stream
     * to client applications.
     */
    protected SSLSocketInputStream(SSLSocketImpl owner) {
        this.owner = owner;
    }

    // The helper delivering the application data from the record layer
    protected Adapter dataPoint = new Adapter();

    /**
     * Tells to the stream that the end of the income data has
     * been reached.
     */
    protected void setEnd() {
        end_reached = true;
    }

    // ------------------ InputStream implementetion -------------------

    /**
     * Returns the number of bytes available for reading without blocking.
     * @return the number of available bytes.
     * @throws  IOException
     */
    @Override
    public int available() throws IOException {
        return end - pos;
    }

    /**
     * Closes the stream
     * @throws  IOException
     */
    @Override
    public void close() throws IOException {
        buffer = null;
    }

    /**
     * Reads one byte. If there is no data in the underlying buffer,
     * this operation can block until the data will be
     * available.
     * @return read value.
     * @throws  IOException
     */
    @Override
    public int read() throws IOException {
        if (buffer == null) {
            throw new IOException("Stream was closed.");
        }
        while (pos == end) {
            if (end_reached) {
                return -1;
            }
            // If there is no data in the buffer
            // - will block untill the data will be provided by
            // record layer
            owner.needAppData();
        }
        return buffer[pos++] & 0xFF;
    }

    /**
     * Method acts as described in spec for superclass.
     * @see java.io.InputStream#read(byte[])
     */
    @Override
    public int read(byte[] b) throws IOException {
        return read(b, 0, b.length);
    }

    /**
     * Method acts as described in spec for superclass.
     * @see java.io.InputStream#read(byte[],int,int)
     */
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int read_b;
        int i = 0;
        do {
            if ((read_b = read()) == -1) {
                return (i == 0) ? -1 : i;
            }
            b[off+i] = (byte) read_b;
            i++;
        } while ((available() != 0) && (i<len));
        return i;
    }

    /**
     * Method acts as described in spec for superclass.
     * @see java.io.InputStream#skip(long)
     */
    @Override
    public long skip(long n) throws IOException {
        long i = 0;
        int av = available();
        if (av < n) {
            n = av;
        }
        while ((i < n) && (read() != -1)) {
            i++;
        }
        return i;
    }

    // The helper class devivering the application data from the record layer
    // to this input stream.
    // It 'adapts' the InputStream interface to Appendable, which is used for
    // transmition of income data from the record protocol to its clients.
    private class Adapter implements org.apache.harmony.xnet.provider.jsse.Appendable {
        /**
         * Appends the data to the stream.
         * This method could be implemented in the outer class
         * itself, but it could be insecure.
         */
        public void append(byte[] src) {
            int length = src.length;
            if (size - (end - pos) < length) {
                // If the size of the buffer is greater than or equals to
                // SSLRecordProtocol.MAX_DATA_LENGTH this situation will
                // happen iff:
                // 1. the length of received data fragment is greater
                // than allowed by the spec
                // 2. it is rehandhaking stage and we have got several
                // extra app data messages.
                // In any case it is better to throw alert exception.
                throw new AlertException(AlertProtocol.INTERNAL_ERROR,
                        new SSLException("Could not accept income app data."));
            }
            if (end + length > size) {
                // move the content of the buffer to the beginnig
                System.arraycopy(buffer, pos, buffer, 0, end-pos);
                end -= pos;
                pos = 0;
            }
            System.arraycopy(src, 0, buffer, end, length);
            end = end + length;
        }
    }
}

