package org.spongycastle.crypto;

/**
 * a buffer wrapper for an asymmetric block cipher, allowing input
 * to be accumulated in a piecemeal fashion until final processing.
 */
public class BufferedAsymmetricBlockCipher
{
    protected byte[]        buf;
    protected int           bufOff;

    private final AsymmetricBlockCipher   cipher;

    /**
     * base constructor.
     *
     * @param cipher the cipher this buffering object wraps.
     */
    public BufferedAsymmetricBlockCipher(
        AsymmetricBlockCipher     cipher)
    {
        this.cipher = cipher;
    }

    /**
     * return the underlying cipher for the buffer.
     *
     * @return the underlying cipher for the buffer.
     */
    public AsymmetricBlockCipher getUnderlyingCipher()
    {
        return cipher;
    }

    /**
     * return the amount of data sitting in the buffer.
     *
     * @return the amount of data sitting in the buffer.
     */
    public int getBufferPosition()
    {
        return bufOff;
    }

    /**
     * initialise the buffer and the underlying cipher.
     *
     * @param forEncryption if true the cipher is initialised for
     *  encryption, if false for decryption.
     * @param params the key and other data required by the cipher.
     */
    public void init(
        boolean             forEncryption,
        CipherParameters    params)
    {
        reset();

        cipher.init(forEncryption, params);

        //
        // we allow for an extra byte where people are using their own padding
        // mechanisms on a raw cipher.
        //
        buf = new byte[cipher.getInputBlockSize() + (forEncryption ? 1 : 0)];
        bufOff = 0;
    }

    /**
     * returns the largest size an input block can be.
     *
     * @return maximum size for an input block.
     */
    public int getInputBlockSize()
    {
        return cipher.getInputBlockSize();
    }

    /**
     * returns the maximum size of the block produced by this cipher.
     *
     * @return maximum size of the output block produced by the cipher.
     */
    public int getOutputBlockSize()
    {
        return cipher.getOutputBlockSize();
    }

    /**
     * add another byte for processing.
     * 
     * @param in the input byte.
     */
    public void processByte(
        byte        in)
    {
        if (bufOff >= buf.length)
        {
            throw new DataLengthException("attempt to process message too long for cipher");
        }

        buf[bufOff++] = in;
    }

    /**
     * add len bytes to the buffer for processing.
     *
     * @param in the input data
     * @param inOff offset into the in array where the data starts
     * @param len the length of the block to be processed.
     */
    public void processBytes(
        byte[]      in,
        int         inOff,
        int         len)
    {
        if (len == 0)
        {
            return;
        }

        if (len < 0)
        {
            throw new IllegalArgumentException("Can't have a negative input length!");
        }

        if (bufOff + len > buf.length)
        {
            throw new DataLengthException("attempt to process message too long for cipher");
        }

        System.arraycopy(in, inOff, buf, bufOff, len);
        bufOff += len;
    }

    /**
     * process the contents of the buffer using the underlying
     * cipher.
     *
     * @return the result of the encryption/decryption process on the
     * buffer.
     * @exception InvalidCipherTextException if we are given a garbage block.
     */
    public byte[] doFinal()
        throws InvalidCipherTextException
    {
        byte[] out = cipher.processBlock(buf, 0, bufOff);

        reset();

        return out;
    }

    /**
     * Reset the buffer and the underlying cipher.
     */
    public void reset()
    {
        /*
         * clean the buffer.
         */
        if (buf != null)
        {
            for (int i = 0; i < buf.length; i++)
            {
                buf[i] = 0;
            }
        }

        bufOff = 0;
    }
}
