/*
 *  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 javax.crypto;

import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;

/**
 * This class defines the <i>Service Provider Interface</i> (<b>SPI</b>) for
 * cryptographic ciphers.
 * <p>
 * Implementers of cryptographic ciphers must implement all the abstract methods
 * for every cipher they implement. {@code CipherSpi} instances are created
 * along with ciphers when the {@link Cipher#getInstance} method is called. A
 * {@code Cipher} is referenced by a <i>transformation</i>, which is a string
 * that describes the operation (or set of operations), always consisting of the
 * cipher's name and optionally followed by a mode and a padding, in the form:
 * <ul>
 * <li>"algorithm"</li>or
 * <li>"algorithm/mode/padding"</li>
 * </ul>
 * The following behavior should be implemented for obtaining {@code Cipher}
 * instances.
 * <p>
 * When one of the {@link Cipher#getInstance} factory methods is called with a
 * <i>transformation</i> that is only an <i>algorithm</i>, check if the provider
 * defines a {@code CipherSpi} for "algorithm", if so: return it, otherwise
 * throw a {@link NoSuchAlgorithmException}.
 * <p>
 * The following rules apply when a <i>transformation</i> is of the form
 * "algorithm/mode/padding":
 * <ul>
 * 1. The Provider has a {@code CipherSpi} subclass registered for
 * "algorithm/mode/padding": return it, otherwise go to step 2.
 * </ul>
 * <ul>
 * 2. The Provider has a {@code CipherSpi} subclass registered for
 * "algorithm/mode": instantiate it, call
 * {@link CipherSpi#engineSetPadding(String) engineSetPadding(String)} for the
 * padding name and return it, otherwise go to step 3.
 * </ul>
 * <ul>
 * 3. The Provider has a {@code CipherSpi} subclass registered for
 * "algorithm//padding": instantiate it, call
 * {@link CipherSpi#engineSetMode(String) engineSetMode(String)} for the mode
 * name and return it, otherwise go to step 4.
 * </ul>
 * <ul>
 * 4. The Provider has a {@code CipherSpi} subclass registered for "algorithm":
 * instantiate it, call {@link CipherSpi#engineSetMode(String)
 * engineSetMode(String)} for the mode name , call
 * {@link CipherSpi#engineSetPadding(String) engineSetPadding(String)} for the
 * padding name and return it, otherwise throw a
 * {@link NoSuchAlgorithmException}.
 * </ul>
 *
 * @see Cipher
 */
public abstract class CipherSpi {

    /**
     * Creates a new {@code CipherSpi} instance.
     */
    public CipherSpi() {
    }

    /**
     * Sets the mode for this cipher.
     *
     * @param mode
     *            the name of the cipher mode.
     * @throws NoSuchAlgorithmException
     *             if the specified cipher mode is not supported by this
     *             provider.
     */
    protected abstract void engineSetMode(String mode)
            throws NoSuchAlgorithmException;

    /**
     * Sets the padding method for this cipher.
     *
     * @param padding
     *            the name of the padding method.
     * @throws NoSuchPaddingException
     *             if the specified padding method is not supported by this
     *             cipher.
     */
    protected abstract void engineSetPadding(String padding)
            throws NoSuchPaddingException;

    /**
     * Returns the block size of this cipher (in bytes)
     *
     * @return the block size of this cipher, or zero if this cipher is not a
     *         block cipher.
     */
    protected abstract int engineGetBlockSize();

    /**
     * Returns the size for a buffer (in bytes), that the next call to {@code
     * update} of {@code doFinal} would return, taking into account any buffered
     * data from previous {@code update} calls and padding.
     * <p>
     * The actual output length of the next call to {@code update} or {@code
     * doFinal} may be smaller than the length returned by this method.
     *
     * @param inputLen
     *            the length of the input (in bytes).
     * @return the size for a buffer (in bytes).
     */
    protected abstract int engineGetOutputSize(int inputLen);

    /**
     * Returns the Initialization Vector (IV) that was used to initialize this
     * cipher or {@code null} if none was used.
     *
     * @return the Initialization Vector (IV), or {@code null} if none was used.
     */
    protected abstract byte[] engineGetIV();

    /**
     * Returns the parameters that where used to create this cipher instance.
     * <p>
     * These may be a the same parameters that were used to create this cipher
     * instance, or may be a combination of default and random parameters,
     * depending on the underlying cipher implementation.
     *
     * @return the parameters that where used to create this cipher instance, or
     *         {@code null} if this cipher instance does not have any parameters
     *         at all.
     */
    protected abstract AlgorithmParameters engineGetParameters();

    /**
     * Initializes this cipher instance with the specified key and a source of
     * randomness.
     * <p>
     * The cipher will be initialized for the specified operation (one of:
     * encryption, decryption, key wrapping or key unwrapping) depending on
     * {@code opmode}.
     * <p>
     * If this cipher instance needs any algorithm parameters or random values
     * that the specified key cannot provide, the underlying implementation of
     * this cipher is supposed to generate the required parameters (using its
     * provider or random values). Random values will be generated using {@code
     * random};
     * <p>
     * When a cipher instance is initialized by a call to any of the {@code
     * init} methods, the state of the instance is overridden, means it is
     * equivalent to creating a new instance and calling it {@code init} method.
     *
     * @param opmode
     *            the operation this cipher instance should be initialized for
     *            (one of: {@code ENCRYPT_MODE}, {@code DECRYPT_MODE}, {@code
     *            WRAP_MODE} or {@code UNWRAP_MODE}).
     * @param key
     *            the input key for the operation.
     * @param random
     *            the source of randomness to use.
     * @throws InvalidKeyException
     *             if the specified key cannot be used to initialize this cipher
     *             instance.
     */
    protected abstract void engineInit(int opmode, Key key, SecureRandom random)
            throws InvalidKeyException;

    /**
     * Initializes this cipher instance with the specified key, algorithm
     * parameters and a source of randomness.
     * <p>
     * The cipher will be initialized for the specified operation (one of:
     * encryption, decryption, key wrapping or key unwrapping) depending on
     * {@code opmode}.
     * <p>
     * If this cipher instance needs any algorithm parameters and {@code params}
     * is {@code null}, the underlying implementation of this cipher is supposed
     * to generate the required parameters (using its provider or random
     * values). Random values are generated using {@code random}.
     * <p>
     * When a cipher instance is initialized by a call to any of the {@code
     * init} methods, the state of the instance is overridden, means it is
     * equivalent to creating a new instance and calling it {@code init} method.
     *
     * @param opmode
     *            the operation this cipher instance should be initialized for
     *            (one of: {@code ENCRYPT_MODE}, {@code DECRYPT_MODE}, {@code
     *            WRAP_MODE} or {@code UNWRAP_MODE}).
     * @param key
     *            the input key for the operation.
     * @param params
     *            the algorithm parameters.
     * @param random
     *            the source of randomness to use.
     * @throws InvalidKeyException
     *             if the specified key cannot be used to initialize this cipher
     *             instance.
     * @throws InvalidAlgorithmParameterException
     *             it the specified parameters are inappropriate for this
     *             cipher.
     */
    protected abstract void engineInit(int opmode, Key key,
            AlgorithmParameterSpec params, SecureRandom random)
            throws InvalidKeyException, InvalidAlgorithmParameterException;

    /**
     * Initializes this cipher instance with the specified key, algorithm
     * parameters and a source of randomness.
     * <p>
     * The cipher will be initialized for the specified operation (one of:
     * encryption, decryption, key wrapping or key unwrapping) depending on
     * {@code opmode}.
     * <p>
     * If this cipher instance needs any algorithm parameters and {@code params}
     * is {@code null}, the underlying implementation of this cipher is supposed
     * to generate the required parameters (using its provider or random
     * values). Random values are generated using {@code random}.
     * <p>
     * When a cipher instance is initialized by a call to any of the {@code
     * init} methods, the state of the instance is overridden, means it is
     * equivalent to creating a new instance and calling it {@code init} method.
     *
     * @param opmode
     *            the operation this cipher instance should be initialized for
     *            (one of: {@code ENCRYPT_MODE}, {@code DECRYPT_MODE}, {@code
     *            WRAP_MODE} or {@code UNWRAP_MODE}).
     * @param key
     *            the input key for the operation.
     * @param params
     *            the algorithm parameters.
     * @param random
     *            the source of randomness to use.
     * @throws InvalidKeyException
     *             if the specified key cannot be used to initialize this cipher
     *             instance.
     * @throws InvalidAlgorithmParameterException
     *             if the specified parameters are inappropriate for this
     *             cipher.
     */
    protected abstract void engineInit(int opmode, Key key,
            AlgorithmParameters params, SecureRandom random)
            throws InvalidKeyException, InvalidAlgorithmParameterException;

    /**
     * Continues a multi-part transformation (encryption or decryption). The
     * transformed bytes are returned.
     *
     * @param input
     *            the input bytes to transform.
     * @param inputOffset
     *            the offset in the input to start.
     * @param inputLen
     *            the length of the input to transform.
     * @return the transformed bytes in a new buffer, or {@code null} if the
     *         input has zero length.
     * @throws IllegalStateException
     *             if this cipher instance is not initialized for encryption or
     *             decryption.
     * @throws IllegalArgumentException
     *             if the input is null, or if {@code inputOffset} and {@code
     *             inputLen} do not specify a valid chunk in the input buffer.
     */
    protected abstract byte[] engineUpdate(byte[] input, int inputOffset,
            int inputLen);

    /**
     * Continues a multi-part transformation (encryption or decryption). The
     * transformed bytes are stored in the {@code output} buffer.
     * <p>
     * If the size of the {@code output} buffer is too small to hold the result,
     * a {@code ShortBufferException} is thrown. Use
     * {@link Cipher#getOutputSize getOutputSize} to check for the size of the
     * output buffer.
     *
     * @param input
     *            the input bytes to transform.
     * @param inputOffset
     *            the offset in the input to start.
     * @param inputLen
     *            the length of the input to transform.
     * @param output
     *            the output buffer.
     * @param outputOffset
     *            the offset in the output buffer.
     * @return the number of bytes placed in output.
     * @throws ShortBufferException
     *             if the size of the {@code output} buffer is too small.
     */
    protected abstract int engineUpdate(byte[] input, int inputOffset,
            int inputLen, byte[] output, int outputOffset)
            throws ShortBufferException;

    /**
     * Continues a multi-part transformation (encryption or decryption). The
     * {@code input.remaining()} bytes starting at {@code input.position()} are
     * transformed and stored in the {@code output} buffer.
     * <p>
     * If the {@code output.remaining()} is too small to hold the transformed
     * bytes a {@code ShortBufferException} is thrown. Use
     * {@link Cipher#getOutputSize getOutputSize} to check for the size of the
     * output buffer.
     *
     * @param input
     *            the input buffer to transform.
     * @param output
     *            the output buffer to store the result within.
     * @return the number of bytes stored in the output buffer.
     * @throws ShortBufferException
     *             if the size of the {@code output} buffer is too small.
     */
    protected int engineUpdate(ByteBuffer input, ByteBuffer output)
            throws ShortBufferException {
        if (input == null) {
            throw new NullPointerException("input == null");
        }
        if (output == null) {
            throw new NullPointerException("output == null");
        }
        int position = input.position();
        int limit = input.limit();
        if ((limit - position) <= 0) {
            return 0;
        }
        byte[] bInput;
        byte[] bOutput;
        if (input.hasArray()) {
            bInput = input.array();
            int offset = input.arrayOffset();
            bOutput = engineUpdate(bInput, offset + position, limit - position);
            input.position(limit);
        } else {
            bInput = new byte[limit - position];
            input.get(bInput);
            bOutput = engineUpdate(bInput, 0, limit - position);
        }
        if (output.remaining() < bOutput.length) {
            throw new ShortBufferException("output buffer too small");
        }
        try {
            output.put(bOutput);
        } catch (java.nio.BufferOverflowException e) {
            throw new ShortBufferException("output buffer too small");
        }
        return bOutput.length;
    }

    /**
     * Finishes a multi-part transformation (encryption or decryption).
     * <p>
     * Processes the {@code inputLen} bytes in {@code input} buffer at {@code
     * inputOffset}, and any bytes that have been buffered in previous {@code
     * update} calls.
     *
     * @param input
     *            the input buffer.
     * @param inputOffset
     *            the offset in the input buffer.
     * @param inputLen
     *            the length of the input.
     * @return the final bytes from the transformation.
     * @throws IllegalBlockSizeException
     *             if the size of the resulting bytes is not a multiple of the
     *             cipher block size.
     * @throws BadPaddingException
     *             if the padding of the data does not match the padding scheme.
     */
    protected abstract byte[] engineDoFinal(byte[] input, int inputOffset,
            int inputLen) throws IllegalBlockSizeException, BadPaddingException;

    /**
     * Finishes a multi-part transformation (encryption or decryption).
     * <p>
     * Processes the {@code inputLen} bytes in {@code input} buffer at
     * {@code inputOffset}, and any bytes that have been buffered in previous
     * {@code update} calls.
     *
     * @param input
     *            the input buffer.
     * @param inputOffset
     *            the offset in the input buffer.
     * @param inputLen
     *            the length of the input.
     * @param output
     *            the output buffer for the transformed bytes.
     * @param outputOffset
     *            the offset in the output buffer.
     * @return the number of bytes placed in the output buffer.
     * @throws ShortBufferException
     *             if the size of the {@code output} buffer is too small.
     * @throws IllegalBlockSizeException
     *             if the size of the resulting bytes is not a multiple of the
     *             cipher block size.
     * @throws BadPaddingException
     *             if the padding of the data does not match the padding scheme.
     */
    protected abstract int engineDoFinal(byte[] input, int inputOffset,
            int inputLen, byte[] output, int outputOffset)
            throws ShortBufferException, IllegalBlockSizeException,
            BadPaddingException;

    /**
     * Finishes a multi-part transformation (encryption or decryption).
     * <p>
     * Processes the {@code input.remaining()} bytes in {@code input} buffer at
     * {@code input.position()}, and any bytes that have been buffered in
     * previous {@code update} calls. The transformed bytes are placed into
     * {@code output} buffer.
     *
     * @param input
     *            the input buffer.
     * @param output
     *            the output buffer.
     * @return the number of bytes placed into the output buffer.
     * @throws ShortBufferException
     *             if the size of the {@code output} buffer is too small.
     * @throws IllegalBlockSizeException
     *             if the size of the resulting bytes is not a multiple of the
     *             cipher block size.
     * @throws BadPaddingException
     *             if the padding of the data does not match the padding scheme.
     * @throws IllegalArgumentException
     *             if the input buffer and the output buffer are the same
     *             object.
     * @throws IllegalStateException
     *             if this cipher instance is not initialized for encryption or
     *             decryption.
     */
    protected int engineDoFinal(ByteBuffer input, ByteBuffer output)
            throws ShortBufferException, IllegalBlockSizeException,
            BadPaddingException {
        if (input == null) {
            throw new NullPointerException("input == null");
        }
        if (output == null) {
            throw new NullPointerException("output == null");
        }
        int position = input.position();
        int limit = input.limit();

        if ((limit - position) <= 0) {
            return 0;
        }
        byte[] bInput;
        byte[] bOutput;

        if (input.hasArray()) {
            bInput = input.array();
            int offset = input.arrayOffset();
            bOutput = engineDoFinal(bInput, offset + position, limit - position);
            input.position(limit);
        } else {
            bInput = new byte[limit - position];
            input.get(bInput);
            bOutput = engineDoFinal(bInput, 0, limit - position);
        }
        if (output.remaining() < bOutput.length) {
            throw new ShortBufferException("output buffer too small");
        }
        try {
            output.put(bOutput);
        } catch (java.nio.BufferOverflowException e) {
            throw new ShortBufferException("output buffer too small");
        }
        return bOutput.length;
    }

    /**
     * Wraps a key using this cipher instance. This method has been added to
     * this class (for backwards compatibility, it cannot be abstract). If this
     * method is not overridden, it throws an {@code
     * UnsupportedOperationException}.
     *
     * @param key
     *            the key to wrap.
     * @return the wrapped key
     * @throws IllegalBlockSizeException
     *             if the size of the resulting bytes is not a multiple of the
     *             cipher block size.
     * @throws InvalidKeyException
     *             if this cipher instance cannot wrap this key.
     */
    protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException {
        throw new UnsupportedOperationException();
    }

    /**
     * Unwraps a key using this cipher instance.
     * <p>
     * This method has been added to this class (for backwards compatibility, it
     * cannot be abstract). If this method is not overridden, it throws an
     * {@code UnsupportedOperationException}.
     *
     * @param wrappedKey
     *            the wrapped key to unwrap.
     * @param wrappedKeyAlgorithm
     *            the algorithm for the wrapped key.
     * @param wrappedKeyType
     *            the type of the wrapped key (one of: {@code SECRET_KEY},
     *            {@code PRIVATE_KEY} or {@code PUBLIC_KEY})
     * @return the unwrapped key.
     * @throws InvalidKeyException
     *             if the {@code wrappedKey} cannot be unwrapped to a key of
     *             type {@code wrappedKeyType} for the {@code
     *             wrappedKeyAlgorithm}.
     * @throws NoSuchAlgorithmException
     *             if no provider can be found that can create a key of type
     *             {@code wrappedKeyType} for the {@code wrappedKeyAlgorithm}.
     */
    protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
            int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
        throw new UnsupportedOperationException();
    }

    /**
     * Returns the size of a specified key object in bits. This method has been
     * added to this class (for backwards compatibility, it cannot be abstract).
     * If this method is not overridden, it throws an {@code
     * UnsupportedOperationException}.
     *
     * @param key
     *            the key to get the size for.
     * @return the size of a specified key object in bits.
     * @throws InvalidKeyException
     *             if the size of the key cannot be determined by this
     *             implementation.
     */
    protected int engineGetKeySize(Key key) throws InvalidKeyException {
        throw new UnsupportedOperationException();
    }
}
