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

package org.bouncycastle.crypto.macs;

import org.bouncycastle.crypto.digests.XofUtils;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.util.Pack;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Strings;
import org.bouncycastle.crypto.digests.CSHAKEDigest;
import org.bouncycastle.crypto.digests.EncodableDigest;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.crypto.Xof;
import org.bouncycastle.crypto.Mac;

public class KMAC implements Mac, Xof, Memoable, EncodableDigest
{
    private static final byte[] padding;
    private final CSHAKEDigest cshake;
    private int bitLength;
    private int outputLength;
    private byte[] key;
    private boolean initialised;
    private boolean firstOutput;
    
    public KMAC(final int bitLength, final byte[] array) {
        this.cshake = new CSHAKEDigest(bitLength, Strings.toByteArray("KMAC"), array);
        this.bitLength = bitLength;
        this.outputLength = bitLength * 2 / 8;
    }
    
    public KMAC(final KMAC kmac) {
        this.cshake = new CSHAKEDigest(kmac.cshake);
        this.bitLength = kmac.bitLength;
        this.outputLength = kmac.outputLength;
        this.key = kmac.key;
        this.initialised = kmac.initialised;
        this.firstOutput = kmac.firstOutput;
    }
    
    public KMAC(final byte[] array) {
        System.arraycopy(array, 1, this.key = new byte[array[0] & 0xFF], 0, this.key.length);
        this.cshake = new CSHAKEDigest(Arrays.copyOfRange(array, 1 + this.key.length, array.length - 10));
        this.bitLength = Pack.bigEndianToInt(array, array.length - 10);
        this.outputLength = Pack.bigEndianToInt(array, array.length - 6);
        this.initialised = (array[array.length - 2] != 0);
        this.firstOutput = (array[array.length - 1] != 0);
    }
    
    private void copyIn(final KMAC kmac) {
        this.cshake.reset(kmac.cshake);
        this.bitLength = kmac.bitLength;
        this.outputLength = kmac.outputLength;
        this.initialised = kmac.initialised;
        this.firstOutput = kmac.firstOutput;
    }
    
    @Override
    public void init(final CipherParameters cipherParameters) throws IllegalArgumentException {
        this.key = Arrays.clone(((KeyParameter)cipherParameters).getKey());
        if (this.key.length > 255) {
            throw new IllegalArgumentException("key length must be between 0 and 2040 bits");
        }
        this.initialised = true;
        this.reset();
    }
    
    @Override
    public String getAlgorithmName() {
        return "KMAC" + this.cshake.getAlgorithmName().substring(6);
    }
    
    @Override
    public int getByteLength() {
        return this.cshake.getByteLength();
    }
    
    @Override
    public int getMacSize() {
        return this.outputLength;
    }
    
    @Override
    public int getDigestSize() {
        return this.outputLength;
    }
    
    @Override
    public void update(final byte b) throws IllegalStateException {
        if (!this.initialised) {
            throw new IllegalStateException("KMAC not initialized");
        }
        this.cshake.update(b);
    }
    
    @Override
    public void update(final byte[] array, final int n, final int n2) throws DataLengthException, IllegalStateException {
        if (!this.initialised) {
            throw new IllegalStateException("KMAC not initialized");
        }
        this.cshake.update(array, n, n2);
    }
    
    @Override
    public int doFinal(final byte[] array, final int n) throws DataLengthException, IllegalStateException {
        if (this.firstOutput) {
            if (!this.initialised) {
                throw new IllegalStateException("KMAC not initialized");
            }
            final byte[] rightEncode = XofUtils.rightEncode(this.getMacSize() * 8);
            this.cshake.update(rightEncode, 0, rightEncode.length);
        }
        final int doFinal = this.cshake.doFinal(array, n, this.getMacSize());
        this.reset();
        return doFinal;
    }
    
    @Override
    public int doFinal(final byte[] array, final int n, final int n2) {
        if (this.firstOutput) {
            if (!this.initialised) {
                throw new IllegalStateException("KMAC not initialized");
            }
            final byte[] rightEncode = XofUtils.rightEncode(n2 * 8);
            this.cshake.update(rightEncode, 0, rightEncode.length);
        }
        final int doFinal = this.cshake.doFinal(array, n, n2);
        this.reset();
        return doFinal;
    }
    
    @Override
    public int doOutput(final byte[] array, final int n, final int n2) {
        if (this.firstOutput) {
            if (!this.initialised) {
                throw new IllegalStateException("KMAC not initialized");
            }
            final byte[] rightEncode = XofUtils.rightEncode(0L);
            this.cshake.update(rightEncode, 0, rightEncode.length);
            this.firstOutput = false;
        }
        return this.cshake.doOutput(array, n, n2);
    }
    
    @Override
    public void reset() {
        this.cshake.reset();
        if (this.key != null) {
            if (this.bitLength == 128) {
                this.bytePad(this.key, 168);
            }
            else {
                this.bytePad(this.key, 136);
            }
        }
        this.firstOutput = true;
    }
    
    private void bytePad(final byte[] array, final int n) {
        final byte[] leftEncode = XofUtils.leftEncode(n);
        this.update(leftEncode, 0, leftEncode.length);
        final byte[] encode = encode(array);
        this.update(encode, 0, encode.length);
        int i = n - (leftEncode.length + encode.length) % n;
        if (i > 0 && i != n) {
            while (i > KMAC.padding.length) {
                this.update(KMAC.padding, 0, KMAC.padding.length);
                i -= KMAC.padding.length;
            }
            this.update(KMAC.padding, 0, i);
        }
    }
    
    private static byte[] encode(final byte[] array) {
        return Arrays.concatenate(XofUtils.leftEncode(array.length * 8), array);
    }
    
    @Override
    public byte[] getEncodedState() {
        if (!this.initialised) {
            throw new IllegalStateException("KMAC not initialised");
        }
        final byte[] encodedState = this.cshake.getEncodedState();
        final byte[] array = new byte[10];
        Pack.intToBigEndian(this.bitLength, array, 0);
        Pack.intToBigEndian(this.outputLength, array, 4);
        array[8] = (byte)(this.initialised ? 1 : 0);
        array[9] = (byte)(this.firstOutput ? 1 : 0);
        final byte[] array2 = new byte[1 + this.key.length + encodedState.length + array.length];
        array2[0] = (byte)this.key.length;
        System.arraycopy(this.key, 0, array2, 1, this.key.length);
        System.arraycopy(encodedState, 0, array2, 1 + this.key.length, encodedState.length);
        System.arraycopy(array, 0, array2, 1 + this.key.length + encodedState.length, array.length);
        return array2;
    }
    
    @Override
    public Memoable copy() {
        return new KMAC(this);
    }
    
    @Override
    public void reset(final Memoable memoable) {
        this.copyIn((KMAC)memoable);
    }
    
    static {
        padding = new byte[100];
    }
}
