// 
// Decompiled by Procyon v0.6.0
// 

package org.bouncycastle.crypto.engines;

import org.bouncycastle.util.Pack;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.CryptoServiceProperties;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.constraints.DefaultServiceProperties;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.BlockCipher;

public class LEAEngine implements BlockCipher
{
    private static final int BASEROUNDS = 16;
    private static final int NUMWORDS = 4;
    private static final int NUMWORDS128 = 4;
    private static final int MASK128 = 3;
    private static final int NUMWORDS192 = 6;
    private static final int NUMWORDS256 = 8;
    private static final int MASK256 = 7;
    private static final int BLOCKSIZE = 16;
    private static final int KEY0 = 0;
    private static final int KEY1 = 1;
    private static final int KEY2 = 2;
    private static final int KEY3 = 3;
    private static final int KEY4 = 4;
    private static final int KEY5 = 5;
    private static final int ROT1 = 1;
    private static final int ROT3 = 3;
    private static final int ROT5 = 5;
    private static final int ROT6 = 6;
    private static final int ROT9 = 9;
    private static final int ROT11 = 11;
    private static final int ROT13 = 13;
    private static final int ROT17 = 17;
    private static final int[] DELTA;
    private final int[] theBlock;
    private int theRounds;
    private int[][] theRoundKeys;
    private boolean forEncryption;
    
    public LEAEngine() {
        this.theBlock = new int[4];
    }
    
    @Override
    public void init(final boolean forEncryption, final CipherParameters cipherParameters) {
        if (!(cipherParameters instanceof KeyParameter)) {
            throw new IllegalArgumentException("Invalid parameter passed to LEA init - " + cipherParameters.getClass().getName());
        }
        final byte[] key = ((KeyParameter)cipherParameters).getKey();
        final int length = key.length;
        if ((length << 1) % 16 != 0 || length < 16 || length > 32) {
            throw new IllegalArgumentException("KeyBitSize must be 128, 192 or 256");
        }
        this.forEncryption = forEncryption;
        CryptoServicesRegistrar.checkConstraints(new DefaultServiceProperties(this.getAlgorithmName(), length * 8, cipherParameters, Utils.getPurpose(this.forEncryption)));
        this.generateRoundKeys(key);
    }
    
    @Override
    public void reset() {
    }
    
    @Override
    public String getAlgorithmName() {
        return "LEA";
    }
    
    @Override
    public int getBlockSize() {
        return 16;
    }
    
    @Override
    public int processBlock(final byte[] array, final int n, final byte[] array2, final int n2) {
        checkBuffer(array, n, false);
        checkBuffer(array2, n2, true);
        return this.forEncryption ? this.encryptBlock(array, n, array2, n2) : this.decryptBlock(array, n, array2, n2);
    }
    
    private static int bufLength(final byte[] array) {
        return (array == null) ? 0 : array.length;
    }
    
    private static void checkBuffer(final byte[] array, final int n, final boolean b) {
        final int bufLength = bufLength(array);
        final int n2 = n + 16;
        if (n < 0 || n2 < 0 || n2 > bufLength) {
            throw b ? new OutputLengthException("Output buffer too short.") : new DataLengthException("Input buffer too short.");
        }
    }
    
    private int encryptBlock(final byte[] array, final int n, final byte[] array2, final int n2) {
        Pack.littleEndianToInt(array, n, this.theBlock, 0, 4);
        for (int i = 0; i < this.theRounds; ++i) {
            this.encryptRound(i);
        }
        Pack.intToLittleEndian(this.theBlock, array2, n2);
        return 16;
    }
    
    private void encryptRound(final int n) {
        final int[] array = this.theRoundKeys[n];
        final int n2 = (3 + n) % 4;
        final int leftIndex = leftIndex(n2);
        this.theBlock[n2] = ror32((this.theBlock[leftIndex] ^ array[4]) + (this.theBlock[n2] ^ array[5]), 3);
        final int n3 = leftIndex;
        final int leftIndex2 = leftIndex(n3);
        this.theBlock[n3] = ror32((this.theBlock[leftIndex2] ^ array[2]) + (this.theBlock[n3] ^ array[3]), 5);
        final int n4 = leftIndex2;
        this.theBlock[n4] = rol32((this.theBlock[leftIndex(n4)] ^ array[0]) + (this.theBlock[n4] ^ array[1]), 9);
    }
    
    private static int leftIndex(final int n) {
        return (n == 0) ? 3 : (n - 1);
    }
    
    private int decryptBlock(final byte[] array, final int n, final byte[] array2, final int n2) {
        Pack.littleEndianToInt(array, n, this.theBlock, 0, 4);
        for (int i = this.theRounds - 1; i >= 0; --i) {
            this.decryptRound(i);
        }
        Pack.intToLittleEndian(this.theBlock, array2, n2);
        return 16;
    }
    
    private void decryptRound(final int n) {
        final int[] array = this.theRoundKeys[n];
        final int n2 = n % 4;
        final int rightIndex = rightIndex(n2);
        this.theBlock[rightIndex] = (ror32(this.theBlock[rightIndex], 9) - (this.theBlock[n2] ^ array[0]) ^ array[1]);
        final int n3 = rightIndex;
        final int rightIndex2 = rightIndex(rightIndex);
        this.theBlock[rightIndex2] = (rol32(this.theBlock[rightIndex2], 5) - (this.theBlock[n3] ^ array[2]) ^ array[3]);
        final int n4 = rightIndex2;
        final int rightIndex3 = rightIndex(rightIndex2);
        this.theBlock[rightIndex3] = (rol32(this.theBlock[rightIndex3], 3) - (this.theBlock[n4] ^ array[4]) ^ array[5]);
    }
    
    private static int rightIndex(final int n) {
        return (n == 3) ? 0 : (n + 1);
    }
    
    private void generateRoundKeys(final byte[] array) {
        this.theRounds = (array.length >> 1) + 16;
        this.theRoundKeys = new int[this.theRounds][6];
        final int n = array.length / 4;
        final int[] array2 = new int[n];
        Pack.littleEndianToInt(array, 0, array2, 0, n);
        switch (n) {
            case 4: {
                this.generate128RoundKeys(array2);
                break;
            }
            case 6: {
                this.generate192RoundKeys(array2);
                break;
            }
            default: {
                this.generate256RoundKeys(array2);
                break;
            }
        }
    }
    
    private void generate128RoundKeys(final int[] array) {
        for (int i = 0; i < this.theRounds; ++i) {
            final int rol32 = rol32(LEAEngine.DELTA[i & 0x3], i);
            int n = 0;
            array[n] = rol32(array[n++] + rol32, 1);
            array[n] = rol32(array[n] + rol32(rol32, n++), 3);
            array[n] = rol32(array[n] + rol32(rol32, n++), 6);
            array[n] = rol32(array[n] + rol32(rol32, n), 11);
            final int[] array2 = this.theRoundKeys[i];
            array2[0] = array[0];
            array2[1] = array[1];
            array2[2] = array[2];
            array2[3] = array[1];
            array2[4] = array[3];
            array2[5] = array[1];
        }
    }
    
    private void generate192RoundKeys(final int[] array) {
        for (int i = 0; i < this.theRounds; ++i) {
            final int rol32 = rol32(LEAEngine.DELTA[i % 6], i);
            int n = 0;
            array[n] = rol32(array[n] + rol32(rol32, n++), 1);
            array[n] = rol32(array[n] + rol32(rol32, n++), 3);
            array[n] = rol32(array[n] + rol32(rol32, n++), 6);
            array[n] = rol32(array[n] + rol32(rol32, n++), 11);
            array[n] = rol32(array[n] + rol32(rol32, n++), 13);
            array[n] = rol32(array[n] + rol32(rol32, n++), 17);
            System.arraycopy(array, 0, this.theRoundKeys[i], 0, n);
        }
    }
    
    private void generate256RoundKeys(final int[] array) {
        int n = 0;
        for (int i = 0; i < this.theRounds; ++i) {
            final int rol32 = rol32(LEAEngine.DELTA[i & 0x7], i);
            final int[] array2 = this.theRoundKeys[i];
            int n2 = 0;
            array2[n2] = rol32(array[n & 0x7] + rol32, 1);
            array[n++ & 0x7] = array2[n2++];
            array2[n2] = rol32(array[n & 0x7] + rol32(rol32, n2), 3);
            array[n++ & 0x7] = array2[n2++];
            array2[n2] = rol32(array[n & 0x7] + rol32(rol32, n2), 6);
            array[n++ & 0x7] = array2[n2++];
            array2[n2] = rol32(array[n & 0x7] + rol32(rol32, n2), 11);
            array[n++ & 0x7] = array2[n2++];
            array2[n2] = rol32(array[n & 0x7] + rol32(rol32, n2), 13);
            array[n++ & 0x7] = array2[n2++];
            array2[n2] = rol32(array[n & 0x7] + rol32(rol32, n2), 17);
            array[n++ & 0x7] = array2[n2];
        }
    }
    
    private static int rol32(final int n, final int n2) {
        return n << n2 | n >>> 32 - n2;
    }
    
    private static int ror32(final int n, final int n2) {
        return n >>> n2 | n << 32 - n2;
    }
    
    static {
        DELTA = new int[] { -1007687205, 1147300610, 2044886154, 2027892972, 1902027934, -947529206, -531697110, -440137385 };
    }
}
