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

package org.bouncycastle.crypto.modes;

import java.io.ByteArrayOutputStream;
import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.BlockCipher;

public class CCMBlockCipher implements CCMModeCipher
{
    private BlockCipher cipher;
    private int blockSize;
    private boolean forEncryption;
    private byte[] nonce;
    private byte[] initialAssociatedText;
    private int macSize;
    private CipherParameters keyParam;
    private byte[] macBlock;
    private ExposedByteArrayOutputStream associatedText;
    private ExposedByteArrayOutputStream data;
    
    public static CCMModeCipher newInstance(final BlockCipher blockCipher) {
        return new CCMBlockCipher(blockCipher);
    }
    
    @Deprecated
    public CCMBlockCipher(final BlockCipher cipher) {
        this.associatedText = new ExposedByteArrayOutputStream();
        this.data = new ExposedByteArrayOutputStream();
        this.cipher = cipher;
        this.blockSize = cipher.getBlockSize();
        this.macBlock = new byte[this.blockSize];
        if (this.blockSize != 16) {
            throw new IllegalArgumentException("cipher required with a block size of 16.");
        }
    }
    
    @Override
    public BlockCipher getUnderlyingCipher() {
        return this.cipher;
    }
    
    @Override
    public void init(final boolean forEncryption, final CipherParameters cipherParameters) throws IllegalArgumentException {
        this.forEncryption = forEncryption;
        CipherParameters keyParam;
        if (cipherParameters instanceof AEADParameters) {
            final AEADParameters aeadParameters = (AEADParameters)cipherParameters;
            this.nonce = aeadParameters.getNonce();
            this.initialAssociatedText = aeadParameters.getAssociatedText();
            this.macSize = this.getMacSize(forEncryption, aeadParameters.getMacSize());
            keyParam = aeadParameters.getKey();
        }
        else {
            if (!(cipherParameters instanceof ParametersWithIV)) {
                throw new IllegalArgumentException("invalid parameters passed to CCM: " + cipherParameters.getClass().getName());
            }
            final ParametersWithIV parametersWithIV = (ParametersWithIV)cipherParameters;
            this.nonce = parametersWithIV.getIV();
            this.initialAssociatedText = null;
            this.macSize = this.getMacSize(forEncryption, 64);
            keyParam = parametersWithIV.getParameters();
        }
        if (keyParam != null) {
            this.keyParam = keyParam;
        }
        if (this.nonce == null || this.nonce.length < 7 || this.nonce.length > 13) {
            throw new IllegalArgumentException("nonce must have length from 7 to 13 octets");
        }
        this.reset();
    }
    
    @Override
    public String getAlgorithmName() {
        return this.cipher.getAlgorithmName() + "/CCM";
    }
    
    @Override
    public void processAADByte(final byte b) {
        this.associatedText.write(b);
    }
    
    @Override
    public void processAADBytes(final byte[] b, final int off, final int len) {
        this.associatedText.write(b, off, len);
    }
    
    @Override
    public int processByte(final byte b, final byte[] array, final int n) throws DataLengthException, IllegalStateException {
        this.data.write(b);
        return 0;
    }
    
    @Override
    public int processBytes(final byte[] b, final int off, final int len, final byte[] array, final int n) throws DataLengthException, IllegalStateException {
        if (b.length < off + len) {
            throw new DataLengthException("Input buffer too short");
        }
        this.data.write(b, off, len);
        return 0;
    }
    
    @Override
    public int doFinal(final byte[] array, final int n) throws IllegalStateException, InvalidCipherTextException {
        final int processPacket = this.processPacket(this.data.getBuffer(), 0, this.data.size(), array, n);
        this.reset();
        return processPacket;
    }
    
    @Override
    public void reset() {
        this.cipher.reset();
        this.associatedText.reset();
        this.data.reset();
    }
    
    @Override
    public byte[] getMac() {
        final byte[] array = new byte[this.macSize];
        System.arraycopy(this.macBlock, 0, array, 0, array.length);
        return array;
    }
    
    @Override
    public int getUpdateOutputSize(final int n) {
        return 0;
    }
    
    @Override
    public int getOutputSize(final int n) {
        final int n2 = n + this.data.size();
        if (this.forEncryption) {
            return n2 + this.macSize;
        }
        return (n2 < this.macSize) ? 0 : (n2 - this.macSize);
    }
    
    public byte[] processPacket(final byte[] array, final int n, final int n2) throws IllegalStateException, InvalidCipherTextException {
        byte[] array2;
        if (this.forEncryption) {
            array2 = new byte[n2 + this.macSize];
        }
        else {
            if (n2 < this.macSize) {
                throw new InvalidCipherTextException("data too short");
            }
            array2 = new byte[n2 - this.macSize];
        }
        this.processPacket(array, n, n2, array2, 0);
        return array2;
    }
    
    public int processPacket(final byte[] array, final int n, final int n2, final byte[] array2, final int n3) throws IllegalStateException, InvalidCipherTextException, DataLengthException {
        if (this.keyParam == null) {
            throw new IllegalStateException("CCM cipher unitialized.");
        }
        final int n4 = 15 - this.nonce.length;
        if (n4 < 4) {
            final int n5 = 1 << 8 * n4;
            int n6 = 0;
            if (!this.forEncryption) {
                n6 = 16;
            }
            if (n2 - n6 >= n5) {
                throw new IllegalStateException("CCM packet too large for choice of q");
            }
        }
        final byte[] array3 = new byte[this.blockSize];
        array3[0] = (byte)(n4 - 1 & 0x7);
        System.arraycopy(this.nonce, 0, array3, 1, this.nonce.length);
        final CTRModeCipher instance = SICBlockCipher.newInstance(this.cipher);
        instance.init(this.forEncryption, new ParametersWithIV(this.keyParam, array3));
        int i = n;
        int n7 = n3;
        int n8;
        if (this.forEncryption) {
            n8 = n2 + this.macSize;
            if (array2.length < n8 + n3) {
                throw new OutputLengthException("Output buffer too short.");
            }
            this.calculateMac(array, n, n2, this.macBlock);
            final byte[] array4 = new byte[this.blockSize];
            instance.processBlock(this.macBlock, 0, array4, 0);
            while (i < n + n2 - this.blockSize) {
                instance.processBlock(array, i, array2, n7);
                n7 += this.blockSize;
                i += this.blockSize;
            }
            final byte[] array5 = new byte[this.blockSize];
            System.arraycopy(array, i, array5, 0, n2 + n - i);
            instance.processBlock(array5, 0, array5, 0);
            System.arraycopy(array5, 0, array2, n7, n2 + n - i);
            System.arraycopy(array4, 0, array2, n3 + n2, this.macSize);
        }
        else {
            if (n2 < this.macSize) {
                throw new InvalidCipherTextException("data too short");
            }
            n8 = n2 - this.macSize;
            if (array2.length < n8 + n3) {
                throw new OutputLengthException("Output buffer too short.");
            }
            System.arraycopy(array, n + n8, this.macBlock, 0, this.macSize);
            instance.processBlock(this.macBlock, 0, this.macBlock, 0);
            for (int j = this.macSize; j != this.macBlock.length; ++j) {
                this.macBlock[j] = 0;
            }
            while (i < n + n8 - this.blockSize) {
                instance.processBlock(array, i, array2, n7);
                n7 += this.blockSize;
                i += this.blockSize;
            }
            final byte[] array6 = new byte[this.blockSize];
            System.arraycopy(array, i, array6, 0, n8 - (i - n));
            instance.processBlock(array6, 0, array6, 0);
            System.arraycopy(array6, 0, array2, n7, n8 - (i - n));
            final byte[] array7 = new byte[this.blockSize];
            this.calculateMac(array2, n3, n8, array7);
            if (!Arrays.constantTimeAreEqual(this.macBlock, array7)) {
                throw new InvalidCipherTextException("mac check in CCM failed");
            }
        }
        return n8;
    }
    
    private int calculateMac(final byte[] array, final int n, final int n2, final byte[] array2) {
        final CBCBlockCipherMac cbcBlockCipherMac = new CBCBlockCipherMac(this.cipher, this.macSize * 8);
        cbcBlockCipherMac.init(this.keyParam);
        final byte[] array3 = new byte[16];
        if (this.hasAssociatedText()) {
            final byte[] array4 = array3;
            final int n3 = 0;
            array4[n3] |= 0x40;
        }
        final byte[] array5 = array3;
        final int n4 = 0;
        array5[n4] |= (byte)(((cbcBlockCipherMac.getMacSize() - 2) / 2 & 0x7) << 3);
        final byte[] array6 = array3;
        final int n5 = 0;
        array6[n5] |= (byte)(15 - this.nonce.length - 1 & 0x7);
        System.arraycopy(this.nonce, 0, array3, 1, this.nonce.length);
        for (int i = n2, n6 = 1; i > 0; i >>>= 8, ++n6) {
            array3[array3.length - n6] = (byte)(i & 0xFF);
        }
        cbcBlockCipherMac.update(array3, 0, array3.length);
        if (this.hasAssociatedText()) {
            final int associatedTextLength = this.getAssociatedTextLength();
            int n7;
            if (associatedTextLength < 65280) {
                cbcBlockCipherMac.update((byte)(associatedTextLength >> 8));
                cbcBlockCipherMac.update((byte)associatedTextLength);
                n7 = 2;
            }
            else {
                cbcBlockCipherMac.update((byte)(-1));
                cbcBlockCipherMac.update((byte)(-2));
                cbcBlockCipherMac.update((byte)(associatedTextLength >> 24));
                cbcBlockCipherMac.update((byte)(associatedTextLength >> 16));
                cbcBlockCipherMac.update((byte)(associatedTextLength >> 8));
                cbcBlockCipherMac.update((byte)associatedTextLength);
                n7 = 6;
            }
            if (this.initialAssociatedText != null) {
                cbcBlockCipherMac.update(this.initialAssociatedText, 0, this.initialAssociatedText.length);
            }
            if (this.associatedText.size() > 0) {
                cbcBlockCipherMac.update(this.associatedText.getBuffer(), 0, this.associatedText.size());
            }
            final int n8 = (n7 + associatedTextLength) % 16;
            if (n8 != 0) {
                for (int j = n8; j != 16; ++j) {
                    cbcBlockCipherMac.update((byte)0);
                }
            }
        }
        cbcBlockCipherMac.update(array, n, n2);
        return cbcBlockCipherMac.doFinal(array2, 0);
    }
    
    private int getMacSize(final boolean b, final int n) {
        if (b && (n < 32 || n > 128 || 0x0 != (n & 0xF))) {
            throw new IllegalArgumentException("tag length in octets must be one of {4,6,8,10,12,14,16}");
        }
        return n >>> 3;
    }
    
    private int getAssociatedTextLength() {
        return this.associatedText.size() + ((this.initialAssociatedText == null) ? 0 : this.initialAssociatedText.length);
    }
    
    private boolean hasAssociatedText() {
        return this.getAssociatedTextLength() > 0;
    }
    
    private static class ExposedByteArrayOutputStream extends ByteArrayOutputStream
    {
        public ExposedByteArrayOutputStream() {
        }
        
        public byte[] getBuffer() {
            return this.buf;
        }
    }
}
