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

package org.bouncycastle.crypto.engines;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;

public class Grain128AEADEngine extends AEADBaseEngine
{
    private static final int STATE_SIZE = 4;
    private byte[] workingKey;
    private byte[] workingIV;
    private final int[] lfsr;
    private final int[] nfsr;
    private final int[] authAcc;
    private final int[] authSr;
    
    public Grain128AEADEngine() {
        this.algorithmName = "Grain-128 AEAD";
        this.KEY_SIZE = 16;
        this.IV_SIZE = 12;
        this.MAC_SIZE = 8;
        this.lfsr = new int[4];
        this.nfsr = new int[4];
        this.authAcc = new int[2];
        this.authSr = new int[2];
        this.setInnerMembers(ProcessingBufferType.Immediate, AADOperatorType.Stream, DataOperatorType.StreamCipher);
    }
    
    @Override
    protected void init(final byte[] workingKey, final byte[] array) throws IllegalArgumentException {
        this.workingIV = new byte[16];
        this.workingKey = workingKey;
        System.arraycopy(array, 0, this.workingIV, 0, this.IV_SIZE);
        this.workingIV[12] = -1;
        this.workingIV[13] = -1;
        this.workingIV[14] = -1;
        this.workingIV[15] = 127;
    }
    
    private void initGrain(final int[] array) {
        for (int i = 0; i < 2; ++i) {
            for (int j = 0; j < 32; ++j) {
                final int n = i;
                array[n] |= this.getByteKeyStream() << j;
            }
        }
    }
    
    private int getOutputNFSR() {
        return (this.nfsr[0] ^ this.nfsr[0] >>> 26 ^ this.nfsr[1] >>> 24 ^ this.nfsr[2] >>> 27 ^ this.nfsr[3] ^ (this.nfsr[0] >>> 3 & this.nfsr[2] >>> 3) ^ (this.nfsr[0] >>> 11 & this.nfsr[0] >>> 13) ^ (this.nfsr[0] >>> 17 & this.nfsr[0] >>> 18) ^ (this.nfsr[0] >>> 27 & this.nfsr[1] >>> 27) ^ (this.nfsr[1] >>> 8 & this.nfsr[1] >>> 16) ^ (this.nfsr[1] >>> 29 & this.nfsr[2] >>> 1) ^ (this.nfsr[2] >>> 4 & this.nfsr[2] >>> 20) ^ (this.nfsr[0] >>> 22 & this.nfsr[0] >>> 24 & this.nfsr[0] >>> 25) ^ (this.nfsr[2] >>> 6 & this.nfsr[2] >>> 14 & this.nfsr[2] >>> 18) ^ (this.nfsr[2] >>> 24 & this.nfsr[2] >>> 28 & this.nfsr[2] >>> 29 & this.nfsr[2] >>> 31)) & 0x1;
    }
    
    private int getOutputLFSR() {
        return (this.lfsr[0] ^ this.lfsr[0] >>> 7 ^ this.lfsr[1] >>> 6 ^ this.lfsr[2] >>> 6 ^ this.lfsr[2] >>> 17 ^ this.lfsr[3]) & 0x1;
    }
    
    private int getOutput() {
        final int n = this.nfsr[0] >>> 2;
        final int n2 = this.nfsr[0] >>> 12;
        final int n3 = this.nfsr[0] >>> 15;
        final int n4 = this.nfsr[1] >>> 4;
        final int n5 = this.nfsr[1] >>> 13;
        final int n6 = this.nfsr[2];
        final int n7 = this.nfsr[2] >>> 9;
        final int n8 = this.nfsr[2] >>> 25;
        final int n9 = this.nfsr[2] >>> 31;
        return ((n2 & this.lfsr[0] >>> 8) ^ (this.lfsr[0] >>> 13 & this.lfsr[0] >>> 20) ^ (n9 & this.lfsr[1] >>> 10) ^ (this.lfsr[1] >>> 28 & this.lfsr[2] >>> 15) ^ (n2 & n9 & this.lfsr[2] >>> 30) ^ this.lfsr[2] >>> 29 ^ n ^ n3 ^ n4 ^ n5 ^ n6 ^ n7 ^ n8) & 0x1;
    }
    
    private void shift(final int[] array, final int n) {
        array[0] = (array[0] >>> 1 | array[1] << 31);
        array[1] = (array[1] >>> 1 | array[2] << 31);
        array[2] = (array[2] >>> 1 | array[3] << 31);
        array[3] = (array[3] >>> 1 | n << 31);
    }
    
    private void shift() {
        this.shift(this.nfsr, (this.getOutputNFSR() ^ this.lfsr[0]) & 0x1);
        this.shift(this.lfsr, this.getOutputLFSR() & 0x1);
    }
    
    @Override
    protected void reset(final boolean b) {
        super.reset(b);
        Pack.littleEndianToInt(this.workingKey, 0, this.nfsr);
        Pack.littleEndianToInt(this.workingIV, 0, this.lfsr);
        Arrays.clear(this.authAcc);
        Arrays.clear(this.authSr);
        for (int i = 0; i < 320; ++i) {
            final int output = this.getOutput();
            this.shift(this.nfsr, (this.getOutputNFSR() ^ this.lfsr[0] ^ output) & 0x1);
            this.shift(this.lfsr, (this.getOutputLFSR() ^ output) & 0x1);
        }
        for (int j = 0; j < 8; ++j) {
            for (int k = 0; k < 8; ++k) {
                final int output2 = this.getOutput();
                this.shift(this.nfsr, (this.getOutputNFSR() ^ this.lfsr[0] ^ output2 ^ this.workingKey[j] >> k) & 0x1);
                this.shift(this.lfsr, (this.getOutputLFSR() ^ output2 ^ this.workingKey[j + 8] >> k) & 0x1);
            }
        }
        this.initGrain(this.authAcc);
        this.initGrain(this.authSr);
    }
    
    private void updateInternalState(int byteKeyStream) {
        byteKeyStream = -byteKeyStream;
        final int[] authAcc = this.authAcc;
        final int n = 0;
        authAcc[n] ^= (this.authSr[0] & byteKeyStream);
        final int[] authAcc2 = this.authAcc;
        final int n2 = 1;
        authAcc2[n2] ^= (this.authSr[1] & byteKeyStream);
        byteKeyStream = this.getByteKeyStream();
        this.authSr[0] = (this.authSr[0] >>> 1 | this.authSr[1] << 31);
        this.authSr[1] = (this.authSr[1] >>> 1 | byteKeyStream << 31);
    }
    
    @Override
    public int getUpdateOutputSize(final int n) {
        return this.getTotalBytesForUpdate(n);
    }
    
    @Override
    protected void finishAAD(final State state, final boolean b) {
        this.finishAAD1(state);
    }
    
    @Override
    protected void processFinalBlock(final byte[] array, final int n) {
        final int[] authAcc = this.authAcc;
        final int n2 = 0;
        authAcc[n2] ^= this.authSr[0];
        final int[] authAcc2 = this.authAcc;
        final int n3 = 1;
        authAcc2[n3] ^= this.authSr[1];
        Pack.intToLittleEndian(this.authAcc, this.mac, 0);
    }
    
    @Override
    protected void processBufferAAD(final byte[] array, final int n) {
    }
    
    @Override
    protected void processFinalAAD() {
        final int len = this.aadOperator.getLen();
        final byte[] bytes = ((StreamAADOperator)this.aadOperator).getBytes();
        final byte[] array = new byte[5];
        int length;
        if (len < 128) {
            length = array.length - 1;
            array[length] = (byte)len;
        }
        else {
            length = array.length;
            int i = len;
            do {
                array[--length] = (byte)i;
                i >>>= 8;
            } while (i != 0);
            array[--length] = (byte)(0x80 | array.length - length);
        }
        this.absorbAadData(array, length, array.length - length);
        this.absorbAadData(bytes, 0, len);
    }
    
    private void absorbAadData(final byte[] array, final int n, final int n2) {
        for (int i = 0; i < n2; ++i) {
            final byte b = array[n + i];
            for (int j = 0; j < 8; ++j) {
                this.shift();
                this.updateInternalState(b >> j & 0x1);
            }
        }
    }
    
    private int getByteKeyStream() {
        final int output = this.getOutput();
        this.shift();
        return output;
    }
    
    @Override
    protected void processBufferEncrypt(final byte[] array, final int n, final byte[] array2, final int n2) {
        for (int len = this.dataOperator.getLen(), i = 0; i < len; ++i) {
            byte b = 0;
            final byte b2 = array[n + i];
            for (int j = 0; j < 8; ++j) {
                final int n3 = b2 >> j & 0x1;
                b |= (byte)((n3 ^ this.getByteKeyStream()) << j);
                this.updateInternalState(n3);
            }
            array2[n2 + i] = b;
        }
    }
    
    @Override
    protected void processBufferDecrypt(final byte[] array, final int n, final byte[] array2, final int n2) {
        for (int len = this.dataOperator.getLen(), i = 0; i < len; ++i) {
            byte b = 0;
            final byte b2 = array[n + i];
            for (int j = 0; j < 8; ++j) {
                b |= (byte)(((b2 >> j & 0x1) ^ this.getByteKeyStream()) << j);
                this.updateInternalState(b >> j & 0x1);
            }
            array2[n2 + i] = b;
        }
    }
}
