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

package org.bouncycastle.crypto.engines;

import java.io.ByteArrayOutputStream;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.util.Arrays;
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.params.ParametersWithIV;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.modes.AEADCipher;

abstract class AEADBaseEngine implements AEADCipher
{
    protected boolean forEncryption;
    protected String algorithmName;
    protected int KEY_SIZE;
    protected int IV_SIZE;
    protected int MAC_SIZE;
    protected int macSizeLowerBound;
    protected byte[] initialAssociatedText;
    protected byte[] mac;
    protected byte[] m_buf;
    protected byte[] m_aad;
    protected int m_bufPos;
    protected int m_aadPos;
    protected int AADBufferSize;
    protected int BlockSize;
    protected State m_state;
    protected int m_bufferSizeDecrypt;
    protected AADProcessingBuffer processor;
    protected AADOperator aadOperator;
    protected DataOperator dataOperator;
    protected DecryptionFailureCounter decryptionFailureCounter;
    protected DataLimitCounter dataLimitCounter;
    
    AEADBaseEngine() {
        this.macSizeLowerBound = 0;
        this.m_state = State.Uninitialized;
        this.decryptionFailureCounter = null;
        this.dataLimitCounter = null;
    }
    
    @Override
    public String getAlgorithmName() {
        return this.algorithmName;
    }
    
    public int getKeyBytesSize() {
        return this.KEY_SIZE;
    }
    
    public int getIVBytesSize() {
        return this.IV_SIZE;
    }
    
    @Override
    public byte[] getMac() {
        return this.mac;
    }
    
    @Override
    public void init(final boolean forEncryption, final CipherParameters cipherParameters) {
        this.forEncryption = forEncryption;
        KeyParameter key;
        byte[] array;
        if (cipherParameters instanceof AEADParameters) {
            final AEADParameters aeadParameters = (AEADParameters)cipherParameters;
            key = aeadParameters.getKey();
            array = aeadParameters.getNonce();
            this.initialAssociatedText = aeadParameters.getAssociatedText();
            final int macSize = aeadParameters.getMacSize();
            if (this.macSizeLowerBound == 0) {
                if (macSize != this.MAC_SIZE << 3) {
                    throw new IllegalArgumentException("Invalid value for MAC size: " + macSize);
                }
            }
            else {
                if (macSize > 128 || macSize < this.macSizeLowerBound << 3 || (macSize & 0x7) != 0x0) {
                    throw new IllegalArgumentException("MAC size must be between " + (this.macSizeLowerBound << 3) + " and 128 bits for " + this.algorithmName);
                }
                this.MAC_SIZE = macSize >>> 3;
            }
        }
        else {
            if (!(cipherParameters instanceof ParametersWithIV)) {
                throw new IllegalArgumentException("invalid parameters passed to " + this.algorithmName);
            }
            final ParametersWithIV parametersWithIV = (ParametersWithIV)cipherParameters;
            key = (KeyParameter)parametersWithIV.getParameters();
            array = parametersWithIV.getIV();
            this.initialAssociatedText = null;
        }
        if (key == null) {
            throw new IllegalArgumentException(this.algorithmName + " Init parameters must include a key");
        }
        if (array == null || array.length != this.IV_SIZE) {
            throw new IllegalArgumentException(this.algorithmName + " requires exactly " + this.IV_SIZE + " bytes of IV");
        }
        final byte[] key2 = key.getKey();
        if (key2.length != this.KEY_SIZE) {
            throw new IllegalArgumentException(this.algorithmName + " key must be " + this.KEY_SIZE + " bytes long");
        }
        CryptoServicesRegistrar.checkConstraints(new DefaultServiceProperties(this.getAlgorithmName(), 128, cipherParameters, Utils.getPurpose(forEncryption)));
        this.m_state = (forEncryption ? State.EncInit : State.DecInit);
        this.init(key2, array);
        if (this.dataLimitCounter != null) {
            this.dataLimitCounter.increment(array.length);
        }
        this.reset(true);
        if (this.initialAssociatedText != null) {
            this.processAADBytes(this.initialAssociatedText, 0, this.initialAssociatedText.length);
        }
    }
    
    @Override
    public void reset() {
        this.reset(true);
    }
    
    protected void reset(final boolean b) {
        this.ensureInitialized();
        if (b) {
            this.mac = null;
        }
        if (this.m_buf != null) {
            Arrays.fill(this.m_buf, (byte)0);
            this.m_bufPos = 0;
        }
        if (this.m_aad != null) {
            Arrays.fill(this.m_aad, (byte)0);
            this.m_aadPos = 0;
        }
        switch (this.m_state.ord) {
            case 1:
            case 5: {
                break;
            }
            case 6:
            case 7:
            case 8: {
                this.m_state = State.DecFinal;
                break;
            }
            case 2:
            case 3:
            case 4: {
                this.m_state = State.EncFinal;
                return;
            }
            default: {
                throw new IllegalStateException(this.getAlgorithmName() + " needs to be initialized");
            }
        }
        this.aadOperator.reset();
        this.dataOperator.reset();
    }
    
    protected void setInnerMembers(final ProcessingBufferType processingBufferType, final AADOperatorType aadOperatorType, final DataOperatorType dataOperatorType) {
        switch (processingBufferType.ord) {
            case 0: {
                this.processor = new BufferedAADProcessor();
                break;
            }
            case 1: {
                this.processor = new ImmediateAADProcessor();
                break;
            }
        }
        this.m_bufferSizeDecrypt = this.BlockSize + this.MAC_SIZE;
        switch (aadOperatorType.ord) {
            case 0: {
                this.m_aad = new byte[this.AADBufferSize];
                this.aadOperator = new DefaultAADOperator();
                break;
            }
            case 1: {
                this.m_aad = new byte[this.AADBufferSize];
                this.aadOperator = new CounterAADOperator();
                break;
            }
            case 2: {
                this.AADBufferSize = 0;
                this.aadOperator = new StreamAADOperator();
                break;
            }
            case 3: {
                this.m_aad = new byte[this.AADBufferSize];
                this.dataLimitCounter = new DataLimitCounter();
                this.aadOperator = new DataLimitAADOperator();
                break;
            }
        }
        switch (dataOperatorType.ord) {
            case 0: {
                this.m_buf = new byte[this.m_bufferSizeDecrypt];
                this.dataOperator = new DefaultDataOperator();
                break;
            }
            case 1: {
                this.m_buf = new byte[this.m_bufferSizeDecrypt];
                this.dataOperator = new CounterDataOperator();
                break;
            }
            case 2: {
                this.m_buf = new byte[this.MAC_SIZE];
                this.dataOperator = new StreamDataOperator();
                break;
            }
            case 3: {
                this.BlockSize = 0;
                this.m_buf = new byte[this.m_bufferSizeDecrypt];
                this.dataOperator = new StreamCipherOperator();
                break;
            }
            case 4: {
                this.m_buf = new byte[this.m_bufferSizeDecrypt];
                this.dataOperator = new DataLimitDataOperator();
                break;
            }
        }
    }
    
    @Override
    public void processAADByte(final byte b) {
        this.checkAAD();
        this.aadOperator.processAADByte(b);
    }
    
    @Override
    public void processAADBytes(final byte[] array, final int n, final int n2) {
        this.ensureSufficientInputBuffer(array, n, n2);
        if (n2 <= 0) {
            return;
        }
        this.checkAAD();
        this.aadOperator.processAADBytes(array, n, n2);
    }
    
    private void processAadBytes(final byte[] array, int n, int aadPos) {
        if (this.m_aadPos > 0) {
            final int n2 = this.AADBufferSize - this.m_aadPos;
            if (this.processor.isLengthWithinAvailableSpace(aadPos, n2)) {
                System.arraycopy(array, n, this.m_aad, this.m_aadPos, aadPos);
                this.m_aadPos += aadPos;
                return;
            }
            System.arraycopy(array, n, this.m_aad, this.m_aadPos, n2);
            n += n2;
            aadPos -= n2;
            this.processBufferAAD(this.m_aad, 0);
        }
        while (this.processor.isLengthExceedingBlockSize(aadPos, this.AADBufferSize)) {
            this.processBufferAAD(array, n);
            n += this.AADBufferSize;
            aadPos -= this.AADBufferSize;
        }
        System.arraycopy(array, n, this.m_aad, 0, aadPos);
        this.m_aadPos = aadPos;
    }
    
    @Override
    public int processByte(final byte b, final byte[] array, final int n) throws DataLengthException {
        return this.dataOperator.processByte(b, array, n);
    }
    
    protected int processEncDecByte(final byte[] array, final int n) {
        int blockSize = 0;
        if ((this.forEncryption ? this.BlockSize : this.m_bufferSizeDecrypt) - this.m_bufPos == 0) {
            this.ensureSufficientOutputBuffer(array, n, this.BlockSize);
            if (this.forEncryption) {
                this.processBufferEncrypt(this.m_buf, 0, array, n);
            }
            else {
                this.processBufferDecrypt(this.m_buf, 0, array, n);
                System.arraycopy(this.m_buf, this.BlockSize, this.m_buf, 0, this.m_bufPos - this.BlockSize);
            }
            this.m_bufPos -= this.BlockSize;
            blockSize = this.BlockSize;
        }
        return blockSize;
    }
    
    @Override
    public int processBytes(final byte[] array, final int n, final int n2, final byte[] array2, final int n3) throws DataLengthException {
        this.ensureSufficientInputBuffer(array, n, n2);
        return this.dataOperator.processBytes(array, n, n2, array2, n3);
    }
    
    protected int processEncDecBytes(byte[] array, int n, int bufPos, final byte[] array2, final int n2) {
        final boolean checkData = this.checkData(false);
        final int n3 = (checkData ? this.BlockSize : this.m_bufferSizeDecrypt) - this.m_bufPos;
        if (this.processor.isLengthWithinAvailableSpace(bufPos, n3)) {
            System.arraycopy(array, n, this.m_buf, this.m_bufPos, bufPos);
            this.m_bufPos += bufPos;
            return 0;
        }
        final int updateOutputSize = this.processor.getUpdateOutputSize(bufPos);
        final int n4 = updateOutputSize + this.m_bufPos - (checkData ? 0 : this.MAC_SIZE);
        this.ensureSufficientOutputBuffer(array2, n2, n4 - n4 % this.BlockSize);
        int blockSize = 0;
        if (array == array2 && Arrays.segmentsOverlap(n, bufPos, n2, updateOutputSize)) {
            array = new byte[bufPos];
            System.arraycopy(array2, n, array, 0, bufPos);
            n = 0;
        }
        if (checkData) {
            if (this.m_bufPos > 0) {
                System.arraycopy(array, n, this.m_buf, this.m_bufPos, n3);
                n += n3;
                bufPos -= n3;
                this.processBufferEncrypt(this.m_buf, 0, array2, n2);
                blockSize = this.BlockSize;
            }
            while (this.processor.isLengthExceedingBlockSize(bufPos, this.BlockSize)) {
                this.processBufferEncrypt(array, n, array2, n2 + blockSize);
                n += this.BlockSize;
                bufPos -= this.BlockSize;
                blockSize += this.BlockSize;
            }
        }
        else {
            while (this.processor.isLengthExceedingBlockSize(this.m_bufPos, this.BlockSize) && this.processor.isLengthExceedingBlockSize(bufPos + this.m_bufPos, this.m_bufferSizeDecrypt)) {
                this.processBufferDecrypt(this.m_buf, blockSize, array2, n2 + blockSize);
                this.m_bufPos -= this.BlockSize;
                blockSize += this.BlockSize;
            }
            if (this.m_bufPos > 0) {
                System.arraycopy(this.m_buf, blockSize, this.m_buf, 0, this.m_bufPos);
                if (this.processor.isLengthWithinAvailableSpace(this.m_bufPos + bufPos, this.m_bufferSizeDecrypt)) {
                    System.arraycopy(array, n, this.m_buf, this.m_bufPos, bufPos);
                    this.m_bufPos += bufPos;
                    return blockSize;
                }
                final int max = Math.max(this.BlockSize - this.m_bufPos, 0);
                System.arraycopy(array, n, this.m_buf, this.m_bufPos, max);
                n += max;
                bufPos -= max;
                this.processBufferDecrypt(this.m_buf, 0, array2, n2 + blockSize);
                blockSize += this.BlockSize;
            }
            while (this.processor.isLengthExceedingBlockSize(bufPos, this.m_bufferSizeDecrypt)) {
                this.processBufferDecrypt(array, n, array2, n2 + blockSize);
                n += this.BlockSize;
                bufPos -= this.BlockSize;
                blockSize += this.BlockSize;
            }
        }
        System.arraycopy(array, n, this.m_buf, 0, bufPos);
        this.m_bufPos = bufPos;
        return blockSize;
    }
    
    @Override
    public int doFinal(final byte[] array, final int n) throws IllegalStateException, InvalidCipherTextException {
        final boolean checkData = this.checkData(true);
        int bufPos;
        if (checkData) {
            bufPos = this.m_bufPos + this.MAC_SIZE;
        }
        else {
            if (this.m_bufPos < this.MAC_SIZE) {
                throw new InvalidCipherTextException("data too short");
            }
            this.m_bufPos -= this.MAC_SIZE;
            bufPos = this.m_bufPos;
        }
        this.ensureSufficientOutputBuffer(array, n, bufPos);
        this.mac = new byte[this.MAC_SIZE];
        this.processFinalBlock(array, n);
        if (checkData) {
            System.arraycopy(this.mac, 0, array, n + bufPos - this.MAC_SIZE, this.MAC_SIZE);
        }
        else if (!Arrays.constantTimeAreEqual(this.MAC_SIZE, this.mac, 0, this.m_buf, this.m_bufPos)) {
            if (this.decryptionFailureCounter != null && this.decryptionFailureCounter.increment()) {
                throw new InvalidCipherTextException(this.algorithmName + " decryption failure limit exceeded");
            }
            throw new InvalidCipherTextException(this.algorithmName + " mac does not match");
        }
        this.reset(!checkData);
        return bufPos;
    }
    
    public final int getBlockSize() {
        return this.BlockSize;
    }
    
    @Override
    public int getUpdateOutputSize(final int n) {
        final int totalBytesForUpdate = this.getTotalBytesForUpdate(n);
        return totalBytesForUpdate - totalBytesForUpdate % this.BlockSize;
    }
    
    protected int getTotalBytesForUpdate(final int n) {
        int n2 = this.processor.getUpdateOutputSize(n);
        switch (this.m_state.ord) {
            case 5:
            case 6:
            case 7:
            case 8: {
                n2 = Math.max(0, n2 + this.m_bufPos - this.MAC_SIZE);
                break;
            }
            case 3:
            case 4: {
                n2 = Math.max(0, n2 + this.m_bufPos);
                break;
            }
        }
        return n2;
    }
    
    @Override
    public int getOutputSize(final int b) {
        final int max = Math.max(0, b);
        switch (this.m_state.ord) {
            case 5:
            case 6:
            case 7:
            case 8: {
                return Math.max(0, max + this.m_bufPos - this.MAC_SIZE);
            }
            case 3:
            case 4: {
                return max + this.m_bufPos + this.MAC_SIZE;
            }
            default: {
                return max + this.MAC_SIZE;
            }
        }
    }
    
    protected void checkAAD() {
        switch (this.m_state.ord) {
            case 5: {
                this.m_state = State.DecAad;
                break;
            }
            case 1: {
                this.m_state = State.EncAad;
                break;
            }
            case 2:
            case 6: {
                break;
            }
            case 4: {
                throw new IllegalStateException(this.getAlgorithmName() + " cannot be reused for encryption");
            }
            default: {
                throw new IllegalStateException(this.getAlgorithmName() + " needs to be initialized");
            }
        }
    }
    
    protected boolean checkData(final boolean b) {
        switch (this.m_state.ord) {
            case 5:
            case 6: {
                this.finishAAD(State.DecData, b);
                return false;
            }
            case 1:
            case 2: {
                this.finishAAD(State.EncData, b);
                return true;
            }
            case 7: {
                return false;
            }
            case 3: {
                return true;
            }
            case 4: {
                throw new IllegalStateException(this.getAlgorithmName() + " cannot be reused for encryption");
            }
            default: {
                throw new IllegalStateException(this.getAlgorithmName() + " needs to be initialized");
            }
        }
    }
    
    protected final void ensureSufficientOutputBuffer(final byte[] array, final int n, final int n2) {
        if (n + n2 > array.length) {
            throw new OutputLengthException("output buffer too short");
        }
    }
    
    protected final void ensureSufficientInputBuffer(final byte[] array, final int n, final int n2) {
        if (n + n2 > array.length) {
            throw new DataLengthException("input buffer too short");
        }
    }
    
    protected final void ensureInitialized() {
        if (this.m_state == State.Uninitialized) {
            throw new IllegalStateException("Need to call init function before operation");
        }
    }
    
    protected void finishAAD1(final State state) {
        switch (this.m_state.ord) {
            case 1:
            case 2:
            case 5:
            case 6: {
                this.processFinalAAD();
                break;
            }
        }
        this.m_state = state;
    }
    
    protected void finishAAD2(final State state) {
        switch (this.m_state.ord) {
            case 2:
            case 6: {
                this.processFinalAAD();
                break;
            }
        }
        this.m_aadPos = 0;
        this.m_state = state;
    }
    
    protected void finishAAD3(final State state, final boolean b) {
        switch (this.m_state.ord) {
            case 5:
            case 6: {
                if (!b && this.dataOperator.getLen() <= this.MAC_SIZE) {
                    return;
                }
            }
            case 1:
            case 2: {
                this.processFinalAAD();
                break;
            }
        }
        this.m_aadPos = 0;
        this.m_state = state;
    }
    
    protected abstract void finishAAD(final State p0, final boolean p1);
    
    protected abstract void init(final byte[] p0, final byte[] p1);
    
    protected abstract void processFinalBlock(final byte[] p0, final int p1);
    
    protected abstract void processBufferAAD(final byte[] p0, final int p1);
    
    protected abstract void processFinalAAD();
    
    protected abstract void processBufferEncrypt(final byte[] p0, final int p1, final byte[] p2, final int p3);
    
    protected abstract void processBufferDecrypt(final byte[] p0, final int p1, final byte[] p2, final int p3);
    
    protected interface AADOperator
    {
        void processAADByte(final byte p0);
        
        void processAADBytes(final byte[] p0, final int p1, final int p2);
        
        void reset();
        
        int getLen();
    }
    
    protected static class AADOperatorType
    {
        public static final int DEFAULT = 0;
        public static final int COUNTER = 1;
        public static final int STREAM = 2;
        public static final int DATA_LIMIT = 3;
        public static final AADOperatorType Default;
        public static final AADOperatorType Counter;
        public static final AADOperatorType Stream;
        public static final AADOperatorType DataLimit;
        private final int ord;
        
        AADOperatorType(final int ord) {
            this.ord = ord;
        }
        
        static {
            Default = new AADOperatorType(0);
            Counter = new AADOperatorType(1);
            Stream = new AADOperatorType(2);
            DataLimit = new AADOperatorType(3);
        }
    }
    
    private interface AADProcessingBuffer
    {
        void processAADByte(final byte p0);
        
        int processByte(final byte p0, final byte[] p1, final int p2);
        
        int getUpdateOutputSize(final int p0);
        
        boolean isLengthWithinAvailableSpace(final int p0, final int p1);
        
        boolean isLengthExceedingBlockSize(final int p0, final int p1);
    }
    
    private class BufferedAADProcessor implements AADProcessingBuffer
    {
        @Override
        public void processAADByte(final byte b) {
            if (AEADBaseEngine.this.m_aadPos == AEADBaseEngine.this.AADBufferSize) {
                AEADBaseEngine.this.processBufferAAD(AEADBaseEngine.this.m_aad, 0);
                AEADBaseEngine.this.m_aadPos = 0;
            }
            AEADBaseEngine.this.m_aad[AEADBaseEngine.this.m_aadPos++] = b;
        }
        
        @Override
        public int processByte(final byte b, final byte[] array, final int n) {
            AEADBaseEngine.this.checkData(false);
            final int processEncDecByte = AEADBaseEngine.this.processEncDecByte(array, n);
            AEADBaseEngine.this.m_buf[AEADBaseEngine.this.m_bufPos++] = b;
            return processEncDecByte;
        }
        
        @Override
        public boolean isLengthWithinAvailableSpace(final int n, final int n2) {
            return n <= n2;
        }
        
        @Override
        public boolean isLengthExceedingBlockSize(final int n, final int n2) {
            return n > n2;
        }
        
        @Override
        public int getUpdateOutputSize(final int b) {
            return Math.max(0, b) - 1;
        }
    }
    
    private class CounterAADOperator implements AADOperator
    {
        private int aadLen;
        
        @Override
        public void processAADByte(final byte b) {
            ++this.aadLen;
            AEADBaseEngine.this.processor.processAADByte(b);
        }
        
        @Override
        public void processAADBytes(final byte[] array, final int n, final int n2) {
            this.aadLen += n2;
            AEADBaseEngine.this.processAadBytes(array, n, n2);
        }
        
        @Override
        public int getLen() {
            return this.aadLen;
        }
        
        @Override
        public void reset() {
            this.aadLen = 0;
        }
    }
    
    private class CounterDataOperator implements DataOperator
    {
        private int messegeLen;
        
        @Override
        public int processByte(final byte b, final byte[] array, final int n) {
            ++this.messegeLen;
            return AEADBaseEngine.this.processor.processByte(b, array, n);
        }
        
        @Override
        public int processBytes(final byte[] array, final int n, final int n2, final byte[] array2, final int n3) {
            this.messegeLen += n2;
            return AEADBaseEngine.this.processEncDecBytes(array, n, n2, array2, n3);
        }
        
        @Override
        public int getLen() {
            return this.messegeLen;
        }
        
        @Override
        public void reset() {
            this.messegeLen = 0;
        }
    }
    
    protected interface DataOperator
    {
        int processByte(final byte p0, final byte[] p1, final int p2);
        
        int processBytes(final byte[] p0, final int p1, final int p2, final byte[] p3, final int p4);
        
        int getLen();
        
        void reset();
    }
    
    private class DataLimitAADOperator implements AADOperator
    {
        @Override
        public void processAADByte(final byte b) {
            AEADBaseEngine.this.dataLimitCounter.increment();
            AEADBaseEngine.this.processor.processAADByte(b);
        }
        
        @Override
        public void processAADBytes(final byte[] array, final int n, final int n2) {
            AEADBaseEngine.this.dataLimitCounter.increment(n2);
            AEADBaseEngine.this.processAadBytes(array, n, n2);
        }
        
        @Override
        public void reset() {
        }
        
        @Override
        public int getLen() {
            return AEADBaseEngine.this.m_aadPos;
        }
    }
    
    protected static class DataLimitCounter
    {
        private long count;
        private long max;
        private int n;
        
        public void init(final int n) {
            this.n = n;
            this.max = 1L << n;
        }
        
        public void increment() {
            final long count = this.count + 1L;
            this.count = count;
            if (count > this.max) {
                throw new IllegalStateException("Total data limit exceeded: maximum 2^" + this.n + " bytes per key (including nonce, AAD, and message)");
            }
        }
        
        public void increment(final int i) {
            this.count += i;
            if (this.count > this.max) {
                throw new IllegalStateException("Total data limit exceeded: maximum 2^" + i + " bytes per key (including nonce, AAD, and message)");
            }
        }
        
        public void reset() {
            this.count = 0L;
        }
    }
    
    private class DataLimitDataOperator implements DataOperator
    {
        @Override
        public int processByte(final byte b, final byte[] array, final int n) {
            AEADBaseEngine.this.dataLimitCounter.increment();
            return AEADBaseEngine.this.processor.processByte(b, array, n);
        }
        
        @Override
        public int processBytes(final byte[] array, final int n, final int n2, final byte[] array2, final int n3) {
            AEADBaseEngine.this.dataLimitCounter.increment(n2);
            return AEADBaseEngine.this.processEncDecBytes(array, n, n2, array2, n3);
        }
        
        @Override
        public int getLen() {
            return AEADBaseEngine.this.m_bufPos;
        }
        
        @Override
        public void reset() {
        }
    }
    
    protected static class DataOperatorType
    {
        public static final int DEFAULT = 0;
        public static final int COUNTER = 1;
        public static final int STREAM = 2;
        public static final int STREAM_CIPHER = 3;
        public static final int DATA_LIMIT = 4;
        public static final DataOperatorType Default;
        public static final DataOperatorType Counter;
        public static final DataOperatorType Stream;
        public static final DataOperatorType StreamCipher;
        public static final DataOperatorType DataLimit;
        private final int ord;
        
        DataOperatorType(final int ord) {
            this.ord = ord;
        }
        
        static {
            Default = new DataOperatorType(0);
            Counter = new DataOperatorType(1);
            Stream = new DataOperatorType(2);
            StreamCipher = new DataOperatorType(3);
            DataLimit = new DataOperatorType(4);
        }
    }
    
    protected static class DecryptionFailureCounter
    {
        private int n;
        private int[] counter;
        
        public void init(final int n) {
            if (this.n != n) {
                this.n = n;
                final int n2 = n + 31 >>> 5;
                if (this.counter == null || n2 != this.counter.length) {
                    this.counter = new int[n2];
                }
                else {
                    this.reset();
                }
            }
        }
        
        public boolean increment() {
            int length = this.counter.length;
            while (--length >= 0 && ++this.counter[length] == 0) {}
            final int n = this.n & 0x1F;
            return length <= 0 && this.counter[0] == ((n == 0) ? 0 : (1 << n));
        }
        
        public void reset() {
            Arrays.fill(this.counter, 0);
        }
    }
    
    private class DefaultAADOperator implements AADOperator
    {
        @Override
        public void processAADByte(final byte b) {
            AEADBaseEngine.this.processor.processAADByte(b);
        }
        
        @Override
        public void processAADBytes(final byte[] array, final int n, final int n2) {
            AEADBaseEngine.this.processAadBytes(array, n, n2);
        }
        
        @Override
        public void reset() {
        }
        
        @Override
        public int getLen() {
            return AEADBaseEngine.this.m_aadPos;
        }
    }
    
    private class DefaultDataOperator implements DataOperator
    {
        @Override
        public int processByte(final byte b, final byte[] array, final int n) {
            return AEADBaseEngine.this.processor.processByte(b, array, n);
        }
        
        @Override
        public int processBytes(final byte[] array, final int n, final int n2, final byte[] array2, final int n3) {
            return AEADBaseEngine.this.processEncDecBytes(array, n, n2, array2, n3);
        }
        
        @Override
        public int getLen() {
            return AEADBaseEngine.this.m_bufPos;
        }
        
        @Override
        public void reset() {
        }
    }
    
    protected static final class ErasableOutputStream extends ByteArrayOutputStream
    {
        public ErasableOutputStream() {
        }
        
        public byte[] getBuf() {
            return this.buf;
        }
    }
    
    private class ImmediateAADProcessor implements AADProcessingBuffer
    {
        @Override
        public void processAADByte(final byte b) {
            AEADBaseEngine.this.m_aad[AEADBaseEngine.this.m_aadPos++] = b;
            if (AEADBaseEngine.this.m_aadPos == AEADBaseEngine.this.AADBufferSize) {
                AEADBaseEngine.this.processBufferAAD(AEADBaseEngine.this.m_aad, 0);
                AEADBaseEngine.this.m_aadPos = 0;
            }
        }
        
        @Override
        public int processByte(final byte b, final byte[] array, final int n) {
            AEADBaseEngine.this.checkData(false);
            AEADBaseEngine.this.m_buf[AEADBaseEngine.this.m_bufPos++] = b;
            return AEADBaseEngine.this.processEncDecByte(array, n);
        }
        
        @Override
        public int getUpdateOutputSize(final int b) {
            return Math.max(0, b);
        }
        
        @Override
        public boolean isLengthWithinAvailableSpace(final int n, final int n2) {
            return n < n2;
        }
        
        @Override
        public boolean isLengthExceedingBlockSize(final int n, final int n2) {
            return n >= n2;
        }
    }
    
    protected static class ProcessingBufferType
    {
        public static final int BUFFERED = 0;
        public static final int IMMEDIATE = 1;
        public static final ProcessingBufferType Buffered;
        public static final ProcessingBufferType Immediate;
        private final int ord;
        
        ProcessingBufferType(final int ord) {
            this.ord = ord;
        }
        
        static {
            Buffered = new ProcessingBufferType(0);
            Immediate = new ProcessingBufferType(1);
        }
    }
    
    protected static class State
    {
        public static final int UNINITIALIZED = 0;
        public static final int ENC_INIT = 1;
        public static final int ENC_AAD = 2;
        public static final int ENC_DATA = 3;
        public static final int ENC_FINAL = 4;
        public static final int DEC_INIT = 5;
        public static final int DEC_AAD = 6;
        public static final int DEC_DATA = 7;
        public static final int DEC_FINAL = 8;
        public static final State Uninitialized;
        public static final State EncInit;
        public static final State EncAad;
        public static final State EncData;
        public static final State EncFinal;
        public static final State DecInit;
        public static final State DecAad;
        public static final State DecData;
        public static final State DecFinal;
        final int ord;
        
        State(final int ord) {
            this.ord = ord;
        }
        
        static {
            Uninitialized = new State(0);
            EncInit = new State(1);
            EncAad = new State(2);
            EncData = new State(3);
            EncFinal = new State(4);
            DecInit = new State(5);
            DecAad = new State(6);
            DecData = new State(7);
            DecFinal = new State(8);
        }
    }
    
    protected static class StreamAADOperator implements AADOperator
    {
        private final ErasableOutputStream stream;
        
        protected StreamAADOperator() {
            this.stream = new ErasableOutputStream();
        }
        
        @Override
        public void processAADByte(final byte b) {
            this.stream.write(b);
        }
        
        @Override
        public void processAADBytes(final byte[] b, final int off, final int len) {
            this.stream.write(b, off, len);
        }
        
        public byte[] getBytes() {
            return this.stream.getBuf();
        }
        
        @Override
        public void reset() {
            this.stream.reset();
        }
        
        @Override
        public int getLen() {
            return this.stream.size();
        }
    }
    
    private class StreamCipherOperator implements DataOperator
    {
        private int len;
        
        @Override
        public int processByte(final byte b, final byte[] array, final int n) {
            if (AEADBaseEngine.this.checkData(false)) {
                this.len = 1;
                AEADBaseEngine.this.processBufferEncrypt(new byte[] { b }, 0, array, n);
                return 1;
            }
            if (AEADBaseEngine.this.m_bufPos == AEADBaseEngine.this.MAC_SIZE) {
                this.len = 1;
                AEADBaseEngine.this.processBufferDecrypt(AEADBaseEngine.this.m_buf, 0, array, n);
                System.arraycopy(AEADBaseEngine.this.m_buf, 1, AEADBaseEngine.this.m_buf, 0, AEADBaseEngine.this.m_bufPos - 1);
                AEADBaseEngine.this.m_buf[AEADBaseEngine.this.m_bufPos - 1] = b;
                return 1;
            }
            AEADBaseEngine.this.m_buf[AEADBaseEngine.this.m_bufPos++] = b;
            return 0;
        }
        
        @Override
        public int processBytes(byte[] array, int n, int len, final byte[] array2, final int n2) {
            if (array == array2 && Arrays.segmentsOverlap(n, len, n2, AEADBaseEngine.this.processor.getUpdateOutputSize(len))) {
                array = new byte[len];
                System.arraycopy(array2, n, array, 0, len);
                n = 0;
            }
            if (AEADBaseEngine.this.checkData(false)) {
                this.len = len;
                AEADBaseEngine.this.processBufferEncrypt(array, n, array2, n2);
                return len;
            }
            int max = Math.max(AEADBaseEngine.this.m_bufPos + len - AEADBaseEngine.this.MAC_SIZE, 0);
            int len2 = 0;
            if (AEADBaseEngine.this.m_bufPos > 0) {
                this.len = Math.min(max, AEADBaseEngine.this.m_bufPos);
                len2 = this.len;
                AEADBaseEngine.this.processBufferDecrypt(AEADBaseEngine.this.m_buf, 0, array2, n2);
                max -= len2;
                final AEADBaseEngine this$0 = AEADBaseEngine.this;
                this$0.m_bufPos -= len2;
                System.arraycopy(AEADBaseEngine.this.m_buf, len2, AEADBaseEngine.this.m_buf, 0, AEADBaseEngine.this.m_bufPos);
            }
            if (max > 0) {
                this.len = max;
                AEADBaseEngine.this.processBufferDecrypt(array, n, array2, n2);
                len2 += max;
                len -= max;
                n += max;
            }
            System.arraycopy(array, n, AEADBaseEngine.this.m_buf, AEADBaseEngine.this.m_bufPos, len);
            final AEADBaseEngine this$2 = AEADBaseEngine.this;
            this$2.m_bufPos += len;
            return len2;
        }
        
        @Override
        public int getLen() {
            return this.len;
        }
        
        @Override
        public void reset() {
        }
    }
    
    protected class StreamDataOperator implements DataOperator
    {
        private final ErasableOutputStream stream;
        
        protected StreamDataOperator() {
            this.stream = new ErasableOutputStream();
        }
        
        @Override
        public int processByte(final byte b, final byte[] array, final int n) {
            AEADBaseEngine.this.checkData(false);
            AEADBaseEngine.this.ensureInitialized();
            this.stream.write(b);
            AEADBaseEngine.this.m_bufPos = this.stream.size();
            return 0;
        }
        
        @Override
        public int processBytes(final byte[] b, final int off, final int len, final byte[] array, final int n) {
            AEADBaseEngine.this.checkData(false);
            AEADBaseEngine.this.ensureInitialized();
            this.stream.write(b, off, len);
            AEADBaseEngine.this.m_bufPos = this.stream.size();
            return 0;
        }
        
        public byte[] getBytes() {
            return this.stream.getBuf();
        }
        
        @Override
        public int getLen() {
            return this.stream.size();
        }
        
        @Override
        public void reset() {
            this.stream.reset();
        }
    }
}
