package javax.crypto;

import java.io.InputStream;
import java.io.IOException;
import java.io.FilterInputStream;

/**
 * A CipherInputStream is composed of an InputStream and a Cipher so
 * that read() methods return data that are read in from the
 * underlying InputStream but have been additionally processed by the
 * Cipher.  The Cipher must be fully initialized before being used by
 * a CipherInputStream.
 * <p>
 * For example, if the Cipher is initialized for decryption, the
 * CipherInputStream will attempt to read in data and decrypt them,
 * before returning the decrypted data.
 * <p>
 * This class adheres strictly to the semantics, especially the
 * failure semantics, of its ancestor classes
 * java.io.FilterInputStream and java.io.InputStream.  This class has
 * exactly those methods specified in its ancestor classes, and
 * overrides them all.  Moreover, this class catches all exceptions
 * that are not thrown by its ancestor classes.  In particular, the
 * <code>skip</code> method skips, and the <code>available</code>
 * method counts only data that have been processed by the encapsulated Cipher.
 * <p>
 * It is crucial for a programmer using this class not to use
 * methods that are not defined or overriden in this class (such as a
 * new method or constructor that is later added to one of the super
 * classes), because the design and implementation of those methods
 * are unlikely to have considered security impact with regard to
 * CipherInputStream.
 *
 * @since JCE1.2
 * @see InputStream
 * @see FilterInputStream
 * @see Cipher
 * @see CipherOutputStream
 */
public class CipherInputStream
    extends FilterInputStream
{
    private Cipher  c;

    private byte[]  buf;
    private byte[]  inBuf;

    private int     bufOff;
    private int     maxBuf;
    private boolean finalized;

    private static final int    INPUT_BUF_SIZE = 2048;

    /**
     * Constructs a CipherInputStream from an InputStream and a
     * Cipher.
     */
    public CipherInputStream(
        InputStream is,
        Cipher      c)
    {
        super(is);

        this.c = c;

        buf = new byte[c.getOutputSize(INPUT_BUF_SIZE)];
        inBuf = new byte[INPUT_BUF_SIZE];
    }

    /**
     * Constructs a CipherInputStream from an InputStream without
     * specifying a Cipher. This has the effect of constructing a
     * CipherInputStream using a NullCipher.
     */
    protected CipherInputStream(
        InputStream is)
    {
        this(is, new NullCipher());
    }

    /**
     * grab the next chunk of input from the underlying input stream
     */
    private int nextChunk()
        throws IOException
    {
        int available = super.available();

        // must always try to read 1 byte!
        // some buggy InputStreams return < 0!
        if (available <= 0)
        {
            available = 1;
        }

        if (available > inBuf.length)
        {
            available = super.read(inBuf, 0, inBuf.length);
        }
        else
        {
            available = super.read(inBuf, 0, available);
        }

        if (available < 0)
        {
            if (finalized)
            {
                return -1;
            }

            try
            {
                buf = c.doFinal();
            }
            catch (Exception e)
            {
                throw new IOException("error processing stream: " + e.toString());
            }

            bufOff = 0;

            if (buf != null)
            {
                maxBuf = buf.length;
            }
            else
            {
                maxBuf = 0;
            }

            finalized = true;

            if (bufOff == maxBuf)
            {
                return -1;
            }
        }
        else
        {
            bufOff = 0;

            try
            {
                maxBuf = c.update(inBuf, 0, available, buf, 0);
            }
            catch (Exception e)
            {
                throw new IOException("error processing stream: " + e.toString());
            }

            if (maxBuf == 0)    // not enough bytes read for first block...
            {
                return nextChunk();
            }
        }

        return maxBuf;
    }

    /**
     * Reads the next byte of data from this input stream. The value 
     * byte is returned as an <code>int</code> in the range 
     * <code>0</code> to <code>255</code>. If no byte is available 
     * because the end of the stream has been reached, the value 
     * <code>-1</code> is returned. This method blocks until input data 
     * is available, the end of the stream is detected, or an exception 
     * is thrown. 
     *
     * @return the next byte of data, or <code>-1</code> if the end of the
     * stream is reached.
     * @exception IOException if an I/O error occurs.
     * @since JCE1.2
     */
    public int read()
        throws IOException
    {
        if (bufOff == maxBuf)
        {
            if (nextChunk() < 0)
            {
                return -1;
            }
        }

        return buf[bufOff++] & 0xff;
    }

    /**
     * Reads up to <code>b.length</code> bytes of data from this input 
     * stream into an array of bytes. 
     * <p>
     * The <code>read</code> method of <code>InputStream</code> calls 
     * the <code>read</code> method of three arguments with the arguments 
     * <code>b</code>, <code>0</code>, and <code>b.length</code>.
     *
     * @param b the buffer into which the data is read.
     * @return the total number of bytes read into the buffer, or
     * <code>-1</code> is there is no more data because the end of
     * the stream has been reached.
     * @exception IOException if an I/O error occurs.
     * @since JCE1.2
     * @see java.io.InputStream.read(byte[], int, int)
     */
    public int read(
        byte[]      b)
    throws IOException
    {
        return read(b, 0, b.length);
    }

    /**
     * Reads up to <code>len</code> bytes of data from this input stream 
     * into an array of bytes. This method blocks until some input is 
     * available. If the first argument is <code>null,</code> up to 
     * <code>len</code> bytes are read and discarded.
     *
     * @param b the buffer into which the data is read.
     * @param off the start offset of the data.
     * @param len the maximum number of bytes 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.
     * @since JCE1.2
     * @see InputStream.read()
     */
    public int read(
        byte[]  b,
        int     off,
        int     len)
    throws IOException
    {
        if (bufOff == maxBuf)
        {
            if (nextChunk() < 0)
            {
                return -1;
            }
        }

        int available = maxBuf - bufOff;

        if (len > available)
        {
            System.arraycopy(buf, bufOff, b, off, available);
            bufOff = maxBuf;

            return available;
        }
        else
        {
            System.arraycopy(buf, bufOff, b, off, len);
            bufOff += len;

            return len;
        }
    }

    /**
     * Skips <code>n</code> bytes of input from the bytes that can be read
     * from this input stream without blocking.
     * <p>
     * Fewer bytes than requested might be skipped.
     * The actual number of bytes skipped is equal to <code>n</code> or
     * the result of a call to <a href = "#available()"><code>available</code></a>,
     * whichever is smaller.
     * If <code>n</code> is less than zero, no bytes are skipped.
     * <p>
     * The actual number of bytes skipped is returned.
     * 
     * @param n the number of bytes to be skipped.
     * @return the actual number of bytes skipped.
     * @exception IOException if an I/O error occurs.
     * @since JCE1.2
     */
    public long skip(
        long    n)
    throws IOException
    {
        if (n <= 0)
        {
            return 0;
        }

        int available = maxBuf - bufOff;

        if (n > available)
        {
            bufOff = maxBuf;

            return available;
        }
        else
        {
            bufOff += (int)n;

            return (int)n;
        }
    }

    /**
     * Returns the number of bytes that can be read from this input 
     * stream without blocking. The <code>available</code> method of 
     * <code>InputStream</code> returns <code>0</code>. This method 
     * <B>should</B> be overridden by subclasses.
     *
     * @return the number of bytes that can be read from this input stream
     * without blocking.
     * @exception IOException if an I/O error occurs.
     * @since JCE1.2
     */
    public int available()
    throws IOException
    {
        return maxBuf - bufOff;
    }

    /**
     * Closes this input stream and releases any system resources 
     * associated with the stream. 
     * <p>
     * The <code>close</code> method of <code>CipherInputStream</code>
     * calls the <code>close</code> method of its underlying input
     * stream.
     *
     * @exception IOException if an I/O error occurs.
     * @since JCE1.2
     */
    public void close()
    throws IOException
    {
        super.close();
    }

    /**
     * Tests if this input stream supports the <code>mark</code> 
     * and <code>reset</code> methods, which it does not.
     *
     * @return <code>false</code>, since this class does not support the
     * <code>mark</code> and <code>reset</code> methods.
     * @since JCE1.2
     * @see java.io.InputStream.mark(int)
     * @see java.io.InputStream.reset()
     */
    public boolean markSupported()
    {
        return false;
    }
}
