/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program 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. 
 * 
 * This program 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 at /legal/license.txt). 
 * 
 * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.midp.crypto;

/**
 * This class implements the ARCfour stream cipher
 */
public final class ARC4 extends Cipher {
    /** Current cipher mode. */
    private int mode;
    /** Local certificate key. */
    private SecretKey ckey = null;
    /** Seed array. */
    private byte[] S = null;
    /** First intermediate result array. */
    private int[] ii = null;
    /** Second intermediate result array. */
    private int[] jj = null;
    
    /**
     * Constructor for algorithm 3 (ALG_ARCFOUR)
     */
    public void ARC4() {
	mode = Cipher.MODE_UNINITIALIZED;
	S = null;
	ii = null;
	jj = null;
    }


    /**
     * Called by the factory method to set the mode and padding parameters.
     * Need because Class.newInstance does not take args.
     *
     * @param mode the mode parsed from the transformation parameter of
     *             getInstance and upper cased
     * @param padding the paddinge parsed from the transformation parameter of
     *                getInstance and upper cased
     *
     * @exception NoSuchPaddingException if <code>transformation</code>
     * contains a padding scheme that is not available
     * @exception IllegalArgumentException if the mode is invalid for the
     * cipher
     */
    protected void setChainingModeAndPadding(String mode, String padding)
            throws NoSuchPaddingException {

        if (!(mode.equals("") || mode.equals("NONE"))) {
            throw new IllegalArgumentException();
        }

        // NOPADDING is not an option.
        if (!(padding.equals("") || padding.equals("NOPADDING"))) {
            throw new NoSuchPaddingException();
        }
    }

    /**
     * Initializes the cipher's S-boxes based on the key.
     *  This code is based on the cipher's description in 
     * Bruce Schenier's "Applied Cryptography", Second Edition, pp 397-398,
     * ISBN 0-471-11709-9
     *
     * @param opmode the operation mode of this cipher (this is one of the
     * following:
     * <code>ENCRYPT_MODE</code> or <code>DECRYPT_MODE</code>)
     * @param key the encryption key
     * @param params the algorithm parameters
     *
     * @exception InvalidKeyException if the given key is inappropriate for
     * initializing this cipher, or its keysize exceeds the maximum allowable
     * keysize.
     * @exception InvalidAlgorithmParameterException if the given algorithm
     * parameters are inappropriate for this cipher,
     * or this cipher is being initialized for decryption and requires
     * algorithm parameters and <code>params</code> is null, or the given
     * algorithm parameters imply a cryptographic strength that would exceed
     * the legal limits.
     * @exception IllegalArgumentException if the opmode is invalid
     */
    public void init(int opmode, Key key, CryptoParameter params)
            throws InvalidKeyException, InvalidAlgorithmParameterException {

        if (!(key instanceof SecretKey)) {
            throw new InvalidKeyException();
        }

        if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) {
	    throw new IllegalArgumentException();
	}
	
	mode = opmode;
	ckey = (SecretKey)key;

	// Initialize the counters
        ii = new int[1];
        ii[0] = 0;
        jj = new int[1];
        jj[0] = 0;

	S = new byte[256];
	
        // Initialize S 
	for (int i = 0; i < 256; i++) {
            S[i] = (byte) i;
        }
    
	// Initilaize K based on the key
        byte[] K = new byte[256];
	int index = 0;
	while (index < 256) {
	    for (int i = 0; (i < ckey.secret.length) && (index < 256); i++) {
		K[index++] = ckey.secret[i];
	    }
	}

	// Populate the 8*8 S-box
	int j = 0;
	byte temp;
	for (int i = 0; i < 256; i++) {
	    j = (j + ((S[i] + K[i]) & 0xff)) & 0xff;
	    temp = S[i];
	    S[i] = S[j];
	    S[j] = temp;
	}
    }
    /**
     * Native function to transform a buffer.
     * @param S array of S box values
     * @param X first set intermediate results
     * @param Y second set of intermediate results
     * @param inbuf input buffer of data 
     * @param inoff offset in the provided input buffer
     * @param inlen length of data to be processed
     * @param outbuf output buffer of data 
     * @param outoff offset in the provided output buffer
     */
    private static native void nativetx(byte[] S, int[] X, int[] Y,
			byte[] inbuf, int inoff, int inlen,
			byte[] outbuf, int outoff);
    /**
     * Transform a buffer of data,
     * @param inBuf input buffer of data 
     * @param inOff offset in the provided input buffer
     * @param inLen length of data to be processed
     * @param outBuf output buffer of data 
     * @param outOff offset in the provided output buffer
     * @return number of bytes copied to output buffer
     */
    private int transform(byte[] inBuf, int inOff, int inLen,
			   byte[] outBuf, int outOff) {
        /*
	 * Normally, we would use something like:
	 * int test = inBuf[inOff] + inBuf[inLen - 1] + 
         *            inBuf[inOff + inLen - 1] + 
	 *             outBuf[outOff] + outBuf[outOff + inLen - 1];
	 * to force an array bounds check that might otherwise crash
	 * the VM. However, since we have such checks in the update
	 * method, we do not need them here.
	 */ 
	nativetx(S, ii, jj, inBuf, inOff, inLen, outBuf, outOff); 
	return inLen;
    }
    
    
    /**
     * Update the current data record.
     * 
     * @param input the input buffer
     * @param inputOffset the offset in <code>input</code> where the input
     * starts
     * @param inputLen the input length
     * @param output the buffer for the result
     * @param outputOffset the offset in <code>output</code> where the result
     * is stored
     *
     * @return the number of bytes stored in <code>output</code>
     *
     * @exception IllegalStateException if this cipher is in a wrong state
     * (e.g., has not been initialized)
     * @exception ShortBufferException if the given output buffer is too small
     * to hold the result
     */
    public int update(byte[] input, int inputOffset, int inputLen,
                      byte[] output, int outputOffset)
            throws IllegalStateException, ShortBufferException {

        Util.checkBounds(input, inputOffset, inputLen,
                         output, outputOffset);
	
	if (mode == Cipher.MODE_UNINITIALIZED) {
	    throw new IllegalStateException();
        }
	
	if (inputLen == 0) {
	    return 0;
        }    

        if (output.length - outputOffset < inputLen) {
            throw new ShortBufferException();
        }

	return transform(input, inputOffset, inputLen, output, outputOffset);
    }
    
    /**
     * Encrypts or decrypts data in a single-part operation, or finishes a
     * multiple-part operation. The data is encrypted or decrypted,
     * depending on how this cipher was initialized.
     *
     * <p>The first <code>inputLen</code> bytes in the <code>input</code>
     * buffer, starting at <code>inputOffset</code> inclusive, and any input
     * bytes that may have been buffered during a previous
     * <code>update</code> operation, are processed, with padding
     * (if requested) being applied.
     * The result is stored in the <code>output</code> buffer, starting at
     * <code>outputOffset</code> inclusive.
     *
     * <p>If the <code>output</code> buffer is too small to hold the result,
     * a <code>ShortBufferException</code> is thrown. In this case, repeat this
     * call with a larger output buffer.
     *
     * <p>Upon finishing, this method resets this cipher object to the state
     * it was in when previously initialized via a call to <code>init</code>.
     * That is, the object is reset and available to encrypt or decrypt
     * (depending on the operation mode that was specified in the call to
     * <code>init</code>) more data.
     *
     * <p>Note: if any exception is thrown, this cipher object may need to
     * be reset before it can be used again.
     *
     * <p>Note: this method should be copy-safe, which means the
     * <code>input</code> and <code>output</code> buffers can reference
     * the same byte array and no unprocessed input data is overwritten
     * when the result is copied into the output buffer.
     *
     * @param input the input buffer
     * @param inputOffset the offset in <code>input</code> where the input
     * starts
     * @param inputLen the input length
     * @param output the buffer for the result
     * @param outputOffset the offset in <code>output</code> where the result
     * is stored
     *
     * @return the number of bytes stored in <code>output</code>
     *
     * @exception IllegalStateException if this cipher is in a wrong state
     * (e.g., has not been initialized)
     * @exception IllegalBlockSizeException if this cipher is a block cipher,
     * no padding has been requested (only in encryption mode), and the total
     * input length of the data processed by this cipher is not a multiple of
     * block size
     * @exception ShortBufferException if the given output buffer is too small
     * to hold the result
     * @exception BadPaddingException if this cipher is in decryption mode,
     * and (un)padding has been requested, but the decrypted data is not
     * bounded by the appropriate padding bytes
     */
    public int doFinal(byte[] input, int inputOffset, int inputLen,
                       byte[] output, int outputOffset)
        throws IllegalStateException, ShortBufferException,
               IllegalBlockSizeException, BadPaddingException {
        int val = update(input, inputOffset, inputLen, output, outputOffset);

        try {
            init(mode, ckey);
        } catch (InvalidKeyException ike) {
            // ignore, the key was already checked
        }

        return val;
    }
}
